@brainervirus/workit-cli 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ workit flow pause --plan docs/<slug>/plan.md [--confirm] # active -> paused
|
|
|
25
25
|
workit flow resume --plan docs/<slug>/plan.md [--confirm] # paused -> active
|
|
26
26
|
workit flow complete --plan docs/<slug>/plan.md [--confirm] # complete (ledger + verification gated)
|
|
27
27
|
workit flow review-package --plan docs/<slug>/plan.md --base <sha> --head <sha> [--confirm] # write a review diff
|
|
28
|
+
workit flow append-advisory --plan docs/<slug>/plan.md --task <id> --text <text> [--confirm] # record an advisory review finding
|
|
28
29
|
workit handoff --message "docs/<slug>/plan.md" # print the destination handoff prompt
|
|
29
30
|
workit # help
|
|
30
31
|
```
|
|
@@ -7,12 +7,13 @@ Load `using-superpowers`, `subagent-driven-development`, `test-driven-developmen
|
|
|
7
7
|
|
|
8
8
|
## Handoff destination
|
|
9
9
|
|
|
10
|
-
This session is a handoff destination for a continued plan. The originating session already recorded the post-plan menu choice; present exactly these four choices and never re-offer the originating handoff option:
|
|
10
|
+
This session is a handoff destination for a continued plan. The originating session already recorded the post-plan menu choice; present exactly these four choices plus model deferral and never re-offer the originating handoff option:
|
|
11
11
|
|
|
12
12
|
- Subagent-driven
|
|
13
13
|
- Inline
|
|
14
14
|
- Review spec first
|
|
15
15
|
- Review plan first
|
|
16
|
+
- Change model first
|
|
16
17
|
|
|
17
18
|
<workflow-handoff-destination>true</workflow-handoff-destination>
|
|
18
19
|
|
|
@@ -24,6 +25,7 @@ This session is a handoff destination for a continued plan. The originating sess
|
|
|
24
25
|
- Use native `todowrite` for visible task state as well as the gitignored ledger.
|
|
25
26
|
- Use native `question` for branch/stash choices and guarded external mutations; call mutation tools only after approval with `confirmed: true` (grounded in the recorded NativeChoiceEvidence).
|
|
26
27
|
- Flow-tool confirmations are never agent-typed booleans and never caller-supplied evidence objects: on OpenCode the plugin records the user's native-`question` answer as a host-observed one-use receipt (`attested: true`, `callID`, `selectedLabel`, `recordedAt`) consumed by `workflow_spec_approve` / `workflow_plan_approve` / `workflow_plan_menu` — no evidence argument exists, and delegated worker status comes from host session parentage (`parentID`), never a caller `role` field. On Cursor, confirmations are policy-only (`attested: false`) and subagent-driven execution is rejected as unsupported.
|
|
28
|
+
- Delegated authority is direct-child-only: a worker is the session whose host `parentID` exactly equals the activating coordinator's recorded `coordinator_session_id`; missing, mismatched, or multi-owner lineage fails closed with `delegation_lineage_denied`, and nested `opencode` launches are denied during active delegated work. An authorized child receives only the compact worker contract (execute the supplied brief, follow TDD, land one contiguous non-empty commit range, report results) — never coordinator guidance, `wk-implement`, or ledger management; coordinator bookkeeping via `workflow_sdd_*` stays with the coordinator session.
|
|
27
29
|
- On Cursor, for every repository-scoped `workflow_*` call, pass the active Cursor workspace as `workspace_root`; never rely on the MCP process default.
|
|
28
30
|
- Use native `task` with only the built-in `explore` and `general` agents.
|
|
29
31
|
|
|
@@ -52,7 +54,7 @@ For each top-level task absent from `completed_task_ids`:
|
|
|
52
54
|
3. Delegate read-only discovery, when needed, to an `explore` agent. Delegate implementation to a fresh `general` agent. Product changes follow TDD.
|
|
53
55
|
4. Create a working-state diff with `workflow_sdd_review_package` and `confirmed: true`.
|
|
54
56
|
5. Delegate spec-compliance review and code-quality review to separate `general` agents.
|
|
55
|
-
6. **Blocking** findings (Critical, Important, or spec-compliance) may trigger at most **two** fix+re-review rounds per task. **Advisory** findings (Minor, style, YAGNI, taste) never pause the loop — append them
|
|
57
|
+
6. **Blocking** findings (Critical, Important, or spec-compliance) may trigger at most **two** fix+re-review rounds per task. **Advisory** findings (Minor, style, YAGNI, taste) never pause the loop — append them with `workflow_sdd_append_advisory` (`--task <id> --text <text>`, `confirmed: true`) instead of an unrestricted file edit.
|
|
56
58
|
7. Append the validated ledger entry with `workflow_sdd_append_progress` and `confirmed: true`; mark the todo completed.
|
|
57
59
|
|
|
58
60
|
## Final gate
|
|
@@ -60,10 +60,13 @@ On success, use native `question` / Cursor `AskQuestion` with exactly these opti
|
|
|
60
60
|
3. Handoff → load `wk-handoff` (new session only)
|
|
61
61
|
4. Review spec first
|
|
62
62
|
5. Review plan first
|
|
63
|
+
6. Change model first
|
|
64
|
+
|
|
65
|
+
`Change model first` is display-only deferral: it ends the turn without calling `workflow_plan_menu` and re-presents the menu on the next turn. Every other choice must call `workflow_plan_menu` immediately after the answer and before any skill, branch question, mutation, or handoff.
|
|
63
66
|
|
|
64
67
|
Never emit Superpowers text beginning “Two execution options”.
|
|
65
68
|
|
|
66
|
-
A handoff destination session (the seeded contract carries `<workflow-handoff-destination>true</workflow-handoff-destination>`) presents exactly
|
|
69
|
+
A handoff destination session (the seeded contract carries `<workflow-handoff-destination>true</workflow-handoff-destination>`) presents exactly five choices — Subagent-driven, Inline, Review spec first, Review plan first, Change model first — and never re-offers the originating handoff option.
|
|
67
70
|
|
|
68
71
|
- Specs/plans must follow `templates/spec-template.md` / `templates/plan-template.md` (mandated diagrams, tables, CA-XX).
|
|
69
72
|
|
package/dist/index.js
CHANGED
|
@@ -43490,7 +43490,11 @@ var checkLauncher = (res) => {
|
|
|
43490
43490
|
detail: "no dev checkout found (WORKFLOW_TOOLKIT_DEV) — skipping non-Cursor launcher checks"
|
|
43491
43491
|
};
|
|
43492
43492
|
}
|
|
43493
|
-
return {
|
|
43493
|
+
return {
|
|
43494
|
+
id: "launcher",
|
|
43495
|
+
status: "pass",
|
|
43496
|
+
detail: `${res.host} launcher/hook entries present`
|
|
43497
|
+
};
|
|
43494
43498
|
};
|
|
43495
43499
|
var checkUtility = (res) => {
|
|
43496
43500
|
const git = commandOnPath("git", res.env);
|
|
@@ -43510,7 +43514,11 @@ var checkUtility = (res) => {
|
|
|
43510
43514
|
fix: "Install util-linux (flock)"
|
|
43511
43515
|
};
|
|
43512
43516
|
}
|
|
43513
|
-
return {
|
|
43517
|
+
return {
|
|
43518
|
+
id: "utility",
|
|
43519
|
+
status: "pass",
|
|
43520
|
+
detail: "git (and flock where required) on PATH"
|
|
43521
|
+
};
|
|
43514
43522
|
};
|
|
43515
43523
|
var staleEntry = (entry) => {
|
|
43516
43524
|
if (!entry)
|
|
@@ -43532,7 +43540,11 @@ var checkStalePin = (res) => {
|
|
|
43532
43540
|
};
|
|
43533
43541
|
}
|
|
43534
43542
|
if (!existsSync7(res.opencodeConfig)) {
|
|
43535
|
-
return {
|
|
43543
|
+
return {
|
|
43544
|
+
id: "stale_pin",
|
|
43545
|
+
status: "pass",
|
|
43546
|
+
detail: "no opencode config — not registered"
|
|
43547
|
+
};
|
|
43536
43548
|
}
|
|
43537
43549
|
const cfg = readJson(res.opencodeConfig);
|
|
43538
43550
|
const entries = pluginEntries(cfg);
|
|
@@ -43563,6 +43575,118 @@ var checkStalePin = (res) => {
|
|
|
43563
43575
|
}
|
|
43564
43576
|
return { id: "stale_pin", status: "pass", detail: "opencode pin resolves" };
|
|
43565
43577
|
};
|
|
43578
|
+
var pluginMcpSelectors = (res) => {
|
|
43579
|
+
const p = path9.join(res.cursorPluginDir, "mcp.json");
|
|
43580
|
+
if (!existsSync7(p))
|
|
43581
|
+
return null;
|
|
43582
|
+
const cfg = readJson(p);
|
|
43583
|
+
const server = cfg?.mcpServers?.workit;
|
|
43584
|
+
if (!server || typeof server !== "object" || Array.isArray(server))
|
|
43585
|
+
return null;
|
|
43586
|
+
const args = server.args;
|
|
43587
|
+
if (!Array.isArray(args))
|
|
43588
|
+
return null;
|
|
43589
|
+
return args.map(String).filter((a) => a.startsWith("--package="));
|
|
43590
|
+
};
|
|
43591
|
+
var registryLatestVersion = (res) => {
|
|
43592
|
+
const seam = res.env.WORKIT_DOCTOR_STALE_REGISTRY_VERSION;
|
|
43593
|
+
if (typeof seam === "string" && seam) {
|
|
43594
|
+
return /^\d+(?:\.\d+){1,2}$/.test(seam) ? seam : null;
|
|
43595
|
+
}
|
|
43596
|
+
const cmd = res.env.WORKIT_DOCTOR_STALE_REGISTRY_CMD ?? (commandOnPath("npm", res.env) ? "npm" : null);
|
|
43597
|
+
if (!cmd)
|
|
43598
|
+
return null;
|
|
43599
|
+
try {
|
|
43600
|
+
const r = spawnSync2(cmd, ["view", CURSOR_RUNTIME_PACKAGE, "version"], {
|
|
43601
|
+
encoding: "utf8",
|
|
43602
|
+
timeout: 20000,
|
|
43603
|
+
env: res.env
|
|
43604
|
+
});
|
|
43605
|
+
const v = (r.stdout ?? "").trim().split(/\s+/)[0];
|
|
43606
|
+
return r.status === 0 && /^\d+(?:\.\d+){1,2}$/.test(v) ? v : null;
|
|
43607
|
+
} catch {
|
|
43608
|
+
return null;
|
|
43609
|
+
}
|
|
43610
|
+
};
|
|
43611
|
+
var installedPluginVersion = (res) => {
|
|
43612
|
+
const pkg = readJson(path9.join(res.cursorPluginDir, "package.json"));
|
|
43613
|
+
return typeof pkg?.version === "string" && pkg.version ? pkg.version : null;
|
|
43614
|
+
};
|
|
43615
|
+
var checkStaleInstall = (res) => {
|
|
43616
|
+
if (res.host !== "cursor" && res.host !== "cli") {
|
|
43617
|
+
return {
|
|
43618
|
+
id: "stale_install",
|
|
43619
|
+
status: "pass",
|
|
43620
|
+
detail: "cursor plugin not inspected on the opencode host"
|
|
43621
|
+
};
|
|
43622
|
+
}
|
|
43623
|
+
const canonicalMcp = cursorMcpServerEntry("").args.find((a) => a.startsWith("--package="));
|
|
43624
|
+
const mcpSelectors = pluginMcpSelectors(res);
|
|
43625
|
+
const legacyPin = (mcpSelectors ?? []).find((s) => s !== canonicalMcp && s.includes("@brainervirus/workit-cursor@"));
|
|
43626
|
+
if (legacyPin) {
|
|
43627
|
+
return {
|
|
43628
|
+
id: "stale_install",
|
|
43629
|
+
status: "fail",
|
|
43630
|
+
detail: `stale_install: plugin mcp.json pins a legacy selector ${legacyPin} (canonical: ${canonicalMcp})`,
|
|
43631
|
+
fix: "Re-run install-cursor-plugin.sh — it rewrites the workit MCP entry to the canonical @latest selector"
|
|
43632
|
+
};
|
|
43633
|
+
}
|
|
43634
|
+
const hooksFile = path9.join(res.cursorPluginDir, "hooks", "hooks-cursor.json");
|
|
43635
|
+
const hookCmd = readJson(hooksFile)?.hooks?.sessionStart?.[0]?.command ?? null;
|
|
43636
|
+
const canonicalHook = canonicalCursorHook;
|
|
43637
|
+
const staleHook = hookCmd !== null && hookCmd !== canonicalHook && !hookCmd.startsWith("node ") && hookCmd.includes("@brainervirus/workit-cursor@");
|
|
43638
|
+
if (staleHook) {
|
|
43639
|
+
return {
|
|
43640
|
+
id: "stale_install",
|
|
43641
|
+
status: "fail",
|
|
43642
|
+
detail: `stale_install: sessionStart hook runs a legacy selector (canonical: ${canonicalHook})`,
|
|
43643
|
+
fix: "Re-run install-cursor-plugin.sh — it rewrites the sessionStart hook to the canonical @latest selector"
|
|
43644
|
+
};
|
|
43645
|
+
}
|
|
43646
|
+
const installed = installedPluginVersion(res);
|
|
43647
|
+
const source = res.dev ? readJson(path9.join(res.dev, "packages/workit-cursor/package.json"))?.version ?? null : readJson(path9.join(packageRoot(), "package.json"))?.version ?? null;
|
|
43648
|
+
const localDist = mcpSelectors === null && hookCmd !== null && hookCmd.startsWith("node ");
|
|
43649
|
+
if (localDist && installed !== null && source !== null && !semverAtLeast(installed, source)) {
|
|
43650
|
+
return {
|
|
43651
|
+
id: "stale_install",
|
|
43652
|
+
status: "fail",
|
|
43653
|
+
detail: `stale_install: installed workit-cursor ${installed} is behind the current runtime ${source}`,
|
|
43654
|
+
fix: "Re-run install-cursor-plugin.sh — it refreshes the plugin directory and rewrites the workit MCP/hook entries"
|
|
43655
|
+
};
|
|
43656
|
+
}
|
|
43657
|
+
if (installed !== null && localDist) {
|
|
43658
|
+
const expected = registryLatestVersion(res);
|
|
43659
|
+
if (expected === null) {
|
|
43660
|
+
return {
|
|
43661
|
+
id: "registry_unreachable",
|
|
43662
|
+
status: "warn",
|
|
43663
|
+
registryProbed: true,
|
|
43664
|
+
detail: "registry_unreachable: cannot compare installed workit-cursor against the published runtime",
|
|
43665
|
+
fix: "Retry when the npm registry is reachable, or re-run install-cursor-plugin.sh to refresh the install"
|
|
43666
|
+
};
|
|
43667
|
+
}
|
|
43668
|
+
if (!semverAtLeast(installed, expected)) {
|
|
43669
|
+
return {
|
|
43670
|
+
id: "stale_install",
|
|
43671
|
+
status: "fail",
|
|
43672
|
+
registryProbed: true,
|
|
43673
|
+
detail: `stale_install: local-dist workit-cursor ${installed} is behind the published runtime ${expected}`,
|
|
43674
|
+
fix: "Re-run install-cursor-plugin.sh — it refreshes the plugin directory with the current build"
|
|
43675
|
+
};
|
|
43676
|
+
}
|
|
43677
|
+
return {
|
|
43678
|
+
id: "stale_install",
|
|
43679
|
+
status: "pass",
|
|
43680
|
+
registryProbed: true,
|
|
43681
|
+
detail: `installed local-dist workit-cursor ${installed} matches the published runtime ${expected}`
|
|
43682
|
+
};
|
|
43683
|
+
}
|
|
43684
|
+
return {
|
|
43685
|
+
id: "stale_install",
|
|
43686
|
+
status: "pass",
|
|
43687
|
+
detail: installed === null ? "installed workit-cursor selectors are canonical" : `installed workit-cursor ${installed} is metadata on the canonical @latest install (fresh at launch)`
|
|
43688
|
+
};
|
|
43689
|
+
};
|
|
43566
43690
|
var checkDuplicateRegistration = (res) => {
|
|
43567
43691
|
const problems = [];
|
|
43568
43692
|
const opencodeHost = res.host !== "cursor";
|
|
@@ -43598,7 +43722,11 @@ var checkDuplicateRegistration = (res) => {
|
|
|
43598
43722
|
}
|
|
43599
43723
|
}
|
|
43600
43724
|
if (problems.length === 0) {
|
|
43601
|
-
return {
|
|
43725
|
+
return {
|
|
43726
|
+
id: "duplicate_registration",
|
|
43727
|
+
status: "pass",
|
|
43728
|
+
detail: "no duplicate registrations"
|
|
43729
|
+
};
|
|
43602
43730
|
}
|
|
43603
43731
|
return {
|
|
43604
43732
|
id: "duplicate_registration",
|
|
@@ -43624,7 +43752,11 @@ var checkMalformedConfig = (res) => {
|
|
|
43624
43752
|
files.push(res.cursorMcp);
|
|
43625
43753
|
const bad = files.filter((p) => !parsesAsConfigObject(p));
|
|
43626
43754
|
if (bad.length === 0)
|
|
43627
|
-
return {
|
|
43755
|
+
return {
|
|
43756
|
+
id: "malformed_config",
|
|
43757
|
+
status: "pass",
|
|
43758
|
+
detail: "config files parse"
|
|
43759
|
+
};
|
|
43628
43760
|
return {
|
|
43629
43761
|
id: "malformed_config",
|
|
43630
43762
|
status: "fail",
|
|
@@ -43635,10 +43767,18 @@ var checkMalformedConfig = (res) => {
|
|
|
43635
43767
|
var checkWorkspaceMismatch = (res) => {
|
|
43636
43768
|
const file = path9.join(res.configDir, "workspaces.json");
|
|
43637
43769
|
if (!existsSync7(file))
|
|
43638
|
-
return {
|
|
43770
|
+
return {
|
|
43771
|
+
id: "workspace_mismatch",
|
|
43772
|
+
status: "pass",
|
|
43773
|
+
detail: "no workspaces configured"
|
|
43774
|
+
};
|
|
43639
43775
|
const ws = readJson(file);
|
|
43640
43776
|
if (!Array.isArray(ws?.workspaces)) {
|
|
43641
|
-
return {
|
|
43777
|
+
return {
|
|
43778
|
+
id: "workspace_mismatch",
|
|
43779
|
+
status: "pass",
|
|
43780
|
+
detail: "no workspaces configured"
|
|
43781
|
+
};
|
|
43642
43782
|
}
|
|
43643
43783
|
const match = resolveWorkspaceFrom(res.cwd, res.configDir);
|
|
43644
43784
|
if (match)
|
|
@@ -43680,7 +43820,11 @@ var checkCredentialMetadata = (res) => {
|
|
|
43680
43820
|
}
|
|
43681
43821
|
}
|
|
43682
43822
|
if (tokenPaths.length === 0) {
|
|
43683
|
-
return {
|
|
43823
|
+
return {
|
|
43824
|
+
id: "credential_metadata",
|
|
43825
|
+
status: "pass",
|
|
43826
|
+
detail: "no credentials configured"
|
|
43827
|
+
};
|
|
43684
43828
|
}
|
|
43685
43829
|
const problems = [];
|
|
43686
43830
|
for (const raw of tokenPaths) {
|
|
@@ -43718,7 +43862,11 @@ var checkLogWritable = (res) => {
|
|
|
43718
43862
|
mkdirSync2(logsDir, { recursive: true, mode: 448 });
|
|
43719
43863
|
writeFileSync4(probe, `{"probe":true}
|
|
43720
43864
|
`, { mode: 384 });
|
|
43721
|
-
return {
|
|
43865
|
+
return {
|
|
43866
|
+
id: "log_writable",
|
|
43867
|
+
status: "pass",
|
|
43868
|
+
detail: "log directory writable"
|
|
43869
|
+
};
|
|
43722
43870
|
} catch (err) {
|
|
43723
43871
|
return {
|
|
43724
43872
|
id: "log_writable",
|
|
@@ -43739,6 +43887,7 @@ var RUN_CHECKS = [
|
|
|
43739
43887
|
checkLauncher,
|
|
43740
43888
|
checkUtility,
|
|
43741
43889
|
checkStalePin,
|
|
43890
|
+
checkStaleInstall,
|
|
43742
43891
|
checkDuplicateRegistration,
|
|
43743
43892
|
checkMalformedConfig,
|
|
43744
43893
|
checkWorkspaceMismatch,
|
|
@@ -43751,21 +43900,27 @@ var INSTALLER_REQUIRED = new Set([
|
|
|
43751
43900
|
"launcher",
|
|
43752
43901
|
"utility",
|
|
43753
43902
|
"stale_pin",
|
|
43903
|
+
"stale_install",
|
|
43754
43904
|
"duplicate_registration",
|
|
43755
43905
|
"malformed_config"
|
|
43756
43906
|
]);
|
|
43757
43907
|
var runDoctor = (options = {}) => {
|
|
43758
43908
|
const res = resolve(options);
|
|
43759
43909
|
const raw = RUN_CHECKS.map((fn) => fn(res));
|
|
43760
|
-
const checks = res.installer ? raw.map((c) => c.status === "fail" && !INSTALLER_REQUIRED.has(c.id) ? {
|
|
43910
|
+
const checks = res.installer ? raw.map((c) => c.status === "fail" && !INSTALLER_REQUIRED.has(c.id) ? {
|
|
43911
|
+
...c,
|
|
43912
|
+
status: "warn",
|
|
43913
|
+
detail: `${c.detail} (not enforced by installer)`
|
|
43914
|
+
} : c) : raw;
|
|
43761
43915
|
const failed = checks.filter((c) => c.status === "fail").length;
|
|
43762
43916
|
const warned = checks.filter((c) => c.status === "warn").length;
|
|
43763
43917
|
const passed = checks.filter((c) => c.status === "pass").length;
|
|
43764
43918
|
const exitCode = failed > 0 ? 1 : 0;
|
|
43919
|
+
const offline = !raw.some((c) => ("registryProbed" in c) && c.registryProbed);
|
|
43765
43920
|
const report = {
|
|
43766
43921
|
ok: failed === 0,
|
|
43767
43922
|
exitCode,
|
|
43768
|
-
offline
|
|
43923
|
+
offline,
|
|
43769
43924
|
host: res.host,
|
|
43770
43925
|
checked_at: new Date().toISOString(),
|
|
43771
43926
|
summary: { passed, warned, failed, total: checks.length },
|
|
@@ -43775,7 +43930,7 @@ var runDoctor = (options = {}) => {
|
|
|
43775
43930
|
getDiagnosticLogger()?.info(EVENT.doctor, {
|
|
43776
43931
|
host: res.host,
|
|
43777
43932
|
exit_code: exitCode,
|
|
43778
|
-
offline
|
|
43933
|
+
offline,
|
|
43779
43934
|
failed: checks.filter((c) => c.status === "fail").map((c) => c.id),
|
|
43780
43935
|
total: checks.length
|
|
43781
43936
|
});
|
|
@@ -46281,6 +46436,7 @@ import {
|
|
|
46281
46436
|
fsyncSync,
|
|
46282
46437
|
mkdirSync as mkdirSync8,
|
|
46283
46438
|
openSync,
|
|
46439
|
+
readdirSync as readdirSync5,
|
|
46284
46440
|
readFileSync as readFileSync13,
|
|
46285
46441
|
renameSync as renameSync2,
|
|
46286
46442
|
rmSync as rmSync2,
|
|
@@ -46827,6 +46983,58 @@ function sddReviewPackage({
|
|
|
46827
46983
|
return { error: error instanceof Error ? error.message : "git diff failed" };
|
|
46828
46984
|
}
|
|
46829
46985
|
}
|
|
46986
|
+
function sddAppendAdvisory({
|
|
46987
|
+
advisories_path,
|
|
46988
|
+
task_id,
|
|
46989
|
+
text,
|
|
46990
|
+
workspace_root
|
|
46991
|
+
}) {
|
|
46992
|
+
if (typeof task_id !== "number" || !Number.isSafeInteger(task_id) || task_id <= 0) {
|
|
46993
|
+
return { error: "task_id must be a positive safe integer", code: "advisory_task_invalid" };
|
|
46994
|
+
}
|
|
46995
|
+
if (typeof text !== "string") {
|
|
46996
|
+
return {
|
|
46997
|
+
error: "advisory text must be a string of 1-1000 characters after normalization",
|
|
46998
|
+
code: "advisory_text_invalid"
|
|
46999
|
+
};
|
|
47000
|
+
}
|
|
47001
|
+
if (text.includes("\r") || text.includes(`
|
|
47002
|
+
`)) {
|
|
47003
|
+
return {
|
|
47004
|
+
error: "advisory text must be a single line (no CR/LF)",
|
|
47005
|
+
code: "advisory_text_invalid"
|
|
47006
|
+
};
|
|
47007
|
+
}
|
|
47008
|
+
const collapsed = text.trim().replace(/[ \t]+/g, " ");
|
|
47009
|
+
if (collapsed.length === 0 || collapsed.length > 1000) {
|
|
47010
|
+
return {
|
|
47011
|
+
error: "advisory text must be 1-1000 characters after trim and horizontal-space collapse",
|
|
47012
|
+
code: "advisory_text_invalid"
|
|
47013
|
+
};
|
|
47014
|
+
}
|
|
47015
|
+
if (!/^docs\/[^/]+\/sdd\/advisories\.md$/.test(advisories_path)) {
|
|
47016
|
+
return {
|
|
47017
|
+
error: `advisories_path must be docs/<slug>/sdd/advisories.md: ${advisories_path}`,
|
|
47018
|
+
code: "advisory_path_invalid"
|
|
47019
|
+
};
|
|
47020
|
+
}
|
|
47021
|
+
const contained = resolveDocsPath({ workspace_root, path: advisories_path });
|
|
47022
|
+
if (!contained.ok)
|
|
47023
|
+
return { error: contained.error, code: "advisory_path_invalid" };
|
|
47024
|
+
const abs = contained.path;
|
|
47025
|
+
if (existsSync12(abs) && statSync5(abs).isDirectory()) {
|
|
47026
|
+
return {
|
|
47027
|
+
error: `advisory target is a directory: ${advisories_path}`,
|
|
47028
|
+
code: "advisory_target_invalid"
|
|
47029
|
+
};
|
|
47030
|
+
}
|
|
47031
|
+
mkdirSync6(path15.dirname(abs), { recursive: true });
|
|
47032
|
+
const line = `- Task ${task_id}: ${collapsed}
|
|
47033
|
+
`;
|
|
47034
|
+
appendFileSync2(abs, line, "utf8");
|
|
47035
|
+
const rel = posix2(path15.relative(contained.base, abs));
|
|
47036
|
+
return { ok: true, advisory: collapsed, advisories_path: rel };
|
|
47037
|
+
}
|
|
46830
47038
|
|
|
46831
47039
|
// packages/workit-core/src/core/verify-project.ts
|
|
46832
47040
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -47215,7 +47423,7 @@ root: ${cwd2}
|
|
|
47215
47423
|
}
|
|
47216
47424
|
|
|
47217
47425
|
// packages/workit-core/src/core/flow-state.ts
|
|
47218
|
-
var COORDINATOR_RECOVERY_TEXT = "A subagent-driven plan is active: coordinator product edits are blocked. " + "Delegate product mutations
|
|
47426
|
+
var COORDINATOR_RECOVERY_TEXT = "A subagent-driven plan is active: coordinator product edits are blocked. " + "Delegate product mutations to an authenticated delegated worker via `task` / `wk-implement` instead of " + "editing in the coordinator session.";
|
|
47219
47427
|
var CURSOR_SUBAGENT_UNSUPPORTED_TEXT = "Cursor cannot execute subagent-driven plans: the MCP has no child-session " + "support. Choose Inline, Handoff, or a review option in this session, or " + "run the plan in OpenCode with `wk-implement`.";
|
|
47220
47428
|
var MENU_CHOICES = [
|
|
47221
47429
|
"subagent-driven",
|
|
@@ -47348,6 +47556,12 @@ var validateState = (parsed, slug) => {
|
|
|
47348
47556
|
if (execRaw?.evidence !== undefined && !validateEvidenceValue(execRaw.evidence, true)) {
|
|
47349
47557
|
return { ok: false, error: "flow state execution.evidence has an unsupported shape" };
|
|
47350
47558
|
}
|
|
47559
|
+
if (execRaw?.coordinator_session_id !== undefined && execRaw.coordinator_session_id !== null && typeof execRaw.coordinator_session_id !== "string") {
|
|
47560
|
+
return {
|
|
47561
|
+
ok: false,
|
|
47562
|
+
error: "flow state execution.coordinator_session_id must be a string or null"
|
|
47563
|
+
};
|
|
47564
|
+
}
|
|
47351
47565
|
return {
|
|
47352
47566
|
ok: true,
|
|
47353
47567
|
state: {
|
|
@@ -47363,7 +47577,8 @@ var validateState = (parsed, slug) => {
|
|
|
47363
47577
|
execution: {
|
|
47364
47578
|
status: execRaw?.status ?? "pending",
|
|
47365
47579
|
mode: execRaw?.mode ?? null,
|
|
47366
|
-
evidence: execRaw?.evidence ?? null
|
|
47580
|
+
evidence: execRaw?.evidence ?? null,
|
|
47581
|
+
coordinator_session_id: execRaw?.coordinator_session_id ?? null
|
|
47367
47582
|
},
|
|
47368
47583
|
handoff_destination: parsed.handoff_destination ?? false,
|
|
47369
47584
|
updated_at: parsed.updated_at ?? Date.now()
|
|
@@ -47500,7 +47715,7 @@ var resetForSpecDrift = (state) => ({
|
|
|
47500
47715
|
spec: { ...state.spec, status: "draft", evidence: null, approved_digest: null },
|
|
47501
47716
|
plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
|
|
47502
47717
|
menu: { presented: false, chosen: "", evidence: null },
|
|
47503
|
-
execution: { status: "pending", mode: null, evidence: null },
|
|
47718
|
+
execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
|
|
47504
47719
|
handoff_destination: false,
|
|
47505
47720
|
updated_at: Date.now()
|
|
47506
47721
|
});
|
|
@@ -47543,9 +47758,14 @@ var reconcileState = (root, slug, state) => {
|
|
|
47543
47758
|
var deriveLegacyExecution = (root, slug, state) => {
|
|
47544
47759
|
const ledger = ledgerCompletion(root, slug);
|
|
47545
47760
|
if (state.plan.status === "approved" && state.menu.chosen === "subagent-driven" && ledger.started && !ledger.complete) {
|
|
47546
|
-
return {
|
|
47761
|
+
return {
|
|
47762
|
+
status: "active",
|
|
47763
|
+
mode: "subagent-driven",
|
|
47764
|
+
evidence: null,
|
|
47765
|
+
coordinator_session_id: null
|
|
47766
|
+
};
|
|
47547
47767
|
}
|
|
47548
|
-
return { status: "pending", mode: null, evidence: null };
|
|
47768
|
+
return { status: "pending", mode: null, evidence: null, coordinator_session_id: null };
|
|
47549
47769
|
};
|
|
47550
47770
|
var normalizeCompatibility = (root, slug, parsed, state) => {
|
|
47551
47771
|
if (!isRecord3(parsed) || !("execution" in parsed)) {
|
|
@@ -47554,6 +47774,11 @@ var normalizeCompatibility = (root, slug, parsed, state) => {
|
|
|
47554
47774
|
if (derived.status !== current.status || derived.mode !== current.mode) {
|
|
47555
47775
|
return { state: { ...state, execution: derived, updated_at: Date.now() }, changed: true };
|
|
47556
47776
|
}
|
|
47777
|
+
return { state, changed: false };
|
|
47778
|
+
}
|
|
47779
|
+
const execRaw = isRecord3(parsed.execution) ? parsed.execution : undefined;
|
|
47780
|
+
if (execRaw && !("coordinator_session_id" in execRaw)) {
|
|
47781
|
+
return { state: { ...state, updated_at: Date.now() }, changed: true };
|
|
47557
47782
|
}
|
|
47558
47783
|
return { state, changed: false };
|
|
47559
47784
|
};
|
|
@@ -47716,19 +47941,49 @@ var isNegativeLabel = (label) => {
|
|
|
47716
47941
|
return firstWord.replace(/[^a-z]/g, "") === entry;
|
|
47717
47942
|
});
|
|
47718
47943
|
};
|
|
47944
|
+
var receiptPurposeForLabel = (label) => {
|
|
47945
|
+
const n = normalizeLabel(label);
|
|
47946
|
+
if (n === "approve spec" || n === "approve spec recommended")
|
|
47947
|
+
return "spec-approval";
|
|
47948
|
+
if (n === "approve plan" || n === "approve plan recommended")
|
|
47949
|
+
return "plan-approval";
|
|
47950
|
+
if (n === "approve")
|
|
47951
|
+
return;
|
|
47952
|
+
if (n === "pause plan")
|
|
47953
|
+
return "plan-pause";
|
|
47954
|
+
if (n === "resume plan")
|
|
47955
|
+
return "plan-resume";
|
|
47956
|
+
if (n === "complete plan")
|
|
47957
|
+
return "plan-complete";
|
|
47958
|
+
const exec3 = new Set([
|
|
47959
|
+
"subagent driven",
|
|
47960
|
+
"inline",
|
|
47961
|
+
"handoff",
|
|
47962
|
+
"review spec",
|
|
47963
|
+
"review plan",
|
|
47964
|
+
"change model"
|
|
47965
|
+
]);
|
|
47966
|
+
if (exec3.has(n))
|
|
47967
|
+
return "execution-menu";
|
|
47968
|
+
return;
|
|
47969
|
+
};
|
|
47719
47970
|
|
|
47720
47971
|
class HostReceiptStore {
|
|
47721
47972
|
#bySession = new Map;
|
|
47722
|
-
record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question) {
|
|
47723
|
-
const
|
|
47724
|
-
if (!
|
|
47973
|
+
record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question = "", purpose) {
|
|
47974
|
+
const trimmed = selectedLabel.trim();
|
|
47975
|
+
if (!trimmed)
|
|
47725
47976
|
return;
|
|
47726
47977
|
if (recordedAt > Date.now() + MAX_CLOCK_SKEW_MS)
|
|
47727
47978
|
return;
|
|
47979
|
+
const derived = purpose ?? receiptPurposeForLabel(selectedLabel);
|
|
47980
|
+
if (derived === undefined)
|
|
47981
|
+
return;
|
|
47982
|
+
const q = question ?? "";
|
|
47728
47983
|
const queue = this.#bySession.get(sessionId) ?? [];
|
|
47729
47984
|
if (queue.length >= MAX_RECEIPTS_PER_SESSION)
|
|
47730
47985
|
queue.shift();
|
|
47731
|
-
queue.push({ sessionId, callID, selectedLabel
|
|
47986
|
+
queue.push({ sessionId, callID, selectedLabel, recordedAt, question: q, purpose: derived });
|
|
47732
47987
|
this.#bySession.set(sessionId, queue);
|
|
47733
47988
|
}
|
|
47734
47989
|
count(sessionId) {
|
|
@@ -47745,10 +48000,40 @@ class HostReceiptStore {
|
|
|
47745
48000
|
if (!queue || queue.length === 0) {
|
|
47746
48001
|
return err2("receipt_missing", "no host-observed native-question receipt for this session — ask the native " + "`question` tool and have the user answer before calling this tool");
|
|
47747
48002
|
}
|
|
47748
|
-
|
|
48003
|
+
let index = -1;
|
|
48004
|
+
if (opts.purpose !== undefined) {
|
|
48005
|
+
const top = queue[queue.length - 1];
|
|
48006
|
+
if (top && top.purpose === opts.purpose && isNegativeLabel(top.selectedLabel)) {
|
|
48007
|
+
const filtered = queue.filter((r) => r.purpose !== opts.purpose);
|
|
48008
|
+
if (filtered.length === 0)
|
|
48009
|
+
this.#bySession.delete(sessionId);
|
|
48010
|
+
else
|
|
48011
|
+
this.#bySession.set(sessionId, filtered);
|
|
48012
|
+
return err2("receipt_rejected", `the user's most recent answer (${JSON.stringify(top.selectedLabel)}) is a ` + "negative answer — it cannot authorize an approval; ask the native question again");
|
|
48013
|
+
}
|
|
48014
|
+
for (let i = queue.length - 1;i >= 0; i--) {
|
|
48015
|
+
if (queue[i].purpose === opts.purpose) {
|
|
48016
|
+
index = i;
|
|
48017
|
+
break;
|
|
48018
|
+
}
|
|
48019
|
+
}
|
|
48020
|
+
if (index === -1) {
|
|
48021
|
+
return err2("receipt_missing", `no host-observed receipt for purpose ${JSON.stringify(opts.purpose)} — ask the native question for that purpose`);
|
|
48022
|
+
}
|
|
48023
|
+
} else {
|
|
48024
|
+
index = queue.length - 1;
|
|
48025
|
+
}
|
|
47749
48026
|
const receipt = queue[index];
|
|
47750
48027
|
if (isNegativeLabel(receipt.selectedLabel)) {
|
|
47751
|
-
|
|
48028
|
+
if (opts.purpose !== undefined) {
|
|
48029
|
+
const filtered = queue.filter((r) => r.purpose !== opts.purpose);
|
|
48030
|
+
if (filtered.length === 0)
|
|
48031
|
+
this.#bySession.delete(sessionId);
|
|
48032
|
+
else
|
|
48033
|
+
this.#bySession.set(sessionId, filtered);
|
|
48034
|
+
} else {
|
|
48035
|
+
this.#bySession.delete(sessionId);
|
|
48036
|
+
}
|
|
47752
48037
|
return err2("receipt_rejected", `the user's most recent answer (${JSON.stringify(receipt.selectedLabel)}) is a ` + "negative answer — it cannot authorize an approval; ask the native question again");
|
|
47753
48038
|
}
|
|
47754
48039
|
if (opts.label !== undefined && !sameChoiceLabel(receipt.selectedLabel, opts.label)) {
|
|
@@ -47940,7 +48225,7 @@ var completeExecution = (root, slug, deps) => {
|
|
|
47940
48225
|
}
|
|
47941
48226
|
const next = {
|
|
47942
48227
|
...reconciled.state,
|
|
47943
|
-
execution: { ...exec3, status: "completed" },
|
|
48228
|
+
execution: { ...exec3, status: "completed", coordinator_session_id: null },
|
|
47944
48229
|
handoff_destination: false,
|
|
47945
48230
|
updated_at: Date.now()
|
|
47946
48231
|
};
|
|
@@ -48000,6 +48285,38 @@ var slugFromPath = (p) => {
|
|
|
48000
48285
|
const dirName = path18.basename(path18.dirname(p));
|
|
48001
48286
|
return dirName === "." || dirName === "/" || dirName === "" ? "" : dirName;
|
|
48002
48287
|
};
|
|
48288
|
+
var assertSddControlGates = (root, slug, opts = {}, ctx) => {
|
|
48289
|
+
const bound = assertMutationWorkspace(root, ctx);
|
|
48290
|
+
if (!bound.ok)
|
|
48291
|
+
return bound;
|
|
48292
|
+
const effective = readEffectiveFlowState(root, slug);
|
|
48293
|
+
if (!effective.ok)
|
|
48294
|
+
return effective;
|
|
48295
|
+
const state = effective.state;
|
|
48296
|
+
if (state.spec.status !== "approved") {
|
|
48297
|
+
return err2("spec_not_approved", `spec not approved (status: ${state.spec.status}). Run workflow_spec_approve after the user's approval.`);
|
|
48298
|
+
}
|
|
48299
|
+
if (state.plan.status !== "approved") {
|
|
48300
|
+
return err2("plan_not_approved", `plan not approved (status: ${state.plan.status}). Run workflow_plan_approve after the user's approval.`);
|
|
48301
|
+
}
|
|
48302
|
+
if (opts.requireMenu && !state.menu.presented) {
|
|
48303
|
+
return err2("menu_not_presented", "post-plan menu not presented. Record the native question answer with workflow_plan_menu.");
|
|
48304
|
+
}
|
|
48305
|
+
if (opts.requireDocs) {
|
|
48306
|
+
const validated = docsValidate({
|
|
48307
|
+
spec_path: path18.posix.join("docs", slug, "spec.md"),
|
|
48308
|
+
plan_path: path18.posix.join("docs", slug, "plan.md"),
|
|
48309
|
+
workspace_root: root
|
|
48310
|
+
});
|
|
48311
|
+
if (validated.ok === false)
|
|
48312
|
+
return err2("docs_invalid", validated.error);
|
|
48313
|
+
}
|
|
48314
|
+
if (state.execution.status === "active" && state.execution.mode === "subagent-driven" && roleFromParentage(ctx?.parentSessionId, state.execution.coordinator_session_id) === "delegated") {
|
|
48315
|
+
return err2("sdd_control_denied", "SDD control metadata is coordinator-owned while a subagent-driven plan is active — delegated workers cannot mutate task briefs, review packages, progress, or advisories");
|
|
48316
|
+
}
|
|
48317
|
+
return { ok: true };
|
|
48318
|
+
};
|
|
48319
|
+
var roleFromParentage = (parentID, coordinatorSessionId) => typeof parentID === "string" && parentID !== "" && parentID === coordinatorSessionId ? "delegated" : "coordinator";
|
|
48003
48320
|
var BASH_READ_TOKENS = new Set([
|
|
48004
48321
|
"cat",
|
|
48005
48322
|
"head",
|
|
@@ -48127,12 +48444,12 @@ var COORDINATOR_SHELL_DENIED_TEXT = "Coordinator shell commands are restricted w
|
|
|
48127
48444
|
import path20 from "node:path";
|
|
48128
48445
|
|
|
48129
48446
|
// packages/workit-core/src/core/handoff-context.ts
|
|
48130
|
-
import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as
|
|
48447
|
+
import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
|
|
48131
48448
|
import path19 from "node:path";
|
|
48132
48449
|
var DOC_RE = /docs\/([A-Za-z0-9][A-Za-z0-9._-]*)\/(spec|plan)\.md/g;
|
|
48133
48450
|
var listMd = (dir) => {
|
|
48134
48451
|
try {
|
|
48135
|
-
return
|
|
48452
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".md")).sort();
|
|
48136
48453
|
} catch {
|
|
48137
48454
|
return [];
|
|
48138
48455
|
}
|
|
@@ -48158,7 +48475,7 @@ var resolveActivePair = (root) => {
|
|
|
48158
48475
|
let best = null;
|
|
48159
48476
|
let entries = [];
|
|
48160
48477
|
try {
|
|
48161
|
-
entries =
|
|
48478
|
+
entries = readdirSync6(docsDir);
|
|
48162
48479
|
} catch {
|
|
48163
48480
|
return { error: "no pair" };
|
|
48164
48481
|
}
|
|
@@ -48250,13 +48567,21 @@ var buildHandoffPrompt = (root, message) => {
|
|
|
48250
48567
|
};
|
|
48251
48568
|
|
|
48252
48569
|
// packages/workit-cli/src/flow.ts
|
|
48253
|
-
var FLOW_ACTIONS = [
|
|
48570
|
+
var FLOW_ACTIONS = [
|
|
48571
|
+
"status",
|
|
48572
|
+
"pause",
|
|
48573
|
+
"resume",
|
|
48574
|
+
"complete",
|
|
48575
|
+
"review-package",
|
|
48576
|
+
"append-advisory"
|
|
48577
|
+
];
|
|
48254
48578
|
var COMMANDS = {
|
|
48255
48579
|
status: "workit flow status --plan <path>",
|
|
48256
48580
|
pause: "workit flow pause --plan <path> [--confirm]",
|
|
48257
48581
|
resume: "workit flow resume --plan <path> [--confirm]",
|
|
48258
48582
|
complete: "workit flow complete --plan <path> [--confirm]",
|
|
48259
48583
|
"review-package": "workit flow review-package --plan <path> --base <sha> --head <sha> [--confirm]",
|
|
48584
|
+
"append-advisory": "workit flow append-advisory --plan <path> --task <id> --text <text> [--confirm]",
|
|
48260
48585
|
handoff: "workit handoff --message <text>"
|
|
48261
48586
|
};
|
|
48262
48587
|
var FLOW_COMMANDS = {
|
|
@@ -48264,7 +48589,8 @@ var FLOW_COMMANDS = {
|
|
|
48264
48589
|
pause: COMMANDS.pause,
|
|
48265
48590
|
resume: COMMANDS.resume,
|
|
48266
48591
|
complete: COMMANDS.complete,
|
|
48267
|
-
"review-package": COMMANDS["review-package"]
|
|
48592
|
+
"review-package": COMMANDS["review-package"],
|
|
48593
|
+
"append-advisory": COMMANDS["append-advisory"]
|
|
48268
48594
|
};
|
|
48269
48595
|
var CLI_FLAG_EVIDENCE = { host: "cli", attested: false, confirmation: "flag" };
|
|
48270
48596
|
var CLI_TTY_EVIDENCE = { host: "cli", attested: false, confirmation: "tty" };
|
|
@@ -48297,14 +48623,22 @@ var usage = (err3, text) => {
|
|
|
48297
48623
|
write(err3, text);
|
|
48298
48624
|
return 2;
|
|
48299
48625
|
};
|
|
48300
|
-
var VALUE_FLAGS = ["--plan", "--base", "--head"];
|
|
48626
|
+
var VALUE_FLAGS = ["--plan", "--base", "--head", "--task", "--text"];
|
|
48301
48627
|
function parseFlowFlags(action, argv, err3) {
|
|
48302
48628
|
const parsed = {};
|
|
48303
48629
|
let confirm = false;
|
|
48304
48630
|
for (let i = 0;i < argv.length; i++) {
|
|
48305
48631
|
const token = argv[i];
|
|
48306
48632
|
if (VALUE_FLAGS.includes(token)) {
|
|
48307
|
-
if (token !== "--plan" && action !== "review-package") {
|
|
48633
|
+
if (token !== "--plan" && action !== "review-package" && action !== "append-advisory") {
|
|
48634
|
+
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${token}`);
|
|
48635
|
+
return { ok: false };
|
|
48636
|
+
}
|
|
48637
|
+
if ((token === "--base" || token === "--head") && action !== "review-package") {
|
|
48638
|
+
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${token}`);
|
|
48639
|
+
return { ok: false };
|
|
48640
|
+
}
|
|
48641
|
+
if ((token === "--task" || token === "--text") && action !== "append-advisory") {
|
|
48308
48642
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${token}`);
|
|
48309
48643
|
return { ok: false };
|
|
48310
48644
|
}
|
|
@@ -48318,7 +48652,7 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48318
48652
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${value}`);
|
|
48319
48653
|
return { ok: false };
|
|
48320
48654
|
}
|
|
48321
|
-
const existing = token === "--plan" ? parsed.plan : token === "--base" ? parsed.base : parsed.head;
|
|
48655
|
+
const existing = token === "--plan" ? parsed.plan : token === "--base" ? parsed.base : token === "--head" ? parsed.head : token === "--task" ? parsed.task : parsed.text;
|
|
48322
48656
|
if (existing !== undefined) {
|
|
48323
48657
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — duplicate ${token} flag`);
|
|
48324
48658
|
return { ok: false };
|
|
@@ -48327,8 +48661,12 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48327
48661
|
parsed.plan = value;
|
|
48328
48662
|
else if (token === "--base")
|
|
48329
48663
|
parsed.base = value;
|
|
48330
|
-
else
|
|
48664
|
+
else if (token === "--head")
|
|
48331
48665
|
parsed.head = value;
|
|
48666
|
+
else if (token === "--task")
|
|
48667
|
+
parsed.task = value;
|
|
48668
|
+
else
|
|
48669
|
+
parsed.text = value;
|
|
48332
48670
|
i += 1;
|
|
48333
48671
|
} else if (token === "--confirm") {
|
|
48334
48672
|
if (action === "status") {
|
|
@@ -48359,7 +48697,27 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48359
48697
|
return { ok: false };
|
|
48360
48698
|
}
|
|
48361
48699
|
}
|
|
48362
|
-
|
|
48700
|
+
if (action === "append-advisory") {
|
|
48701
|
+
if (parsed.task === undefined) {
|
|
48702
|
+
usage(err3, `usage: ${FLOW_COMMANDS[action]} — --task <id> required`);
|
|
48703
|
+
return { ok: false };
|
|
48704
|
+
}
|
|
48705
|
+
if (parsed.text === undefined) {
|
|
48706
|
+
usage(err3, `usage: ${FLOW_COMMANDS[action]} — --text <text> required`);
|
|
48707
|
+
return { ok: false };
|
|
48708
|
+
}
|
|
48709
|
+
}
|
|
48710
|
+
return {
|
|
48711
|
+
ok: true,
|
|
48712
|
+
parsed: {
|
|
48713
|
+
plan: parsed.plan,
|
|
48714
|
+
confirm,
|
|
48715
|
+
base: parsed.base,
|
|
48716
|
+
head: parsed.head,
|
|
48717
|
+
task: parsed.task,
|
|
48718
|
+
text: parsed.text
|
|
48719
|
+
}
|
|
48720
|
+
};
|
|
48363
48721
|
}
|
|
48364
48722
|
async function resolveConfirmation(deps, confirmFlag, out, err3) {
|
|
48365
48723
|
if (confirmFlag)
|
|
@@ -48435,12 +48793,34 @@ function reviewPackageCommand(root, parsed, out, err3) {
|
|
|
48435
48793
|
}
|
|
48436
48794
|
return domainFail(err3, result2.code ?? "review_package_failed", result2.error);
|
|
48437
48795
|
}
|
|
48796
|
+
function appendAdvisoryCommand(root, parsed, out, err3) {
|
|
48797
|
+
const resolved = resolveCanonicalLayout({ workspace_root: root, plan_path: parsed.plan });
|
|
48798
|
+
if (!resolved.ok)
|
|
48799
|
+
return domainFail(err3, "path_invalid", resolved.error);
|
|
48800
|
+
const slug = resolved.layout.slug;
|
|
48801
|
+
const controlGate = assertSddControlGates(root, slug, { requireMenu: true, requireDocs: true });
|
|
48802
|
+
if (!controlGate.ok)
|
|
48803
|
+
return domainFail(err3, controlGate.code, controlGate.error);
|
|
48804
|
+
const trimmed = parsed.task.trim();
|
|
48805
|
+
const taskId = /^-?\d+(\.\d+)?$/.test(trimmed) ? Number(trimmed) : trimmed;
|
|
48806
|
+
const result2 = sddAppendAdvisory({
|
|
48807
|
+
advisories_path: path21.posix.join("docs", slug, "sdd", "advisories.md"),
|
|
48808
|
+
task_id: taskId,
|
|
48809
|
+
text: parsed.text,
|
|
48810
|
+
workspace_root: root
|
|
48811
|
+
});
|
|
48812
|
+
if ("error" in result2) {
|
|
48813
|
+
return domainFail(err3, result2.code ?? "advisory_failed", result2.error);
|
|
48814
|
+
}
|
|
48815
|
+
writeJSON(out, { ok: true, advisories_path: result2.advisories_path, advisory: result2.advisory });
|
|
48816
|
+
return 0;
|
|
48817
|
+
}
|
|
48438
48818
|
async function runFlowCommand(argv, deps = {}) {
|
|
48439
48819
|
const out = outStream(deps);
|
|
48440
48820
|
const err3 = errStream(deps);
|
|
48441
48821
|
const [action, ...rest2] = argv;
|
|
48442
48822
|
if (!action || !FLOW_ACTIONS.includes(action)) {
|
|
48443
|
-
return usage(err3, "usage: workit flow <status|pause|resume|complete|review-package> --plan <path> [--confirm]");
|
|
48823
|
+
return usage(err3, "usage: workit flow <status|pause|resume|complete|review-package|append-advisory> --plan <path> [--confirm]");
|
|
48444
48824
|
}
|
|
48445
48825
|
const flowAction = action;
|
|
48446
48826
|
const parsed = parseFlowFlags(flowAction, rest2, err3);
|
|
@@ -48456,6 +48836,9 @@ async function runFlowCommand(argv, deps = {}) {
|
|
|
48456
48836
|
if (flowAction === "review-package") {
|
|
48457
48837
|
return reviewPackageCommand(root, parsed.parsed, out, err3);
|
|
48458
48838
|
}
|
|
48839
|
+
if (flowAction === "append-advisory") {
|
|
48840
|
+
return appendAdvisoryCommand(root, parsed.parsed, out, err3);
|
|
48841
|
+
}
|
|
48459
48842
|
return mutateCommand(root, parsed.parsed.plan, flowAction, evidence, deps, out, err3);
|
|
48460
48843
|
}
|
|
48461
48844
|
async function runHandoffCommand(argv, deps = {}) {
|
|
@@ -48494,6 +48877,7 @@ var COMMAND_DESCRIPTIONS = [
|
|
|
48494
48877
|
[COMMANDS.resume, "Resume a paused plan"],
|
|
48495
48878
|
[COMMANDS.complete, "Complete a plan (ledger and verification gated)"],
|
|
48496
48879
|
[COMMANDS["review-package"], "Write a review diff for a base..head range"],
|
|
48880
|
+
[COMMANDS["append-advisory"], "Append an advisory line to docs/<slug>/sdd/advisories.md"],
|
|
48497
48881
|
[COMMANDS.handoff, "Emit the destination handoff prompt for a plan"]
|
|
48498
48882
|
];
|
|
48499
48883
|
var helpColumn = Math.max(...COMMAND_DESCRIPTIONS.map(([cmd]) => cmd.length)) + 2;
|
|
@@ -48582,7 +48966,7 @@ function runDoctorCommand(args) {
|
|
|
48582
48966
|
if (args.includes("--json")) {
|
|
48583
48967
|
console.log(JSON.stringify(report, null, 2));
|
|
48584
48968
|
} else {
|
|
48585
|
-
console.log(`workit doctor — ${report.ok ? "healthy" : "problems found"} (offline)`);
|
|
48969
|
+
console.log(`workit doctor — ${report.ok ? "healthy" : "problems found"} (${report.offline ? "offline" : "online"})`);
|
|
48586
48970
|
for (const check2 of report.checks) {
|
|
48587
48971
|
const mark = check2.status === "fail" ? "FAIL" : check2.status === "warn" ? "WARN" : "ok ";
|
|
48588
48972
|
console.log(`${mark} ${check2.id} — ${check2.detail}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit interactive setup wizard (Ink TUI)",
|
|
6
6
|
"keywords": [
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"typecheck": "tsc --noEmit"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@brainervirus/workit-core": "^0.8.
|
|
38
|
-
"@brainervirus/workit-cursor": "^0.8.
|
|
39
|
-
"@brainervirus/workit-opencode": "^0.8.
|
|
37
|
+
"@brainervirus/workit-core": "^0.8.9",
|
|
38
|
+
"@brainervirus/workit-cursor": "^0.8.9",
|
|
39
|
+
"@brainervirus/workit-opencode": "^0.8.9",
|
|
40
40
|
"@inkjs/ui": "2.0.0",
|
|
41
41
|
"ink": "7.1.1",
|
|
42
42
|
"react": "19.2.8"
|