@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 to `<SDD_DIR>/advisories.md`.
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 four choices — Subagent-driven, Inline, Review spec first, Review plan first — and never re-offers the originating handoff option.
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 (task briefs, progress, review packages) to an " + "authenticated delegated worker via `task` / `wk-implement` instead of " + "editing in the coordinator session.";
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 { status: "active", mode: "subagent-driven", evidence: null };
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 label = selectedLabel.trim();
47879
- if (!label)
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: label, recordedAt, question });
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
- const index = queue.length - 1;
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
- this.#bySession.delete(sessionId);
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 readdirSync5, statSync as statSync8 } from "node:fs";
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 readdirSync5(dir).filter((f) => f.endsWith(".md")).sort();
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 = readdirSync5(docsDir);
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 = ["status", "pause", "resume", "complete", "review-package"];
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
- return { ok: true, parsed: { plan: parsed.plan, confirm, base: parsed.base, head: parsed.head } };
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.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.8",
38
- "@brainervirus/workit-cursor": "^0.8.8",
39
- "@brainervirus/workit-opencode": "^0.8.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"