@brainervirus/workit-cli 0.8.8 → 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
|
@@ -46436,6 +46436,7 @@ import {
|
|
|
46436
46436
|
fsyncSync,
|
|
46437
46437
|
mkdirSync as mkdirSync8,
|
|
46438
46438
|
openSync,
|
|
46439
|
+
readdirSync as readdirSync5,
|
|
46439
46440
|
readFileSync as readFileSync13,
|
|
46440
46441
|
renameSync as renameSync2,
|
|
46441
46442
|
rmSync as rmSync2,
|
|
@@ -46982,6 +46983,58 @@ function sddReviewPackage({
|
|
|
46982
46983
|
return { error: error instanceof Error ? error.message : "git diff failed" };
|
|
46983
46984
|
}
|
|
46984
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
|
+
}
|
|
46985
47038
|
|
|
46986
47039
|
// packages/workit-core/src/core/verify-project.ts
|
|
46987
47040
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
@@ -47370,7 +47423,7 @@ root: ${cwd2}
|
|
|
47370
47423
|
}
|
|
47371
47424
|
|
|
47372
47425
|
// packages/workit-core/src/core/flow-state.ts
|
|
47373
|
-
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.";
|
|
47374
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`.";
|
|
47375
47428
|
var MENU_CHOICES = [
|
|
47376
47429
|
"subagent-driven",
|
|
@@ -47503,6 +47556,12 @@ var validateState = (parsed, slug) => {
|
|
|
47503
47556
|
if (execRaw?.evidence !== undefined && !validateEvidenceValue(execRaw.evidence, true)) {
|
|
47504
47557
|
return { ok: false, error: "flow state execution.evidence has an unsupported shape" };
|
|
47505
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
|
+
}
|
|
47506
47565
|
return {
|
|
47507
47566
|
ok: true,
|
|
47508
47567
|
state: {
|
|
@@ -47518,7 +47577,8 @@ var validateState = (parsed, slug) => {
|
|
|
47518
47577
|
execution: {
|
|
47519
47578
|
status: execRaw?.status ?? "pending",
|
|
47520
47579
|
mode: execRaw?.mode ?? null,
|
|
47521
|
-
evidence: execRaw?.evidence ?? null
|
|
47580
|
+
evidence: execRaw?.evidence ?? null,
|
|
47581
|
+
coordinator_session_id: execRaw?.coordinator_session_id ?? null
|
|
47522
47582
|
},
|
|
47523
47583
|
handoff_destination: parsed.handoff_destination ?? false,
|
|
47524
47584
|
updated_at: parsed.updated_at ?? Date.now()
|
|
@@ -47655,7 +47715,7 @@ var resetForSpecDrift = (state) => ({
|
|
|
47655
47715
|
spec: { ...state.spec, status: "draft", evidence: null, approved_digest: null },
|
|
47656
47716
|
plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
|
|
47657
47717
|
menu: { presented: false, chosen: "", evidence: null },
|
|
47658
|
-
execution: { status: "pending", mode: null, evidence: null },
|
|
47718
|
+
execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
|
|
47659
47719
|
handoff_destination: false,
|
|
47660
47720
|
updated_at: Date.now()
|
|
47661
47721
|
});
|
|
@@ -47698,9 +47758,14 @@ var reconcileState = (root, slug, state) => {
|
|
|
47698
47758
|
var deriveLegacyExecution = (root, slug, state) => {
|
|
47699
47759
|
const ledger = ledgerCompletion(root, slug);
|
|
47700
47760
|
if (state.plan.status === "approved" && state.menu.chosen === "subagent-driven" && ledger.started && !ledger.complete) {
|
|
47701
|
-
return {
|
|
47761
|
+
return {
|
|
47762
|
+
status: "active",
|
|
47763
|
+
mode: "subagent-driven",
|
|
47764
|
+
evidence: null,
|
|
47765
|
+
coordinator_session_id: null
|
|
47766
|
+
};
|
|
47702
47767
|
}
|
|
47703
|
-
return { status: "pending", mode: null, evidence: null };
|
|
47768
|
+
return { status: "pending", mode: null, evidence: null, coordinator_session_id: null };
|
|
47704
47769
|
};
|
|
47705
47770
|
var normalizeCompatibility = (root, slug, parsed, state) => {
|
|
47706
47771
|
if (!isRecord3(parsed) || !("execution" in parsed)) {
|
|
@@ -47709,6 +47774,11 @@ var normalizeCompatibility = (root, slug, parsed, state) => {
|
|
|
47709
47774
|
if (derived.status !== current.status || derived.mode !== current.mode) {
|
|
47710
47775
|
return { state: { ...state, execution: derived, updated_at: Date.now() }, changed: true };
|
|
47711
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 };
|
|
47712
47782
|
}
|
|
47713
47783
|
return { state, changed: false };
|
|
47714
47784
|
};
|
|
@@ -47871,19 +47941,49 @@ var isNegativeLabel = (label) => {
|
|
|
47871
47941
|
return firstWord.replace(/[^a-z]/g, "") === entry;
|
|
47872
47942
|
});
|
|
47873
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
|
+
};
|
|
47874
47970
|
|
|
47875
47971
|
class HostReceiptStore {
|
|
47876
47972
|
#bySession = new Map;
|
|
47877
|
-
record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question) {
|
|
47878
|
-
const
|
|
47879
|
-
if (!
|
|
47973
|
+
record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question = "", purpose) {
|
|
47974
|
+
const trimmed = selectedLabel.trim();
|
|
47975
|
+
if (!trimmed)
|
|
47880
47976
|
return;
|
|
47881
47977
|
if (recordedAt > Date.now() + MAX_CLOCK_SKEW_MS)
|
|
47882
47978
|
return;
|
|
47979
|
+
const derived = purpose ?? receiptPurposeForLabel(selectedLabel);
|
|
47980
|
+
if (derived === undefined)
|
|
47981
|
+
return;
|
|
47982
|
+
const q = question ?? "";
|
|
47883
47983
|
const queue = this.#bySession.get(sessionId) ?? [];
|
|
47884
47984
|
if (queue.length >= MAX_RECEIPTS_PER_SESSION)
|
|
47885
47985
|
queue.shift();
|
|
47886
|
-
queue.push({ sessionId, callID, selectedLabel
|
|
47986
|
+
queue.push({ sessionId, callID, selectedLabel, recordedAt, question: q, purpose: derived });
|
|
47887
47987
|
this.#bySession.set(sessionId, queue);
|
|
47888
47988
|
}
|
|
47889
47989
|
count(sessionId) {
|
|
@@ -47900,10 +48000,40 @@ class HostReceiptStore {
|
|
|
47900
48000
|
if (!queue || queue.length === 0) {
|
|
47901
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");
|
|
47902
48002
|
}
|
|
47903
|
-
|
|
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
|
+
}
|
|
47904
48026
|
const receipt = queue[index];
|
|
47905
48027
|
if (isNegativeLabel(receipt.selectedLabel)) {
|
|
47906
|
-
|
|
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
|
+
}
|
|
47907
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");
|
|
47908
48038
|
}
|
|
47909
48039
|
if (opts.label !== undefined && !sameChoiceLabel(receipt.selectedLabel, opts.label)) {
|
|
@@ -48095,7 +48225,7 @@ var completeExecution = (root, slug, deps) => {
|
|
|
48095
48225
|
}
|
|
48096
48226
|
const next = {
|
|
48097
48227
|
...reconciled.state,
|
|
48098
|
-
execution: { ...exec3, status: "completed" },
|
|
48228
|
+
execution: { ...exec3, status: "completed", coordinator_session_id: null },
|
|
48099
48229
|
handoff_destination: false,
|
|
48100
48230
|
updated_at: Date.now()
|
|
48101
48231
|
};
|
|
@@ -48155,6 +48285,38 @@ var slugFromPath = (p) => {
|
|
|
48155
48285
|
const dirName = path18.basename(path18.dirname(p));
|
|
48156
48286
|
return dirName === "." || dirName === "/" || dirName === "" ? "" : dirName;
|
|
48157
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";
|
|
48158
48320
|
var BASH_READ_TOKENS = new Set([
|
|
48159
48321
|
"cat",
|
|
48160
48322
|
"head",
|
|
@@ -48282,12 +48444,12 @@ var COORDINATOR_SHELL_DENIED_TEXT = "Coordinator shell commands are restricted w
|
|
|
48282
48444
|
import path20 from "node:path";
|
|
48283
48445
|
|
|
48284
48446
|
// packages/workit-core/src/core/handoff-context.ts
|
|
48285
|
-
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";
|
|
48286
48448
|
import path19 from "node:path";
|
|
48287
48449
|
var DOC_RE = /docs\/([A-Za-z0-9][A-Za-z0-9._-]*)\/(spec|plan)\.md/g;
|
|
48288
48450
|
var listMd = (dir) => {
|
|
48289
48451
|
try {
|
|
48290
|
-
return
|
|
48452
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".md")).sort();
|
|
48291
48453
|
} catch {
|
|
48292
48454
|
return [];
|
|
48293
48455
|
}
|
|
@@ -48313,7 +48475,7 @@ var resolveActivePair = (root) => {
|
|
|
48313
48475
|
let best = null;
|
|
48314
48476
|
let entries = [];
|
|
48315
48477
|
try {
|
|
48316
|
-
entries =
|
|
48478
|
+
entries = readdirSync6(docsDir);
|
|
48317
48479
|
} catch {
|
|
48318
48480
|
return { error: "no pair" };
|
|
48319
48481
|
}
|
|
@@ -48405,13 +48567,21 @@ var buildHandoffPrompt = (root, message) => {
|
|
|
48405
48567
|
};
|
|
48406
48568
|
|
|
48407
48569
|
// packages/workit-cli/src/flow.ts
|
|
48408
|
-
var FLOW_ACTIONS = [
|
|
48570
|
+
var FLOW_ACTIONS = [
|
|
48571
|
+
"status",
|
|
48572
|
+
"pause",
|
|
48573
|
+
"resume",
|
|
48574
|
+
"complete",
|
|
48575
|
+
"review-package",
|
|
48576
|
+
"append-advisory"
|
|
48577
|
+
];
|
|
48409
48578
|
var COMMANDS = {
|
|
48410
48579
|
status: "workit flow status --plan <path>",
|
|
48411
48580
|
pause: "workit flow pause --plan <path> [--confirm]",
|
|
48412
48581
|
resume: "workit flow resume --plan <path> [--confirm]",
|
|
48413
48582
|
complete: "workit flow complete --plan <path> [--confirm]",
|
|
48414
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]",
|
|
48415
48585
|
handoff: "workit handoff --message <text>"
|
|
48416
48586
|
};
|
|
48417
48587
|
var FLOW_COMMANDS = {
|
|
@@ -48419,7 +48589,8 @@ var FLOW_COMMANDS = {
|
|
|
48419
48589
|
pause: COMMANDS.pause,
|
|
48420
48590
|
resume: COMMANDS.resume,
|
|
48421
48591
|
complete: COMMANDS.complete,
|
|
48422
|
-
"review-package": COMMANDS["review-package"]
|
|
48592
|
+
"review-package": COMMANDS["review-package"],
|
|
48593
|
+
"append-advisory": COMMANDS["append-advisory"]
|
|
48423
48594
|
};
|
|
48424
48595
|
var CLI_FLAG_EVIDENCE = { host: "cli", attested: false, confirmation: "flag" };
|
|
48425
48596
|
var CLI_TTY_EVIDENCE = { host: "cli", attested: false, confirmation: "tty" };
|
|
@@ -48452,14 +48623,22 @@ var usage = (err3, text) => {
|
|
|
48452
48623
|
write(err3, text);
|
|
48453
48624
|
return 2;
|
|
48454
48625
|
};
|
|
48455
|
-
var VALUE_FLAGS = ["--plan", "--base", "--head"];
|
|
48626
|
+
var VALUE_FLAGS = ["--plan", "--base", "--head", "--task", "--text"];
|
|
48456
48627
|
function parseFlowFlags(action, argv, err3) {
|
|
48457
48628
|
const parsed = {};
|
|
48458
48629
|
let confirm = false;
|
|
48459
48630
|
for (let i = 0;i < argv.length; i++) {
|
|
48460
48631
|
const token = argv[i];
|
|
48461
48632
|
if (VALUE_FLAGS.includes(token)) {
|
|
48462
|
-
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") {
|
|
48463
48642
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${token}`);
|
|
48464
48643
|
return { ok: false };
|
|
48465
48644
|
}
|
|
@@ -48473,7 +48652,7 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48473
48652
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — unknown flag: ${value}`);
|
|
48474
48653
|
return { ok: false };
|
|
48475
48654
|
}
|
|
48476
|
-
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;
|
|
48477
48656
|
if (existing !== undefined) {
|
|
48478
48657
|
usage(err3, `usage: ${FLOW_COMMANDS[action]} — duplicate ${token} flag`);
|
|
48479
48658
|
return { ok: false };
|
|
@@ -48482,8 +48661,12 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48482
48661
|
parsed.plan = value;
|
|
48483
48662
|
else if (token === "--base")
|
|
48484
48663
|
parsed.base = value;
|
|
48485
|
-
else
|
|
48664
|
+
else if (token === "--head")
|
|
48486
48665
|
parsed.head = value;
|
|
48666
|
+
else if (token === "--task")
|
|
48667
|
+
parsed.task = value;
|
|
48668
|
+
else
|
|
48669
|
+
parsed.text = value;
|
|
48487
48670
|
i += 1;
|
|
48488
48671
|
} else if (token === "--confirm") {
|
|
48489
48672
|
if (action === "status") {
|
|
@@ -48514,7 +48697,27 @@ function parseFlowFlags(action, argv, err3) {
|
|
|
48514
48697
|
return { ok: false };
|
|
48515
48698
|
}
|
|
48516
48699
|
}
|
|
48517
|
-
|
|
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
|
+
};
|
|
48518
48721
|
}
|
|
48519
48722
|
async function resolveConfirmation(deps, confirmFlag, out, err3) {
|
|
48520
48723
|
if (confirmFlag)
|
|
@@ -48590,12 +48793,34 @@ function reviewPackageCommand(root, parsed, out, err3) {
|
|
|
48590
48793
|
}
|
|
48591
48794
|
return domainFail(err3, result2.code ?? "review_package_failed", result2.error);
|
|
48592
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
|
+
}
|
|
48593
48818
|
async function runFlowCommand(argv, deps = {}) {
|
|
48594
48819
|
const out = outStream(deps);
|
|
48595
48820
|
const err3 = errStream(deps);
|
|
48596
48821
|
const [action, ...rest2] = argv;
|
|
48597
48822
|
if (!action || !FLOW_ACTIONS.includes(action)) {
|
|
48598
|
-
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]");
|
|
48599
48824
|
}
|
|
48600
48825
|
const flowAction = action;
|
|
48601
48826
|
const parsed = parseFlowFlags(flowAction, rest2, err3);
|
|
@@ -48611,6 +48836,9 @@ async function runFlowCommand(argv, deps = {}) {
|
|
|
48611
48836
|
if (flowAction === "review-package") {
|
|
48612
48837
|
return reviewPackageCommand(root, parsed.parsed, out, err3);
|
|
48613
48838
|
}
|
|
48839
|
+
if (flowAction === "append-advisory") {
|
|
48840
|
+
return appendAdvisoryCommand(root, parsed.parsed, out, err3);
|
|
48841
|
+
}
|
|
48614
48842
|
return mutateCommand(root, parsed.parsed.plan, flowAction, evidence, deps, out, err3);
|
|
48615
48843
|
}
|
|
48616
48844
|
async function runHandoffCommand(argv, deps = {}) {
|
|
@@ -48649,6 +48877,7 @@ var COMMAND_DESCRIPTIONS = [
|
|
|
48649
48877
|
[COMMANDS.resume, "Resume a paused plan"],
|
|
48650
48878
|
[COMMANDS.complete, "Complete a plan (ledger and verification gated)"],
|
|
48651
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"],
|
|
48652
48881
|
[COMMANDS.handoff, "Emit the destination handoff prompt for a plan"]
|
|
48653
48882
|
];
|
|
48654
48883
|
var helpColumn = Math.max(...COMMAND_DESCRIPTIONS.map(([cmd]) => cmd.length)) + 2;
|
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"
|