Skip to content

Commit d2f2c9f

Browse files
committed
fix(kiro-ide): make hooks work via USER_PROMPT env, not stdin
Kiro IDE delivers hook context through the USER_PROMPT environment variable (not stdin, which it opens but never writes), and leaves toolArgs empty. The IDE adapter now reads USER_PROMPT, recovers the written file path from the tool-result prose, and drives the command/payload-free hooks off the audit trail. - audit-logger, sensor-fire, runtime-compile, sync-statusline now do real work on Kiro IDE; write paths resolved relative to workspace root. - runtime-compile and sync-statusline gate on the audit tail (payload-free). - sync-statusline ide-audit-sync is forward-only (never rewinds Current Stage): skips when not Running, pointer none, or audit slug already completed/skipped. - runtime-compile IDE path adds an mtime idempotency guard so a lingering transition does not recompile on every subsequent shell command. - extractWrittenPath tolerates trailing newlines, strips a str_replace " (N occurrences)" suffix, and records a visible hook-drop when a write-class tool yields no extractable path. - latestStartedStageSlug ignores synthetic --single stage-runner rows. - opt-in hook debug log: AIDLC_HOOK_DEBUG=1 or touch aidlc/.aidlc-hook-debug. Kiro CLI, Claude Code, and Codex behaviour unchanged (new branches gated behind the IDE-only ide-audit-sync marker and the opt-in debug env var).
1 parent 242953e commit d2f2c9f

41 files changed

Lines changed: 2396 additions & 323 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,67 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.2.11] - 2026-07-08
6+
7+
Fixes the Kiro IDE harness's hooks, which previously no-op'd for everything that
8+
needed to know what the agent just did. Kiro IDE delivers hook context through
9+
the `USER_PROMPT` environment variable (not stdin, which it opens but never
10+
writes), and it leaves the tool arguments empty — so the IDE adapter now reads
11+
`USER_PROMPT`, recovers the written file path from the tool result text, and
12+
drives the command/payload-free hooks off the audit trail instead. The audit
13+
logger, sensor firing, runtime-graph recompile, and statusline sync now work in
14+
Kiro IDE without the agent having to compile the graph by hand. Dropping the
15+
stdin read also removes the 2s stdin-race timeout the CLI adapter paid on every
16+
hook; the human-presence mint/block hooks now resolve the project dir from
17+
`process.cwd()`. The Kiro CLI harness is unaffected (it uses a separate adapter
18+
and a working stdin channel). Re-copy your `dist/kiro-ide/.kiro/` to pick up the
19+
fix.
20+
21+
* **Kiro IDE hooks fire correctly**`aidlc-audit-logger`, `aidlc-sensor-fire`,
22+
`aidlc-runtime-compile`, and `aidlc-sync-statusline` now do real work on Kiro
23+
IDE. Artifact writes are audited, sensors run, the runtime graph recompiles on
24+
transitions, and `Current Stage` stays in sync.
25+
* **File paths are resolved relative to the workspace root.** Kiro IDE reports
26+
the written path relative to the project root in the tool-result text; the
27+
adapter resolves it to absolute so `audit-logger`'s record-root check passes.
28+
* **`aidlc-runtime-compile` and `aidlc-sync-statusline` are payload-free on the
29+
IDE** — both gate on the audit tail (latest transition / latest
30+
`STAGE_STARTED`) instead of the tool command or task payload the IDE does not
31+
surface. `aidlc-sync-statusline` is wired to the `shell` tool event.
32+
* **The IDE audit-tail gating is forward-only and idempotent.**
33+
`aidlc-sync-statusline` never rewinds `Current Stage`: it skips when the
34+
workflow is not `Running`, when the pointer is `none`, or when the audit's
35+
latest stage is already completed/skipped. `latestStartedStageSlug` ignores
36+
synthetic `--single` stage-runner rows. `aidlc-runtime-compile`'s IDE path
37+
adds an mtime idempotency guard so a lingering transition (e.g. after
38+
`WORKFLOW_COMPLETED`) does not recompile on every subsequent shell command.
39+
* **The `str_replace`/`fs_append` path extraction is robust.** The adapter
40+
tolerates trailing newlines, strips a `str_replace` ` (N occurrences)`
41+
suffix, and records a visible hook-drop when a write-class tool yields no
42+
extractable path (no silent no-op).
43+
* **The human-presence mint/block hooks survive the stdin removal.** The
44+
`promptSubmit` mint (`HUMAN_TURN`) and the `preToolUse` block floor now
45+
resolve the project dir from `process.cwd()` (the IDE gives no `cwd` payload),
46+
so the Kiro IDE human-presence gate keeps working; dropping the stdin read
47+
also removes the 2s stdin-race the block hook paid on every `preToolUse`.
48+
* **`log-subagent` records the delegate's identity.** The adapter no longer
49+
hardcodes `agent_type: "unknown"`; it extracts the self-identifying first line
50+
(`**Reviewer:** <name>` / `**Agent:** <name>`) from the subagent result and
51+
forwards the result text, so `SUBAGENT_COMPLETED` carries the real agent.
52+
* **Failed tool calls are not audited as writes.** `audit-and-sensors`
53+
explicitly guards `toolSuccess === false`, so a failed op no longer relies on
54+
its error prose failing to match the path regexes.
55+
* **Opt-in hook debug log.** Enable it to have every hook append its decision
56+
path to `<record>/.aidlc-hooks-health/hook-debug.log` — either set
57+
`AIDLC_HOOK_DEBUG=1`, or `touch aidlc/.aidlc-hook-debug` (the filesystem
58+
marker takes effect on the next hook fire with no IDE restart). Off by
59+
default (no log, no overhead); see the Kiro IDE harness guide.
60+
* **Behaviour for Kiro CLI, Claude Code, and Codex is unchanged.** The shared
61+
core hooks are edited and re-shipped to all four dists, but the new branches
62+
are gated behind the IDE-only `ide-audit-sync` marker (and the opt-in
63+
`AIDLC_HOOK_DEBUG` env var), so the CLI/Claude/Codex payload paths behave
64+
exactly as before.
65+
566
## [2.2.10] - 2026-07-06
667

768
Workspace detection now recognizes git submodules. A workspace whose code lives in uninitialized submodules (empty dirs plus a `.gitmodules` file) previously scanned as Greenfield, so reverse-engineering was auto-skipped and every design stage ran with zero code understanding. The scanner gains a sixth brownfield signal: a parseable `.gitmodules` with at least one submodule path entry classifies the workspace Brownfield. When submodule paths are uninitialized, the scan warns and names the remedy (`git submodule update --init --recursive`) at birth, in the doctor report, and on `detect`. Languages stay as scanned (Unknown is truthful until the submodules are fetched). **Upgrade:** re-copy your `dist/<harness>/` shell into the project.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A native implementation of the **AI-DLC methodology** (AI-Driven Development Lif
88

99
The methodology lives once, in a harness-neutral `core/`; each harness adds a thin surface that decides how it shows up on that harness. So you edit the methodology in one place, and every harness distribution is generated from it — no harness gets special treatment. (See [Repository layout](#repository-layout) for how the pieces fit together.)
1010

11-
![version](https://img.shields.io/badge/version-2.2.10-blue)
11+
![version](https://img.shields.io/badge/version-2.2.11-blue)
1212
![license](https://img.shields.io/badge/license-MIT--0-green)
1313
![Kiro IDE](https://img.shields.io/badge/harness-Kiro%20IDE-orange)
1414
![Kiro CLI](https://img.shields.io/badge/harness-Kiro%20CLI-orange)

core/hooks/aidlc-audit-logger.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
type ClaudeCodeHookInput,
1414
docsRoot,
1515
errorMessage,
16+
hookDebug,
1617
hooksHealthDir,
1718
isClaudeCodeHookInput,
1819
isoTimestamp,
@@ -21,6 +22,7 @@ import {
2122
} from "../tools/aidlc-lib.ts";
2223

2324
const projectDir = resolveProjectDirFromHook(import.meta.url);
25+
hookDebug(projectDir, "audit-logger", "invoked", { projectDir, cwd: process.cwd() });
2426

2527
// Write health heartbeat
2628
const healthDir = hooksHealthDir(projectDir);
@@ -30,15 +32,22 @@ writeFileSync(join(healthDir, "audit-logger.last"), isoTimestamp(), "utf-8");
3032
// Read JSON from stdin. If stdin is a TTY (interactive shell, test harness
3133
// running under `bash -x`-inheriting pipeline), no JSON is coming — exit
3234
// cleanly instead of blocking on the terminal read.
33-
if (process.stdin.isTTY) process.exit(0);
35+
if (process.stdin.isTTY) {
36+
hookDebug(projectDir, "audit-logger", "exit: stdin isTTY");
37+
process.exit(0);
38+
}
3439

3540
const input = await Bun.stdin.text();
3641
let parsed: ClaudeCodeHookInput;
3742
try {
3843
const raw: unknown = JSON.parse(input);
39-
if (!isClaudeCodeHookInput(raw)) process.exit(0);
44+
if (!isClaudeCodeHookInput(raw)) {
45+
hookDebug(projectDir, "audit-logger", "exit: not ClaudeCodeHookInput", { input: input.slice(0, 200) });
46+
process.exit(0);
47+
}
4048
parsed = raw;
4149
} catch {
50+
hookDebug(projectDir, "audit-logger", "exit: stdin parse failed", { input: input.slice(0, 200) });
4251
process.exit(0);
4352
}
4453

@@ -54,7 +63,11 @@ const fileNorm = auditFileValue; // forward-slash form for all path matching bel
5463
// the bare space record root — the write is logged iff it lands under that root.
5564
const recordRoot = docsRoot(projectDir).replace(/\\/g, "/").replace(/\/$/, "");
5665
const underRecord = fileNorm === recordRoot || fileNorm.startsWith(`${recordRoot}/`);
57-
if (!underRecord) process.exit(0);
66+
hookDebug(projectDir, "audit-logger", "path-gate", { tool, file: fileNorm, recordRoot, underRecord });
67+
if (!underRecord) {
68+
hookDebug(projectDir, "audit-logger", "exit: not under record root");
69+
process.exit(0);
70+
}
5871

5972
// Don't log writes to an audit shard itself (avoid recursion). The shard is
6073
// audit/<host>-<clone>.md under the record dir; the bare audit.md guard also
@@ -64,13 +77,17 @@ if (
6477
file.endsWith("\\audit.md") ||
6578
/[/\\]audit[/\\][^/\\]+\.md$/.test(file)
6679
) {
80+
hookDebug(projectDir, "audit-logger", "exit: write to audit shard (recursion guard)");
6781
process.exit(0);
6882
}
6983

7084
const auditFile = auditFilePath(projectDir);
7185

7286
// Don't auto-create the audit trail — the orchestrator creates it at workflow start.
73-
if (!existsSync(auditFile)) process.exit(0);
87+
if (!existsSync(auditFile)) {
88+
hookDebug(projectDir, "audit-logger", "exit: audit file missing", { auditFile });
89+
process.exit(0);
90+
}
7491

7592
// Extract the context breadcrumb: the path relative to the record root (the
7693
// per-intent record dir on the new layout, or the flat `aidlc-docs/` root).
@@ -123,10 +140,12 @@ try {
123140
File: auditFileValue,
124141
Context: context,
125142
}, projectDir);
143+
hookDebug(projectDir, "audit-logger", "emitted", { eventType, file: auditFileValue, context });
126144
} catch (e) {
127145
// Hook must be a no-op on any audit emission failure to avoid breaking the
128146
// user's tool call. Record the drop so `--doctor` can surface it, then
129147
// exit cleanly.
148+
hookDebug(projectDir, "audit-logger", "exit: emit threw", { eventType, error: errorMessage(e) });
130149
recordHookDrop(projectDir, "audit-logger", errorMessage(e));
131150
process.exit(0);
132151
}

core/hooks/aidlc-runtime-compile.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,27 @@
2121
// compile's own audit emits cannot re-trigger the compile.
2222

2323
import { spawnSync } from "node:child_process";
24-
import { mkdirSync, writeFileSync } from "node:fs";
24+
import { mkdirSync, statSync, writeFileSync } from "node:fs";
2525
import { join } from "node:path";
2626
import {
2727
activeIntent,
2828
activeSpace,
29+
auditShards,
2930
type ClaudeCodeHookInput,
3031
errorMessage,
32+
hookDebug,
3133
hooksHealthDir,
3234
isClaudeCodeHookInput,
3335
isoTimestamp,
3436
readAllAuditShards,
3537
recordHookDrop,
3638
resolveProjectDirFromHook,
39+
runtimeGraphPath,
3740
harnessDir,
3841
} from "../tools/aidlc-lib.ts";
3942

4043
const projectDir = resolveProjectDirFromHook(import.meta.url);
44+
hookDebug(projectDir, "runtime-compile", "invoked");
4145

4246
// 1. TTY guard — exit cleanly when invoked outside a piped stdin context
4347
// (interactive shell, test harness running under `bash -x`).
@@ -68,8 +72,20 @@ const command: string = parsed.tool_input?.command ?? "";
6872
const aidlcTransitionTool = /\bbun\b.*\.(?:claude|kiro|codex)\/tools\/aidlc-(state|jump|bolt|utility)\.ts\b/;
6973
const aidlcOrchestrateReport = /\bbun\b.*\.(?:claude|kiro|codex)\/tools\/aidlc-orchestrate\.ts\b.*\breport\b/;
7074
const aidlcRuntimeRef = /\bbun\b.*\.(?:claude|kiro|codex)\/tools\/aidlc-runtime\.ts\b/;
71-
if (aidlcRuntimeRef.test(command)) process.exit(0);
72-
if (!aidlcTransitionTool.test(command) && !aidlcOrchestrateReport.test(command)) process.exit(0);
75+
// IDE audit-tail mode: Kiro IDE does not surface the shell command, so the
76+
// command-based filter cannot run. The adapter sets source="ide-audit-sync" to
77+
// signal "skip the command filter and gate purely on the audit tail" (steps
78+
// 6-7). The audit-tail transition check is the real gate; the command filter is
79+
// only a cheap pre-filter that needs a command string to work.
80+
const ideAuditMode = (parsed.tool_input?.source ?? "") === "ide-audit-sync";
81+
hookDebug(projectDir, "runtime-compile", "command-gate", { ideAuditMode, command: command.slice(0, 120) });
82+
if (!ideAuditMode) {
83+
if (aidlcRuntimeRef.test(command)) process.exit(0);
84+
if (!aidlcTransitionTool.test(command) && !aidlcOrchestrateReport.test(command)) {
85+
hookDebug(projectDir, "runtime-compile", "exit: command not a transition tool");
86+
process.exit(0);
87+
}
88+
}
7389

7490
// 4. Audit read — across EVERY per-clone shard of the ACTIVE intent, NOT this
7591
// hook process's own PID/clone shard. The state tool that wrote the
@@ -82,7 +98,10 @@ if (!aidlcTransitionTool.test(command) && !aidlcOrchestrateReport.test(command))
8298
const space = activeSpace(projectDir);
8399
const intent = activeIntent(projectDir, space) ?? undefined;
84100
const audit = readAllAuditShards(projectDir, intent, space).replace(/\r\n/g, "\n");
85-
if (audit.length === 0) process.exit(0);
101+
if (audit.length === 0) {
102+
hookDebug(projectDir, "runtime-compile", "exit: audit empty");
103+
process.exit(0);
104+
}
86105

87106
// 5. Heartbeat — doctor reads this file's mtime to detect silent-hook failure.
88107
// Kept at the bare (workspace-level) health dir to match where --doctor reads
@@ -111,7 +130,45 @@ const last3 = blocks.slice(-3);
111130
// (before the orchestrator wrote any §13 entries).
112131
const transitionRegex = /^\*\*Event\*\*:\s*(GATE_APPROVED|STAGE_STARTED|STAGE_AWAITING_APPROVAL|AUDIT_MERGED|WORKFLOW_COMPLETED)\s*$/m;
113132
const hasTransition = last3.some((b) => transitionRegex.test(b));
114-
if (!hasTransition) process.exit(0);
133+
hookDebug(projectDir, "runtime-compile", "transition-gate", { hasTransition, last3count: last3.length });
134+
if (!hasTransition) {
135+
hookDebug(projectDir, "runtime-compile", "exit: no transition in audit tail");
136+
process.exit(0);
137+
}
138+
139+
// 7b. Idempotency guard (IDE audit-tail mode only). On the CLI the command
140+
// filter (step 3) already bounds compiles to the one Bash call that emitted
141+
// the transition. In ide-audit-sync mode that filter is skipped, so the
142+
// transition sits in the tail across EVERY subsequent shell command — and
143+
// after WORKFLOW_COMPLETED the tail never changes again, which would make
144+
// every future shell command pay a blocking recompile forever. Bound it by
145+
// mtime: if runtime-graph.json is already at least as new as the newest
146+
// audit shard, the tail hasn't changed since the last compile — skip. A real
147+
// new transition bumps a shard's mtime past the graph and re-enables the
148+
// compile. Cheap stat calls; no new marker file.
149+
if (ideAuditMode) {
150+
try {
151+
const graphMtime = statSync(runtimeGraphPath(projectDir, intent, space)).mtimeMs;
152+
let newestShard = 0;
153+
for (const shard of auditShards(projectDir, intent, space)) {
154+
try {
155+
const m = statSync(shard).mtimeMs;
156+
if (m > newestShard) newestShard = m;
157+
} catch {
158+
// shard vanished mid-read — ignore
159+
}
160+
}
161+
if (graphMtime >= newestShard) {
162+
hookDebug(projectDir, "runtime-compile", "skip: graph newer than audit (idempotent)", {
163+
graphMtime,
164+
newestShard,
165+
});
166+
process.exit(0);
167+
}
168+
} catch {
169+
// runtime-graph.json absent (never compiled) → fall through and compile.
170+
}
171+
}
115172

116173
// 8. Dispatch — sync subprocess. Hook waits for completion. On non-zero
117174
// exit, record the drop for `--doctor` to surface; never block the

core/hooks/aidlc-sync-statusline.ts

Lines changed: 71 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,34 @@
1-
// PostToolUse hook: Sync aidlc-state.md on stage task activation
2-
// Triggered on TaskUpdate — extracts slug from activeForm "[slug]" suffix
3-
// Receives JSON on stdin from Claude Code
1+
// PostToolUse hook: Sync aidlc-state.md current stage.
2+
//
3+
// Two activation paths, distinguished by the payload:
4+
// 1. Claude Code / Kiro CLI — a TaskUpdate carrying status + activeForm
5+
// "[slug]". Fires on transition to in_progress; the slug comes from the
6+
// activeForm suffix.
7+
// 2. Kiro IDE — the IDE gives no task payload (toolArgs is empty), so the
8+
// adapter sends tool_input.source = "ide-audit-sync" and this hook reads
9+
// the latest STAGE_STARTED slug from the audit tail instead. Payload-free.
10+
// In both cases the slug is reconciled into the state file via set-status.
11+
// Receives JSON on stdin from the adapter / Claude Code.
412
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
513
import { join } from "node:path";
614
import {
715
type ClaudeCodeHookInput,
16+
getField,
17+
hookDebug,
818
hooksHealthDir,
919
isClaudeCodeHookInput,
1020
isoTimestamp,
21+
latestStartedStageSlug,
22+
parseCheckboxes,
23+
readAllAuditShards,
24+
readStateFile,
1125
resolveProjectDirFromHook,
1226
stateFilePath,
1327
harnessDir,
1428
} from "../tools/aidlc-lib.ts";
1529

1630
const projectDir = resolveProjectDirFromHook(import.meta.url);
31+
hookDebug(projectDir, "sync-statusline", "invoked");
1732

1833
// Read JSON from stdin. Exit cleanly if stdin is a TTY — no Claude Code JSON
1934
// coming in this scenario (test / direct-run / debug-mode inherited stdin).
@@ -29,30 +44,70 @@ try {
2944
process.exit(0);
3045
}
3146

32-
const status = parsed.tool_input?.status ?? "";
33-
34-
// Only fire when a task transitions to in_progress
35-
if (status !== "in_progress") process.exit(0);
36-
37-
const activeForm: string = parsed.tool_input?.activeForm ?? "";
38-
if (!activeForm) process.exit(0);
39-
40-
// Extract slug from "[slug]" suffix in activeForm
41-
const slugMatch = activeForm.match(/\[([a-z][a-z0-9-]*)\]$/);
42-
if (!slugMatch) process.exit(0);
43-
const slug = slugMatch[1];
44-
4547
// State file must exist (won't exist before handleInit runs)
4648
const stateFile = stateFilePath(projectDir);
4749
if (!existsSync(stateFile)) process.exit(0);
4850

51+
// Resolve the target slug by activation path.
52+
let slug = "";
53+
const source = parsed.tool_input?.source ?? "";
54+
if (source === "ide-audit-sync") {
55+
// Kiro IDE path: derive the current stage from the audit tail (payload-free).
56+
// This is a FORWARD-ONLY mirror: it may only nudge Current Stage toward the
57+
// latest STAGE_STARTED, never backward. Without the guards below, the last
58+
// STAGE_STARTED lingers after a stage completes (approve advances Current
59+
// Stage / finalize sets it to none + Status: Completed but emits no new
60+
// STAGE_STARTED), so a naive "audit != state → set-status" would resurrect a
61+
// finished stage — set-status forces Status: Running and flips the checkbox
62+
// back to in-progress. Guards, in order:
63+
const stateContent = readStateFile(projectDir);
64+
const status = (getField(stateContent, "Status") ?? "").trim();
65+
const current = (getField(stateContent, "Current Stage") ?? "").trim();
66+
const audit = readAllAuditShards(projectDir);
67+
const auditSlug = latestStartedStageSlug(audit);
68+
hookDebug(projectDir, "sync-statusline", "ide-audit-sync", { auditSlug, current, status });
69+
70+
// (a) Only sync a live, running workflow. A completed/parked workflow (Status
71+
// != Running) or a cleared pointer (Current Stage none/empty) is ahead of
72+
// the audit tail by design — never rewind it.
73+
if (status !== "Running") process.exit(0);
74+
if (current === "" || current === "none") process.exit(0);
75+
// (b) No audit slug, or it already matches state → nothing to do.
76+
if (!auditSlug) process.exit(0);
77+
if (current === auditSlug) process.exit(0);
78+
// (c) Never sync BACKWARD: if the audit slug is a stage the workflow has
79+
// already completed or skipped, the state is legitimately ahead of it
80+
// (the stage finished; a newer STAGE_STARTED just wasn't the last row).
81+
// Syncing would demote a done stage — refuse.
82+
const checkboxes = parseCheckboxes(stateContent);
83+
const auditCb = checkboxes.find((c) => c.slug === auditSlug);
84+
if (auditCb && (auditCb.state === "completed" || auditCb.state === "skipped")) {
85+
hookDebug(projectDir, "sync-statusline", "skip: audit slug already done/skipped", {
86+
auditSlug,
87+
auditState: auditCb.state,
88+
});
89+
process.exit(0);
90+
}
91+
slug = auditSlug;
92+
} else {
93+
// Claude Code / Kiro CLI path: TaskUpdate → in_progress with "[slug]" suffix.
94+
const status = parsed.tool_input?.status ?? "";
95+
if (status !== "in_progress") process.exit(0);
96+
const activeForm: string = parsed.tool_input?.activeForm ?? "";
97+
if (!activeForm) process.exit(0);
98+
const slugMatch = activeForm.match(/\[([a-z][a-z0-9-]*)\]$/);
99+
if (!slugMatch) process.exit(0);
100+
slug = slugMatch[1];
101+
}
102+
49103
// Health heartbeat
50104
const healthDir = hooksHealthDir(projectDir);
51105
mkdirSync(healthDir, { recursive: true });
52106
writeFileSync(join(healthDir, "sync-statusline.last"), isoTimestamp(), "utf-8");
53107

54108
// Update state file via set-status (call the utility tool directly)
55109
const toolPath = join(projectDir, harnessDir(), "tools", "aidlc-utility.ts");
110+
hookDebug(projectDir, "sync-statusline", "set-status", { slug });
56111
Bun.spawnSync(["bun", toolPath, "set-status", "--stage", slug, "--project-dir", projectDir], {
57112
stdout: "ignore",
58113
stderr: "ignore",

0 commit comments

Comments
 (0)