@brainervirus/workit-opencode 0.8.8 → 0.8.10

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/dist/plugin.js CHANGED
@@ -14,7 +14,7 @@ var __export = (target, all) => {
14
14
  };
15
15
 
16
16
  // packages/workit-opencode/src/plugin.ts
17
- import { existsSync as existsSync21, readdirSync as readdirSync11 } from "node:fs";
17
+ import { existsSync as existsSync21, readdirSync as readdirSync12 } from "node:fs";
18
18
  import path39 from "node:path";
19
19
  import { fileURLToPath as fileURLToPath3 } from "node:url";
20
20
 
@@ -348,16 +348,16 @@ ${contract}
348
348
  ## Presentation and visual companion overrides
349
349
 
350
350
  - NEVER offer Superpowers visual companion or open a browser tab for layout comparisons.
351
- - For UI wireframes and layout options use \`workflow_present_ascii\` with a JSON spec; show the tool output in a fenced \`text\` block.
352
- - For process or architecture flows use \`workflow_present_flow\`; show the tool output in a fenced \`mermaid\` block.
351
+ - For UI wireframes and layout options use \`workit_present_ascii\` with a JSON spec; show the tool output in a fenced \`text\` block.
352
+ - For process or architecture flows use \`workit_present_flow\`; show the tool output in a fenced \`mermaid\` block.
353
353
  - NEVER hand-draw ASCII wireframes or mermaid in chat without calling the tool first.
354
354
  - For bounded user choices use OpenCode native \`question\`; never A/B/C option lists in chat prose.
355
355
 
356
356
  ## Post-plan execution choice
357
357
 
358
- After saving a plan, call \`workflow_docs_validate\` on the spec/plan pair. On failure, stop — do not offer execution.
358
+ After saving a plan, call \`workit_docs_validate\` on the spec/plan pair. On failure, stop — do not offer execution.
359
359
 
360
- On success, use native \`question\` with exactly: Subagent-driven, Inline, Handoff (new session only), Review spec first, Review plan first. Never emit Superpowers “Two execution options” prose. No \`--stay\` in this menu.
360
+ On success, use native \`question\` with exactly: Subagent-driven, Inline, Handoff (new session only), Review spec first, Review plan first, Change model first. Change model first is display-only: ends turn without workit_plan_menu, re-presents menu next turn; real choices call workit_plan_menu immediately before any skill/branch/mutation/handoff. Never emit Superpowers “Two execution options” prose. No \`--stay\` in this menu.
361
361
 
362
362
  ## Library documentation
363
363
 
@@ -374,13 +374,15 @@ var SOURCE_MENU_LABELS = [
374
374
  "Inline",
375
375
  "Handoff",
376
376
  "Review spec first",
377
- "Review plan first"
377
+ "Review plan first",
378
+ "Change model first"
378
379
  ];
379
380
  var DESTINATION_MENU_LABELS = [
380
381
  "Subagent-driven",
381
382
  "Inline",
382
383
  "Review spec first",
383
- "Review plan first"
384
+ "Review plan first",
385
+ "Change model first"
384
386
  ];
385
387
  var HANDOFF_DESTINATION_MARKER = "<workflow-handoff-destination>true</workflow-handoff-destination>";
386
388
 
@@ -390,14 +392,14 @@ var REMINDER_TEXT = `<workflow-contract-reminder>
390
392
  - Bounded user choices → call the native \`question\` tool (never A/B/C or 1/2/3 lists in prose).
391
393
  - After a plan is approved → native \`question\` menu with exactly: ${SOURCE_MENU_LABELS_DISPLAY.join(", ")}.
392
394
  - Tools with \`confirmed\` → call them; never fabricate their result.
393
- - Before the first \`workflow_spec_approve\`/\`workflow_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
395
+ - Before the first \`workit_spec_approve\`/\`workit_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
394
396
  - Delivering docs → clickable markdown link \`[spec.md](docs/<slug>/spec.md)\` + 3-5 bullet summary.
395
397
  </workflow-contract-reminder>`;
396
398
  var DESTINATION_REMINDER_TEXT = `<workflow-contract-reminder>
397
399
  - Bounded user choices → call the native \`question\` tool (never A/B/C or 1/2/3 lists in prose).
398
400
  - This session is a handoff destination: present the post-plan menu with exactly: ${DESTINATION_MENU_LABELS.join(", ")}.
399
401
  - Tools with \`confirmed\` → call them; never fabricate their result.
400
- - Before the first \`workflow_spec_approve\`/\`workflow_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
402
+ - Before the first \`workit_spec_approve\`/\`workit_plan_approve\` (self-review) run the superpowers writing-plans Self-Review checklist: spec coverage (every spec requirement maps to a task), placeholder scan, type consistency; fix findings inline.
401
403
  - Delivering docs → clickable markdown link \`[spec.md](docs/<slug>/spec.md)\` + 3-5 bullet summary.
402
404
  ${HANDOFF_DESTINATION_MARKER}
403
405
  </workflow-contract-reminder>`;
@@ -412,6 +414,10 @@ You referenced a doc with a backtick-only path. Deliver docs with a clickable ma
412
414
  var SDD_REMINDER_TEXT = `<workflow-sdd-reminder>
413
415
  An approved plan is subagent-driven — execute it via \`wk-implement\` / \`task\` delegation. Never implement the approved plan inline in the main session.
414
416
  </workflow-sdd-reminder>`;
417
+ var SDD_WORKER_REMINDER_TEXT = `<workflow-sdd-worker>
418
+ You are an authorized delegated worker for an active subagent-driven plan.
419
+ Execute only the supplied task brief: follow TDD (failing test first), land exactly one contiguous non-empty commit range for your task, then report status, commits, and test results to the coordinator. Do not manage coordinator bookkeeping or launch another agent harness.
420
+ </workflow-sdd-worker>`;
415
421
  var DOC_RENDER_TEXT = `<workflow-doc-render>
416
422
  When delivering a spec or plan, by default render the full markdown content of the doc in chat (headings, tables, mermaid fences preserved) — NOT a backtick-wrapped raw block.
417
423
  If the doc exceeds the render threshold (more than 150 lines, over 8KB, or more than 3 mermaid diagrams), deliver only the clickable link \`[spec.md](docs/<slug>/spec.md)\` + a 3-5 bullet summary.
@@ -425,7 +431,7 @@ var CONFIG_GUARD_TEXT = `<workflow-config-guard>
425
431
  A tool failed with a config-gap error (\`workflow config missing\`). Never configure without asking — ask with the native \`question\` tool, exactly three options: (1) configure only what's missing (guided, via the /wk-init skill flow for those actions), (2) run the full wizard (\`npx workit init\`), (3) skip — report the final error naming the missing items and how to configure them.
426
432
  </workflow-config-guard>`;
427
433
  var VERIFICATION_TEXT = `<workflow-verification-rail>
428
- Skill: verification-before-completion. NO completion claims without fresh verification evidence — run the check command (e.g. \`bun run check\` / \`workflow_verify\`) and show its output before claiming done/fixed/passing. If you haven't run the verification command in this message, you cannot claim it passes.
434
+ Skill: verification-before-completion. NO completion claims without fresh verification evidence — run the check command (e.g. \`bun run check\` / \`workit_verify\`) and show its output before claiming done/fixed/passing. If you haven't run the verification command in this message, you cannot claim it passes.
429
435
  </workflow-verification-rail>`;
430
436
  var TDD_TEXT = `<workflow-tdd-rail>
431
437
  Skill: test-driven-development. NO production code without a failing test first — write the test, watch it fail, then write the minimal code to pass.
@@ -445,12 +451,11 @@ var shouldInjectBrainstorm = (currentText) => !currentText.includes(BRAINSTORM_T
445
451
  var shouldInjectDebug = (currentText) => !currentText.includes(DEBUG_TEXT);
446
452
  var shouldInjectReviewReception = (currentText) => !currentText.includes(REVIEW_RECEPTION_TEXT);
447
453
  var shouldInjectDocRender = (currentText) => !currentText.includes(DOC_RENDER_TEXT);
448
- var shouldInjectSddReminder = (currentText) => !currentText.includes(SDD_REMINDER_TEXT);
449
454
  var shouldInjectConfigGuard = (currentText) => !currentText.includes(CONFIG_GUARD_TEXT);
450
455
  var shouldInjectIssueRail = (currentText) => !currentText.includes(ISSUE_RAIL_TEXT);
451
456
 
452
457
  // packages/workit-core/src/core/detector.ts
453
- import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "node:fs";
458
+ import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync17 } from "node:fs";
454
459
  import path23 from "node:path";
455
460
 
456
461
  // packages/workit-core/src/core/init.ts
@@ -2432,7 +2437,7 @@ var branchSetup = ({
2432
2437
  if (dirty) {
2433
2438
  if (stash !== "yes") {
2434
2439
  return {
2435
- error: "dirty working tree — ask with native question, then call workflow_branch_setup with stash=yes"
2440
+ error: "dirty working tree — ask with native question, then call workit_branch_setup with stash=yes"
2436
2441
  };
2437
2442
  }
2438
2443
  try {
@@ -3358,7 +3363,7 @@ async function postUpdate({
3358
3363
  postedComment: true,
3359
3364
  loggedMinutes: 0,
3360
3365
  error: time.error,
3361
- retry: "workflow_youtrack_log_time"
3366
+ retry: "workit_youtrack_log_time"
3362
3367
  };
3363
3368
  }
3364
3369
  return { ok: true, issueId, postedComment: true, loggedMinutes: minutes };
@@ -3868,6 +3873,7 @@ import {
3868
3873
  fsyncSync,
3869
3874
  mkdirSync as mkdirSync9,
3870
3875
  openSync,
3876
+ readdirSync as readdirSync6,
3871
3877
  readFileSync as readFileSync16,
3872
3878
  renameSync as renameSync2,
3873
3879
  rmSync as rmSync2,
@@ -4497,7 +4503,7 @@ function sddContext({
4497
4503
  task_count,
4498
4504
  flow: { spec: flow.spec, plan: flow.plan, menu: flow.menu },
4499
4505
  todowrite_required: true,
4500
- todowrite_hint: "REQUIRED: Call OpenCode todowrite with todos from this result so the native task list shows progress. Before each task set status in_progress; after workflow_sdd_append_progress set it completed."
4506
+ todowrite_hint: "REQUIRED: Call OpenCode todowrite with todos from this result so the native task list shows progress. Before each task set status in_progress; after workit_sdd_append_progress set it completed."
4501
4507
  };
4502
4508
  }
4503
4509
  function sddTaskBrief({
@@ -4582,6 +4588,58 @@ function sddAppendProgress({
4582
4588
  const rel = posix2(path19.relative(contained.base, path_));
4583
4589
  return { ok: true, line: trimmed, progress_path: rel };
4584
4590
  }
4591
+ function sddAppendAdvisory({
4592
+ advisories_path,
4593
+ task_id,
4594
+ text,
4595
+ workspace_root
4596
+ }) {
4597
+ if (typeof task_id !== "number" || !Number.isSafeInteger(task_id) || task_id <= 0) {
4598
+ return { error: "task_id must be a positive safe integer", code: "advisory_task_invalid" };
4599
+ }
4600
+ if (typeof text !== "string") {
4601
+ return {
4602
+ error: "advisory text must be a string of 1-1000 characters after normalization",
4603
+ code: "advisory_text_invalid"
4604
+ };
4605
+ }
4606
+ if (text.includes("\r") || text.includes(`
4607
+ `)) {
4608
+ return {
4609
+ error: "advisory text must be a single line (no CR/LF)",
4610
+ code: "advisory_text_invalid"
4611
+ };
4612
+ }
4613
+ const collapsed = text.trim().replace(/[ \t]+/g, " ");
4614
+ if (collapsed.length === 0 || collapsed.length > 1000) {
4615
+ return {
4616
+ error: "advisory text must be 1-1000 characters after trim and horizontal-space collapse",
4617
+ code: "advisory_text_invalid"
4618
+ };
4619
+ }
4620
+ if (!/^docs\/[^/]+\/sdd\/advisories\.md$/.test(advisories_path)) {
4621
+ return {
4622
+ error: `advisories_path must be docs/<slug>/sdd/advisories.md: ${advisories_path}`,
4623
+ code: "advisory_path_invalid"
4624
+ };
4625
+ }
4626
+ const contained = resolveDocsPath({ workspace_root, path: advisories_path });
4627
+ if (!contained.ok)
4628
+ return { error: contained.error, code: "advisory_path_invalid" };
4629
+ const abs = contained.path;
4630
+ if (existsSync12(abs) && statSync5(abs).isDirectory()) {
4631
+ return {
4632
+ error: `advisory target is a directory: ${advisories_path}`,
4633
+ code: "advisory_target_invalid"
4634
+ };
4635
+ }
4636
+ mkdirSync8(path19.dirname(abs), { recursive: true });
4637
+ const line = `- Task ${task_id}: ${collapsed}
4638
+ `;
4639
+ appendFileSync(abs, line, "utf8");
4640
+ const rel = posix2(path19.relative(contained.base, abs));
4641
+ return { ok: true, advisory: collapsed, advisories_path: rel };
4642
+ }
4585
4643
 
4586
4644
  // packages/workit-core/src/core/verify-project.ts
4587
4645
  import { spawnSync as spawnSync5 } from "node:child_process";
@@ -4880,7 +4938,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
4880
4938
  - Entries should be human-readable and user-facing.
4881
4939
  - Do not use raw commit messages as changelog bullets.
4882
4940
  - MERGE into existing ### Category under [Unreleased] — never append a second ### Added / ### Fixed block.
4883
- - Apply with the native workflow_changelog_apply tool only (not hand-edits under Unreleased).
4941
+ - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
4884
4942
  - If Unreleased already has duplicate category headings, normalize_only first.`;
4885
4943
  function changelogContext(root2, range) {
4886
4944
  const cwd = path20.resolve(repoRoot3(root2));
@@ -5193,7 +5251,7 @@ root: ${cwd}
5193
5251
  }
5194
5252
 
5195
5253
  // packages/workit-core/src/core/flow-state.ts
5196
- 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.";
5254
+ 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.";
5197
5255
  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`.";
5198
5256
  var MENU_CHOICES = [
5199
5257
  "subagent-driven",
@@ -5253,7 +5311,8 @@ var normalizeState = (parsed, slug) => {
5253
5311
  execution: {
5254
5312
  status: execution.status ?? "pending",
5255
5313
  mode: execution.mode ?? null,
5256
- evidence: execution.evidence ?? null
5314
+ evidence: execution.evidence ?? null,
5315
+ coordinator_session_id: execution.coordinator_session_id ?? null
5257
5316
  },
5258
5317
  handoff_destination: p.handoff_destination ?? false,
5259
5318
  updated_at: p.updated_at ?? Date.now()
@@ -5265,7 +5324,7 @@ var emptyState = (slug) => ({
5265
5324
  spec: { path: "", status: "draft", evidence: null, approved_digest: null },
5266
5325
  plan: { path: "", status: "draft", evidence: null, approved_digest: null },
5267
5326
  menu: { presented: false, chosen: "", evidence: null },
5268
- execution: { status: "pending", mode: null, evidence: null },
5327
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
5269
5328
  handoff_destination: false,
5270
5329
  updated_at: Date.now()
5271
5330
  });
@@ -5381,6 +5440,12 @@ var validateState = (parsed, slug) => {
5381
5440
  if (execRaw?.evidence !== undefined && !validateEvidenceValue(execRaw.evidence, true)) {
5382
5441
  return { ok: false, error: "flow state execution.evidence has an unsupported shape" };
5383
5442
  }
5443
+ if (execRaw?.coordinator_session_id !== undefined && execRaw.coordinator_session_id !== null && typeof execRaw.coordinator_session_id !== "string") {
5444
+ return {
5445
+ ok: false,
5446
+ error: "flow state execution.coordinator_session_id must be a string or null"
5447
+ };
5448
+ }
5384
5449
  return {
5385
5450
  ok: true,
5386
5451
  state: {
@@ -5396,7 +5461,8 @@ var validateState = (parsed, slug) => {
5396
5461
  execution: {
5397
5462
  status: execRaw?.status ?? "pending",
5398
5463
  mode: execRaw?.mode ?? null,
5399
- evidence: execRaw?.evidence ?? null
5464
+ evidence: execRaw?.evidence ?? null,
5465
+ coordinator_session_id: execRaw?.coordinator_session_id ?? null
5400
5466
  },
5401
5467
  handoff_destination: parsed.handoff_destination ?? false,
5402
5468
  updated_at: parsed.updated_at ?? Date.now()
@@ -5407,7 +5473,7 @@ var readFlowStrict = (root2, slug) => {
5407
5473
  const file = flowPath(root2, slug);
5408
5474
  const rel = path22.posix.join("docs", slug, "sdd", "flow.json");
5409
5475
  if (!existsSync15(file)) {
5410
- return err2("flow_not_activated", `flow not activated for ${slug} — run workflow_flow_status first`);
5476
+ return err2("flow_not_activated", `flow not activated for ${slug} — run workit_flow_status first`);
5411
5477
  }
5412
5478
  let text;
5413
5479
  try {
@@ -5533,7 +5599,7 @@ var resetForSpecDrift = (state) => ({
5533
5599
  spec: { ...state.spec, status: "draft", evidence: null, approved_digest: null },
5534
5600
  plan: { ...state.plan, status: "draft", evidence: null, approved_digest: null },
5535
5601
  menu: { presented: false, chosen: "", evidence: null },
5536
- execution: { status: "pending", mode: null, evidence: null },
5602
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
5537
5603
  handoff_destination: false,
5538
5604
  updated_at: Date.now()
5539
5605
  });
@@ -5576,9 +5642,14 @@ var reconcileState = (root2, slug, state) => {
5576
5642
  var deriveLegacyExecution = (root2, slug, state) => {
5577
5643
  const ledger = ledgerCompletion(root2, slug);
5578
5644
  if (state.plan.status === "approved" && state.menu.chosen === "subagent-driven" && ledger.started && !ledger.complete) {
5579
- return { status: "active", mode: "subagent-driven", evidence: null };
5645
+ return {
5646
+ status: "active",
5647
+ mode: "subagent-driven",
5648
+ evidence: null,
5649
+ coordinator_session_id: null
5650
+ };
5580
5651
  }
5581
- return { status: "pending", mode: null, evidence: null };
5652
+ return { status: "pending", mode: null, evidence: null, coordinator_session_id: null };
5582
5653
  };
5583
5654
  var normalizeCompatibility = (root2, slug, parsed, state) => {
5584
5655
  if (!isRecord(parsed) || !("execution" in parsed)) {
@@ -5587,6 +5658,11 @@ var normalizeCompatibility = (root2, slug, parsed, state) => {
5587
5658
  if (derived.status !== current.status || derived.mode !== current.mode) {
5588
5659
  return { state: { ...state, execution: derived, updated_at: Date.now() }, changed: true };
5589
5660
  }
5661
+ return { state, changed: false };
5662
+ }
5663
+ const execRaw = isRecord(parsed.execution) ? parsed.execution : undefined;
5664
+ if (execRaw && !("coordinator_session_id" in execRaw)) {
5665
+ return { state: { ...state, updated_at: Date.now() }, changed: true };
5590
5666
  }
5591
5667
  return { state, changed: false };
5592
5668
  };
@@ -5721,15 +5797,6 @@ var assertMutationWorkspace = (root2, ctx) => {
5721
5797
  }
5722
5798
  return { ok: true };
5723
5799
  };
5724
- var assertCoordinatorBoundary = (ctx, state) => {
5725
- if (ctx?.role === "coordinator" && state.execution.status === "active" && state.execution.mode === "subagent-driven") {
5726
- return err2("coordinator_blocked", COORDINATOR_RECOVERY_TEXT);
5727
- }
5728
- if (ctx?.role === "delegated" && !ctx.taskIdentity) {
5729
- return err2("delegated_unauthenticated", "delegated mutations require an authenticated task identity (taskIdentity) — re-run inside the delegated worker session");
5730
- }
5731
- return { ok: true };
5732
- };
5733
5800
  var MAX_CLOCK_SKEW_MS = 60000;
5734
5801
  var MAX_RECEIPTS_PER_SESSION = 10;
5735
5802
  var RECEIPT_FRESHNESS_MS = 10 * 60 * 1000;
@@ -5758,19 +5825,49 @@ var isNegativeLabel = (label) => {
5758
5825
  return firstWord.replace(/[^a-z]/g, "") === entry;
5759
5826
  });
5760
5827
  };
5828
+ var receiptPurposeForLabel = (label) => {
5829
+ const n = normalizeLabel(label);
5830
+ if (n === "approve spec" || n === "approve spec recommended")
5831
+ return "spec-approval";
5832
+ if (n === "approve plan" || n === "approve plan recommended")
5833
+ return "plan-approval";
5834
+ if (n === "approve")
5835
+ return;
5836
+ if (n === "pause plan")
5837
+ return "plan-pause";
5838
+ if (n === "resume plan")
5839
+ return "plan-resume";
5840
+ if (n === "complete plan")
5841
+ return "plan-complete";
5842
+ const exec = new Set([
5843
+ "subagent driven",
5844
+ "inline",
5845
+ "handoff",
5846
+ "review spec",
5847
+ "review plan",
5848
+ "change model"
5849
+ ]);
5850
+ if (exec.has(n))
5851
+ return "execution-menu";
5852
+ return;
5853
+ };
5761
5854
 
5762
5855
  class HostReceiptStore {
5763
5856
  #bySession = new Map;
5764
- record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question) {
5765
- const label = selectedLabel.trim();
5766
- if (!label)
5857
+ record(sessionId, callID, selectedLabel, recordedAt = Date.now(), question = "", purpose) {
5858
+ const trimmed = selectedLabel.trim();
5859
+ if (!trimmed)
5767
5860
  return;
5768
5861
  if (recordedAt > Date.now() + MAX_CLOCK_SKEW_MS)
5769
5862
  return;
5863
+ const derived = purpose ?? receiptPurposeForLabel(selectedLabel);
5864
+ if (derived === undefined)
5865
+ return;
5866
+ const q = question ?? "";
5770
5867
  const queue = this.#bySession.get(sessionId) ?? [];
5771
5868
  if (queue.length >= MAX_RECEIPTS_PER_SESSION)
5772
5869
  queue.shift();
5773
- queue.push({ sessionId, callID, selectedLabel: label, recordedAt, question });
5870
+ queue.push({ sessionId, callID, selectedLabel, recordedAt, question: q, purpose: derived });
5774
5871
  this.#bySession.set(sessionId, queue);
5775
5872
  }
5776
5873
  count(sessionId) {
@@ -5787,10 +5884,40 @@ class HostReceiptStore {
5787
5884
  if (!queue || queue.length === 0) {
5788
5885
  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");
5789
5886
  }
5790
- const index = queue.length - 1;
5887
+ let index = -1;
5888
+ if (opts.purpose !== undefined) {
5889
+ const top = queue[queue.length - 1];
5890
+ if (top && top.purpose === opts.purpose && isNegativeLabel(top.selectedLabel)) {
5891
+ const filtered = queue.filter((r) => r.purpose !== opts.purpose);
5892
+ if (filtered.length === 0)
5893
+ this.#bySession.delete(sessionId);
5894
+ else
5895
+ this.#bySession.set(sessionId, filtered);
5896
+ 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");
5897
+ }
5898
+ for (let i = queue.length - 1;i >= 0; i--) {
5899
+ if (queue[i].purpose === opts.purpose) {
5900
+ index = i;
5901
+ break;
5902
+ }
5903
+ }
5904
+ if (index === -1) {
5905
+ return err2("receipt_missing", `no host-observed receipt for purpose ${JSON.stringify(opts.purpose)} — ask the native question for that purpose`);
5906
+ }
5907
+ } else {
5908
+ index = queue.length - 1;
5909
+ }
5791
5910
  const receipt = queue[index];
5792
5911
  if (isNegativeLabel(receipt.selectedLabel)) {
5793
- this.#bySession.delete(sessionId);
5912
+ if (opts.purpose !== undefined) {
5913
+ const filtered = queue.filter((r) => r.purpose !== opts.purpose);
5914
+ if (filtered.length === 0)
5915
+ this.#bySession.delete(sessionId);
5916
+ else
5917
+ this.#bySession.set(sessionId, filtered);
5918
+ } else {
5919
+ this.#bySession.delete(sessionId);
5920
+ }
5794
5921
  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");
5795
5922
  }
5796
5923
  if (opts.label !== undefined && !sameChoiceLabel(receipt.selectedLabel, opts.label)) {
@@ -5924,7 +6051,7 @@ var prepareFlowState = (root2, slug, opts = {}, ctx) => {
5924
6051
  spec: { path: specPath, status: "draft", evidence: null, approved_digest: null },
5925
6052
  plan: { path: planPath, status: "draft", evidence: null, approved_digest: null },
5926
6053
  menu: { presented: false, chosen: "", evidence: null },
5927
- execution: { status: "pending", mode: null, evidence: null },
6054
+ execution: { status: "pending", mode: null, evidence: null, coordinator_session_id: null },
5928
6055
  handoff_destination: false,
5929
6056
  updated_at: Date.now()
5930
6057
  });
@@ -6070,13 +6197,24 @@ var recordMenuChoice = (root2, slug, planPath, choice, evidence, ctx) => {
6070
6197
  return err2("recursive_handoff", "this flow is already a handoff destination — a second handoff is rejected");
6071
6198
  }
6072
6199
  const executing = choice === "subagent-driven" || choice === "inline";
6200
+ const coordinatorSessionId = choice === "subagent-driven" && recorded.evidence.host === "opencode" ? ctx?.sessionId ?? null : null;
6073
6201
  return {
6074
6202
  ok: true,
6075
6203
  next: {
6076
6204
  ...state,
6077
6205
  plan: { ...state.plan, path: state.plan.path || `docs/${slug}/plan.md` },
6078
6206
  menu: { presented: true, chosen: choice, evidence: recorded.evidence },
6079
- execution: executing ? { status: "active", mode: choice, evidence: recorded.evidence } : { status: "pending", mode: null, evidence: recorded.evidence },
6207
+ execution: executing ? {
6208
+ status: "active",
6209
+ mode: choice,
6210
+ evidence: recorded.evidence,
6211
+ coordinator_session_id: coordinatorSessionId
6212
+ } : {
6213
+ status: "pending",
6214
+ mode: null,
6215
+ evidence: recorded.evidence,
6216
+ coordinator_session_id: null
6217
+ },
6080
6218
  updated_at: Date.now()
6081
6219
  }
6082
6220
  };
@@ -6170,7 +6308,7 @@ var completeExecution = (root2, slug, deps) => {
6170
6308
  }
6171
6309
  const next = {
6172
6310
  ...reconciled.state,
6173
- execution: { ...exec, status: "completed" },
6311
+ execution: { ...exec, status: "completed", coordinator_session_id: null },
6174
6312
  handoff_destination: false,
6175
6313
  updated_at: Date.now()
6176
6314
  };
@@ -6234,7 +6372,7 @@ var slugFromSddPath = (p) => {
6234
6372
  const match = p.split(path22.sep).join("/").match(/^docs\/([^/]+)\/sdd(\/|$|['"])/);
6235
6373
  return match?.[1] ?? "";
6236
6374
  };
6237
- var assertFlowGates = (root2, planPath, opts = {}) => {
6375
+ var assertHandoffReady = (root2, planPath) => {
6238
6376
  const doc = resolveDoc(root2, "", planPath, "plan");
6239
6377
  if (!doc.ok)
6240
6378
  return err2("path_invalid", doc.error);
@@ -6244,17 +6382,20 @@ var assertFlowGates = (root2, planPath, opts = {}) => {
6244
6382
  return effective;
6245
6383
  const state = effective.state;
6246
6384
  if (state.spec.status !== "approved") {
6247
- return err2("spec_not_approved", `spec not approved (status: ${state.spec.status}). Run workflow_spec_approve after the user's approval.`);
6385
+ return err2("spec_not_approved", `spec not approved (status: ${state.spec.status}). Run workit_spec_approve after the user's approval.`);
6248
6386
  }
6249
6387
  if (state.plan.status !== "approved") {
6250
- return err2("plan_not_approved", `plan not approved (status: ${state.plan.status}). Run workflow_plan_approve after the user's approval.`);
6388
+ return err2("plan_not_approved", `plan not approved (status: ${state.plan.status}). Run workit_plan_approve after the user's approval.`);
6251
6389
  }
6252
- if (opts.requireMenu && !state.menu.presented) {
6253
- return err2("menu_not_presented", "post-plan menu not presented. Ask the native question menu (Subagent-driven/Inline/Handoff/Review spec/Review plan) and record the answer with workflow_plan_menu.");
6390
+ if (state.handoff_destination) {
6391
+ return err2("recursive_handoff", "this flow is already a handoff destination a second handoff is rejected");
6392
+ }
6393
+ if (!state.menu.presented || state.menu.chosen !== "handoff") {
6394
+ return err2("handoff_not_chosen", `handoff requires the execution menu choice "handoff" (chosen: ${JSON.stringify(state.menu.chosen)}, presented: ${state.menu.presented})`);
6254
6395
  }
6255
6396
  return { ok: true };
6256
6397
  };
6257
- var assertProductGates = (root2, slug, opts = {}, ctx) => {
6398
+ var assertSddControlGates = (root2, slug, opts = {}, ctx) => {
6258
6399
  const bound = assertMutationWorkspace(root2, ctx);
6259
6400
  if (!bound.ok)
6260
6401
  return bound;
@@ -6263,13 +6404,13 @@ var assertProductGates = (root2, slug, opts = {}, ctx) => {
6263
6404
  return effective;
6264
6405
  const state = effective.state;
6265
6406
  if (state.spec.status !== "approved") {
6266
- return err2("spec_not_approved", `spec not approved (status: ${state.spec.status}). Run workflow_spec_approve after the user's approval.`);
6407
+ return err2("spec_not_approved", `spec not approved (status: ${state.spec.status}). Run workit_spec_approve after the user's approval.`);
6267
6408
  }
6268
6409
  if (state.plan.status !== "approved") {
6269
- return err2("plan_not_approved", `plan not approved (status: ${state.plan.status}). Run workflow_plan_approve after the user's approval.`);
6410
+ return err2("plan_not_approved", `plan not approved (status: ${state.plan.status}). Run workit_plan_approve after the user's approval.`);
6270
6411
  }
6271
6412
  if (opts.requireMenu && !state.menu.presented) {
6272
- return err2("menu_not_presented", "post-plan menu not presented. Record the native question answer with workflow_plan_menu.");
6413
+ return err2("menu_not_presented", "post-plan menu not presented. Record the native question answer with workit_plan_menu.");
6273
6414
  }
6274
6415
  if (opts.requireDocs) {
6275
6416
  const validated = docsValidate({
@@ -6280,9 +6421,12 @@ var assertProductGates = (root2, slug, opts = {}, ctx) => {
6280
6421
  if (validated.ok === false)
6281
6422
  return err2("docs_invalid", validated.error);
6282
6423
  }
6283
- return assertCoordinatorBoundary(ctx, state);
6424
+ if (state.execution.status === "active" && state.execution.mode === "subagent-driven" && roleFromParentage(ctx?.parentSessionId, state.execution.coordinator_session_id) === "delegated") {
6425
+ 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");
6426
+ }
6427
+ return { ok: true };
6284
6428
  };
6285
- var roleFromParentage = (parentID) => parentID ? "delegated" : "coordinator";
6429
+ var roleFromParentage = (parentID, coordinatorSessionId) => typeof parentID === "string" && parentID !== "" && parentID === coordinatorSessionId ? "delegated" : "coordinator";
6286
6430
  var COORDINATOR_WRITE_TOOLS = [
6287
6431
  "write",
6288
6432
  "edit",
@@ -6297,21 +6441,18 @@ var COORDINATOR_WRITE_TOOLS = [
6297
6441
  "touch",
6298
6442
  "chmod",
6299
6443
  "chown",
6300
- "workflow_commit",
6301
- "workflow_pr_create",
6302
- "workflow_rule_edit",
6303
- "workflow_template_edit",
6304
- "workflow_changelog_apply",
6305
- "workflow_branch_setup",
6444
+ "workit_commit",
6445
+ "workit_pr_create",
6446
+ "workit_rule_edit",
6447
+ "workit_template_edit",
6448
+ "workit_changelog_apply",
6449
+ "workit_branch_setup",
6306
6450
  "workit_init_apply",
6307
- "workflow_docs_promote",
6308
- "workflow_docs_layout",
6309
- "workflow_docs_repo_link",
6310
- "workflow_sdd_task_brief",
6311
- "workflow_sdd_review_package",
6312
- "workflow_sdd_append_progress",
6313
- "workflow_youtrack_post",
6314
- "workflow_youtrack_log_time"
6451
+ "workit_docs_promote",
6452
+ "workit_docs_layout",
6453
+ "workit_docs_repo_link",
6454
+ "workit_youtrack_post",
6455
+ "workit_youtrack_log_time"
6315
6456
  ];
6316
6457
  var BASH_READ_TOKENS = new Set([
6317
6458
  "cat",
@@ -6614,10 +6755,34 @@ var isCoordinatorBashAllowed = (command) => {
6614
6755
  };
6615
6756
  var COORDINATOR_SHELL_DENIED_TEXT = "Coordinator shell commands are restricted while a subagent-driven plan is " + "active: only bounded read/test/review commands are allowed (the exact " + "allowlist is in flow-state.ts, isCoordinatorBashAllowed). " + COORDINATOR_RECOVERY_TEXT;
6616
6757
  var subagentDrivenInterception = (input) => {
6617
- if (input.parentID)
6758
+ const ids = Array.from(new Set((input.activeCoordinatorIds ?? []).filter((id) => typeof id === "string" && id !== "")));
6759
+ const parent = typeof input.parentID === "string" && input.parentID !== "" ? input.parentID : null;
6760
+ const legacyActive = input.active === true;
6761
+ if (!legacyActive && ids.length === 0)
6618
6762
  return { ok: true };
6619
- if (!input.active)
6763
+ if (parent !== null && ids.length === 1 && ids[0] === parent) {
6764
+ if (input.tool === "bash") {
6765
+ const tokens = (input.command ?? "").split(/[\s'"]+/).filter(Boolean);
6766
+ const launchesOpencode = tokens.some((t) => t.split("/").pop() === "opencode");
6767
+ if (launchesOpencode) {
6768
+ return err2("delegation_lineage_denied", "nested opencode launch is denied while a subagent-driven plan is active");
6769
+ }
6770
+ }
6771
+ if ([
6772
+ "workit_sdd_task_brief",
6773
+ "workit_sdd_review_package",
6774
+ "workit_sdd_append_progress",
6775
+ "workit_sdd_append_advisory"
6776
+ ].includes(input.tool)) {
6777
+ return err2("delegation_lineage_denied", "SDD control metadata is coordinator-owned — workers execute briefs, not bookkeeping");
6778
+ }
6620
6779
  return { ok: true };
6780
+ }
6781
+ if (parent !== null) {
6782
+ if (!legacyActive || ids.length > 0) {
6783
+ return err2("delegation_lineage_denied", "delegated writes require an exact direct-parent match to the activating coordinator");
6784
+ }
6785
+ }
6621
6786
  if (COORDINATOR_WRITE_TOOLS.includes(input.tool)) {
6622
6787
  return err2("coordinator_write_denied", COORDINATOR_RECOVERY_TEXT);
6623
6788
  }
@@ -6628,11 +6793,29 @@ var subagentDrivenInterception = (input) => {
6628
6793
  }
6629
6794
  return { ok: true };
6630
6795
  };
6796
+ var findActiveSubagentDrivenContexts = (root2) => {
6797
+ let entries = [];
6798
+ try {
6799
+ entries = readdirSync6(path22.join(root2, "docs"), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
6800
+ } catch {
6801
+ return [];
6802
+ }
6803
+ const out = [];
6804
+ for (const slug of entries) {
6805
+ try {
6806
+ const state = readFlowState(root2, slug);
6807
+ if (state.execution.status === "active" && state.execution.mode === "subagent-driven") {
6808
+ out.push({ slug, coordinator_session_id: state.execution.coordinator_session_id });
6809
+ }
6810
+ } catch {}
6811
+ }
6812
+ return out;
6813
+ };
6631
6814
 
6632
6815
  // packages/workit-core/src/core/detector.ts
6633
6816
  var detectConfigGapError = (text) => text.includes(CONFIG_GAP_MARKER);
6634
6817
  var COMPLETION_CLAIMS = /\b(?:done|fixed|passing|green|complete|all set)\b/i;
6635
- var VERIFICATION_EVIDENCE = /\bbun run check\b|\bworkflow_verify\b|\bbun test\b|\bchecks?\s+pass(?:es|ing)?\b|\btests?\s+pass(?:es|ing)?\b/i;
6818
+ var VERIFICATION_EVIDENCE = /\bbun run check\b|\bworkit_verify\b|\bbun test\b|\bchecks?\s+pass(?:es|ing)?\b|\btests?\s+pass(?:es|ing)?\b/i;
6636
6819
  var detectVerificationClaim = (text) => COMPLETION_CLAIMS.test(text) && !VERIFICATION_EVIDENCE.test(text);
6637
6820
  var IMPLEMENTATION_SIGNALS = /\b(?:changed|implemented|implementing|added|refactored|commit(?:ted|s)?|edited)\b/i;
6638
6821
  var FAILING_TEST_WORDS = /\b(?:failing test|test failed|watch it fail|red-green|tdd|test first)\b/i;
@@ -6748,7 +6931,7 @@ var scanActiveSubagentDrivenPlans = (root2) => {
6748
6931
  if (exec.status === "active" && exec.mode === "subagent-driven")
6749
6932
  slugs.push(slug);
6750
6933
  };
6751
- for (const slug of readdirSync6(docsDir)) {
6934
+ for (const slug of readdirSync7(docsDir)) {
6752
6935
  const file = path23.join(docsDir, slug, "sdd", "flow.json");
6753
6936
  if (!existsSync16(file))
6754
6937
  continue;
@@ -19666,37 +19849,37 @@ function createRepoTools(runtime = defaultRuntime) {
19666
19849
  args: {},
19667
19850
  execute: async (_input, context2) => output(scriptResult(await runtime.toolkitStatus(context2.directory), json2))
19668
19851
  }),
19669
- workflow_git_context: tool({
19852
+ workit_git_context: tool({
19670
19853
  description: "Read Git branch and change context",
19671
19854
  args: { paths: tool.schema.array(tool.schema.string()).optional() },
19672
19855
  execute: async ({ paths }, context2) => output(ok(gitContext(context2.directory, paths ?? [])))
19673
19856
  }),
19674
- workflow_verify: tool({
19857
+ workit_verify: tool({
19675
19858
  description: "Discover and run repository verification",
19676
19859
  args: { dry_run: tool.schema.boolean().optional() },
19677
19860
  execute: async ({ dry_run }, context2) => output(scriptResult(runtime.verifyProject(context2.directory, Boolean(dry_run)), parseVerifyOutput))
19678
19861
  }),
19679
- workflow_pr_context: tool({
19862
+ workit_pr_context: tool({
19680
19863
  description: "Gather branch-exclusive PR context",
19681
19864
  args: { range: tool.schema.string().optional() },
19682
19865
  execute: async ({ range }, context2) => contextWithRange(context2.directory, runtime.prContext, range, parsePr)
19683
19866
  }),
19684
- workflow_changelog_context: tool({
19867
+ workit_changelog_context: tool({
19685
19868
  description: "Gather changelog context",
19686
19869
  args: { range: tool.schema.string().optional() },
19687
19870
  execute: async ({ range }, context2) => contextWithRange(context2.directory, runtime.changelogContext, range, parseChangelog)
19688
19871
  }),
19689
- workflow_release_notes_context: tool({
19872
+ workit_release_notes_context: tool({
19690
19873
  description: "Gather release notes for an explicit range",
19691
19874
  args: { range_or_tag: tool.schema.string() },
19692
19875
  execute: async ({ range_or_tag }, context2) => !range_or_tag.trim() ? output(fail("release tag or range required")) : contextWithRange(context2.directory, (root2, value) => runtime.releaseContext(root2, value ?? ""), range_or_tag, parseRelease)
19693
19876
  }),
19694
- workflow_docs_context: tool({
19877
+ workit_docs_context: tool({
19695
19878
  description: "Gather documentation refresh context",
19696
19879
  args: { range: tool.schema.string().optional() },
19697
19880
  execute: async ({ range }, context2) => output(scriptResult(runtime.docsContext(context2.directory, range), parseDocs))
19698
19881
  }),
19699
- workflow_changelog_apply: tool({
19882
+ workit_changelog_apply: tool({
19700
19883
  description: "Apply confirmed Keep a Changelog entries to Unreleased",
19701
19884
  args: {
19702
19885
  confirmed: tool.schema.boolean(),
@@ -19724,7 +19907,7 @@ function createRepoTools(runtime = defaultRuntime) {
19724
19907
  })));
19725
19908
  }
19726
19909
  }),
19727
- workflow_branch_setup: tool({
19910
+ workit_branch_setup: tool({
19728
19911
  description: "Apply a confirmed in-place feature or bugfix branch setup",
19729
19912
  args: {
19730
19913
  confirmed: tool.schema.boolean(),
@@ -19758,7 +19941,7 @@ function createRepoTools(runtime = defaultRuntime) {
19758
19941
  }));
19759
19942
  }
19760
19943
  }),
19761
- workflow_commit: tool({
19944
+ workit_commit: tool({
19762
19945
  description: "Commit the current index on a feature or bugfix branch without staging files",
19763
19946
  args: { confirmed: tool.schema.boolean(), message: tool.schema.string() },
19764
19947
  execute: async ({ confirmed, message }, context2) => {
@@ -19779,7 +19962,7 @@ function createRepoTools(runtime = defaultRuntime) {
19779
19962
  })));
19780
19963
  }
19781
19964
  }),
19782
- workflow_pr_create: tool({
19965
+ workit_pr_create: tool({
19783
19966
  description: "Create a confirmed pull or merge request",
19784
19967
  args: {
19785
19968
  confirmed: tool.schema.boolean(),
@@ -19963,15 +20146,21 @@ var opencodeMutationContext = async (context2, client) => {
19963
20146
  const session = await client?.session.get({ path: { id: context2.sessionID } });
19964
20147
  parentID = session?.data?.parentID;
19965
20148
  } catch {}
19966
- const role = roleFromParentage(parentID);
20149
+ const role = roleFromParentage(parentID, undefined);
19967
20150
  return {
19968
20151
  hostWorkspace: context2.directory,
19969
20152
  role,
19970
20153
  sessionId: context2.sessionID,
20154
+ parentSessionId: parentID,
19971
20155
  taskIdentity: role === "delegated" ? context2.sessionID : undefined
19972
20156
  };
19973
20157
  };
19974
20158
  function createFlowTools(receipts, client) {
20159
+ const purposeForLifecycle = {
20160
+ "Pause plan": "plan-pause",
20161
+ "Resume plan": "plan-resume",
20162
+ "Complete plan": "plan-complete"
20163
+ };
19975
20164
  const lifecycleTool = (action, label, description) => tool({
19976
20165
  description,
19977
20166
  args: {
@@ -19982,7 +20171,10 @@ function createFlowTools(receipts, client) {
19982
20171
  if ("error" in slugged)
19983
20172
  return output2(fail(slugged.error));
19984
20173
  const slug = slugged.slug;
19985
- const consumed = receipts.consume(context2.sessionID, { label });
20174
+ const consumed = receipts.consume(context2.sessionID, {
20175
+ purpose: purposeForLifecycle[label],
20176
+ label
20177
+ });
19986
20178
  if (!consumed.ok)
19987
20179
  return output2(fail(consumed.error, { code: consumed.code }));
19988
20180
  const result = transitionExecution(context2.directory, slug, plan_path, action, createOpenCodeEvidence(consumed.receipt), await opencodeMutationContext(context2, client));
@@ -20004,7 +20196,7 @@ function createFlowTools(receipts, client) {
20004
20196
  }
20005
20197
  });
20006
20198
  return {
20007
- workflow_flow_status: tool({
20199
+ workit_flow_status: tool({
20008
20200
  description: "Read the spec/plan approval flow state for a workflow; on first read it records flow activation and canonical document paths (FG-01)",
20009
20201
  args: {
20010
20202
  plan_path: tool.schema.string().optional(),
@@ -20042,7 +20234,7 @@ function createFlowTools(receipts, client) {
20042
20234
  }
20043
20235
  }
20044
20236
  }),
20045
- workflow_spec_approve: tool({
20237
+ workit_spec_approve: tool({
20046
20238
  description: "Advance spec status from a host-observed native-question receipt: draft -> approved in a single call. The self-review validation runs automatically inside the transition; only the final approval asks for your confirmation. The receipt is recorded automatically when the user answers the `question` tool; there is no evidence argument (AR-12).",
20047
20239
  args: {
20048
20240
  spec_path: tool.schema.string()
@@ -20052,7 +20244,7 @@ function createFlowTools(receipts, client) {
20052
20244
  if ("error" in slugged)
20053
20245
  return output2(fail(slugged.error));
20054
20246
  const slug = slugged.slug;
20055
- const consumed = receipts.consume(context2.sessionID);
20247
+ const consumed = receipts.consume(context2.sessionID, { purpose: "spec-approval" });
20056
20248
  if (!consumed.ok)
20057
20249
  return output2(fail(consumed.error, { code: consumed.code }));
20058
20250
  const result = transitionSpec(context2.directory, slug, spec_path, createOpenCodeEvidence(consumed.receipt), await opencodeMutationContext(context2, client));
@@ -20061,7 +20253,7 @@ function createFlowTools(receipts, client) {
20061
20253
  return output2(result.ok ? ok({ spec: spec_path, status, question: consumed.receipt.question }) : fail(result.error, { code: result.code }));
20062
20254
  }
20063
20255
  }),
20064
- workflow_plan_approve: tool({
20256
+ workit_plan_approve: tool({
20065
20257
  description: "Advance plan status from a host-observed native-question receipt: draft -> approved in a single call. The self-review validation runs automatically inside the transition; only the final approval asks for your confirmation. Requires approved spec. There is no evidence argument (AR-12).",
20066
20258
  args: {
20067
20259
  plan_path: tool.schema.string()
@@ -20071,7 +20263,7 @@ function createFlowTools(receipts, client) {
20071
20263
  if ("error" in slugged)
20072
20264
  return output2(fail(slugged.error));
20073
20265
  const slug = slugged.slug;
20074
- const consumed = receipts.consume(context2.sessionID);
20266
+ const consumed = receipts.consume(context2.sessionID, { purpose: "plan-approval" });
20075
20267
  if (!consumed.ok)
20076
20268
  return output2(fail(consumed.error, { code: consumed.code }));
20077
20269
  const result = transitionPlan(context2.directory, slug, plan_path, createOpenCodeEvidence(consumed.receipt), await opencodeMutationContext(context2, client));
@@ -20080,7 +20272,7 @@ function createFlowTools(receipts, client) {
20080
20272
  return output2(result.ok ? ok({ plan: plan_path, status, question: consumed.receipt.question }) : fail(result.error, { code: result.code }));
20081
20273
  }
20082
20274
  }),
20083
- workflow_plan_menu: tool({
20275
+ workit_plan_menu: tool({
20084
20276
  description: "Record the answered post-plan choice menu (called after the native question). The receipt label must match the choice exactly; there is no evidence argument (AR-12).",
20085
20277
  args: {
20086
20278
  plan_path: tool.schema.string(),
@@ -20097,7 +20289,10 @@ function createFlowTools(receipts, client) {
20097
20289
  if ("error" in slugged)
20098
20290
  return output2(fail(slugged.error));
20099
20291
  const slug = slugged.slug;
20100
- const consumed = receipts.consume(context2.sessionID, { label: choice });
20292
+ const consumed = receipts.consume(context2.sessionID, {
20293
+ purpose: "execution-menu",
20294
+ label: choice
20295
+ });
20101
20296
  if (!consumed.ok)
20102
20297
  return output2(fail(consumed.error, { code: consumed.code }));
20103
20298
  const result = recordMenuChoice(context2.directory, slug, plan_path, choice, createOpenCodeEvidence(consumed.receipt), await opencodeMutationContext(context2, client));
@@ -20107,20 +20302,20 @@ function createFlowTools(receipts, client) {
20107
20302
  }) : fail(result.error, { code: result.code }));
20108
20303
  }
20109
20304
  }),
20110
- workflow_plan_pause: lifecycleTool("pause", "Pause plan", "Pause a running plan from a host-observed native-question receipt: active -> paused. The receipt label must be exactly `Pause plan`; there is no evidence argument (AR-12). A failed gate (already-paused) spends the receipt — re-answer the native question to retry."),
20111
- workflow_plan_resume: lifecycleTool("resume", "Resume plan", "Resume a paused plan from a host-observed native-question receipt: paused -> active. The receipt label must be exactly `Resume plan`; there is no evidence argument (AR-12). A failed gate (flow_not_paused) spends the receipt — re-answer the native question to retry."),
20112
- workflow_plan_complete: lifecycleTool("complete", "Complete plan", "Complete a running plan from a host-observed native-question receipt: active/paused -> completed, after the SDD ledger is complete and repository verification passes. The receipt label must be exactly `Complete plan`; there is no evidence argument (AR-12). A failed gate (execution_incomplete or verification_failed) spends the receipt — re-answer the native question to retry.")
20305
+ workit_plan_pause: lifecycleTool("pause", "Pause plan", "Pause a running plan from a host-observed native-question receipt: active -> paused. The receipt label must be exactly `Pause plan`; there is no evidence argument (AR-12). A failed gate (already-paused) spends the receipt — re-answer the native question to retry."),
20306
+ workit_plan_resume: lifecycleTool("resume", "Resume plan", "Resume a paused plan from a host-observed native-question receipt: paused -> active. The receipt label must be exactly `Resume plan`; there is no evidence argument (AR-12). A failed gate (flow_not_paused) spends the receipt — re-answer the native question to retry."),
20307
+ workit_plan_complete: lifecycleTool("complete", "Complete plan", "Complete a running plan from a host-observed native-question receipt: active/paused -> completed, after the SDD ledger is complete and repository verification passes. The receipt label must be exactly `Complete plan`; there is no evidence argument (AR-12). A failed gate (execution_incomplete or verification_failed) spends the receipt — re-answer the native question to retry.")
20113
20308
  };
20114
20309
  }
20115
20310
 
20116
20311
  // packages/workit-opencode/src/tools/sdd.ts
20117
20312
  var output3 = (value) => JSON.stringify(value, null, 2);
20118
20313
  var requireConfirmed2 = (confirmed) => confirmed === true ? null : output3(fail("confirmed: true required"));
20119
- var gateProductWrite = async (root2, sddPath, context2, client) => {
20314
+ var gateSddControl = async (root2, sddPath, context2, client) => {
20120
20315
  const slug = slugFromSddPath(sddPath);
20121
20316
  if (!slug)
20122
20317
  return output3(fail("could not derive slug — expected docs/<slug>/sdd/..."));
20123
- const gate = assertProductGates(root2, slug, { requireMenu: true, requireDocs: true }, await opencodeMutationContext(context2, client));
20318
+ const gate = assertSddControlGates(root2, slug, { requireMenu: true, requireDocs: true }, await opencodeMutationContext(context2, client));
20124
20319
  if (!gate.ok)
20125
20320
  return output3(fail(gate.error, { code: gate.code }));
20126
20321
  return null;
@@ -20165,7 +20360,7 @@ function createSddTools(state, client) {
20165
20360
  sdd: String(data.sdd_dir ?? "")
20166
20361
  });
20167
20362
  return {
20168
- workflow_docs_branch: tool({
20363
+ workit_docs_branch: tool({
20169
20364
  description: "Resolve branch for spec/plan authors: keep current feature|bugfix or create from the configured base",
20170
20365
  args: {
20171
20366
  plan_path: tool.schema.string().optional(),
@@ -20181,7 +20376,7 @@ function createSddTools(state, client) {
20181
20376
  });
20182
20377
  })
20183
20378
  }),
20184
- workflow_docs_validate: tool({
20379
+ workit_docs_validate: tool({
20185
20380
  description: "Hard-fail validate spec/plan headers, link, branch, task order; returns quality findings (hard/warning)",
20186
20381
  args: {
20187
20382
  spec_path: tool.schema.string(),
@@ -20202,7 +20397,7 @@ function createSddTools(state, client) {
20202
20397
  return result;
20203
20398
  })
20204
20399
  }),
20205
- workflow_plan_tasks: tool({
20400
+ workit_plan_tasks: tool({
20206
20401
  description: "Parse top-level tasks from a workflow plan",
20207
20402
  args: { plan_path: tool.schema.string(), spec_path: tool.schema.string().optional() },
20208
20403
  execute: async ({ plan_path, spec_path }, context2) => invoke(() => {
@@ -20219,7 +20414,7 @@ function createSddTools(state, client) {
20219
20414
  return data;
20220
20415
  })
20221
20416
  }),
20222
- workflow_resolve_branch: tool({
20417
+ workit_resolve_branch: tool({
20223
20418
  description: "Resolve a branch from repository spec and plan metadata",
20224
20419
  args: { spec_path: tool.schema.string(), plan_path: tool.schema.string() },
20225
20420
  execute: async ({ spec_path, plan_path }, context2) => invoke(() => {
@@ -20228,7 +20423,7 @@ function createSddTools(state, client) {
20228
20423
  return resolveBranch({ spec_path, plan_path, workspace_root: context2.directory });
20229
20424
  })
20230
20425
  }),
20231
- workflow_sdd_context: tool({
20426
+ workit_sdd_context: tool({
20232
20427
  description: "Resolve the SDD workspace and progress ledger",
20233
20428
  args: {
20234
20429
  plan_path: tool.schema.string().optional(),
@@ -20255,7 +20450,7 @@ function createSddTools(state, client) {
20255
20450
  return data;
20256
20451
  })
20257
20452
  }),
20258
- workflow_sdd_task_brief: tool({
20453
+ workit_sdd_task_brief: tool({
20259
20454
  description: "Write a confirmed task brief. Delegated status is host-derived from session parentage (child session = worker; root = coordinator, blocked for subagent-driven product edits).",
20260
20455
  args: {
20261
20456
  confirmed: tool.schema.boolean(),
@@ -20267,7 +20462,7 @@ function createSddTools(state, client) {
20267
20462
  const rejected = requireConfirmed2(confirmed);
20268
20463
  if (rejected)
20269
20464
  return rejected;
20270
- const gated = await gateProductWrite(context2.directory, sdd_dir, context2, client);
20465
+ const gated = await gateSddControl(context2.directory, sdd_dir, context2, client);
20271
20466
  if (gated)
20272
20467
  return gated;
20273
20468
  return invoke(() => {
@@ -20281,7 +20476,7 @@ function createSddTools(state, client) {
20281
20476
  });
20282
20477
  }
20283
20478
  }),
20284
- workflow_sdd_review_package: tool({
20479
+ workit_sdd_review_package: tool({
20285
20480
  description: "Write a confirmed task review diff. Delegated status is host-derived from session parentage (child session = worker; root = coordinator, blocked for subagent-driven product edits).",
20286
20481
  args: {
20287
20482
  confirmed: tool.schema.boolean(),
@@ -20300,7 +20495,7 @@ function createSddTools(state, client) {
20300
20495
  } catch (error45) {
20301
20496
  return output3(fail(error45 instanceof Error ? error45.message : "workflow operation failed"));
20302
20497
  }
20303
- const gated = await gateProductWrite(context2.directory, sdd_dir, context2, client);
20498
+ const gated = await gateSddControl(context2.directory, sdd_dir, context2, client);
20304
20499
  if (gated)
20305
20500
  return gated;
20306
20501
  return invoke(() => sddReviewPackage({
@@ -20311,7 +20506,7 @@ function createSddTools(state, client) {
20311
20506
  }));
20312
20507
  }
20313
20508
  }),
20314
- workflow_sdd_append_progress: tool({
20509
+ workit_sdd_append_progress: tool({
20315
20510
  description: "Append one confirmed validated SDD progress line. Delegated status is host-derived from session parentage (child session = worker; root = coordinator, blocked for subagent-driven product edits).",
20316
20511
  args: {
20317
20512
  confirmed: tool.schema.boolean(),
@@ -20322,7 +20517,7 @@ function createSddTools(state, client) {
20322
20517
  const rejected = requireConfirmed2(confirmed);
20323
20518
  if (rejected)
20324
20519
  return rejected;
20325
- const gated = await gateProductWrite(context2.directory, progress_path, context2, client);
20520
+ const gated = await gateSddControl(context2.directory, progress_path, context2, client);
20326
20521
  if (gated)
20327
20522
  return gated;
20328
20523
  return invoke(() => {
@@ -20330,6 +20525,32 @@ function createSddTools(state, client) {
20330
20525
  return sddAppendProgress({ progress_path, line, workspace_root: context2.directory });
20331
20526
  });
20332
20527
  }
20528
+ }),
20529
+ workit_sdd_append_advisory: tool({
20530
+ description: "Append a validated advisory line to docs/<slug>/sdd/advisories.md (coordinator-owned).",
20531
+ args: {
20532
+ confirmed: tool.schema.boolean(),
20533
+ advisories_path: tool.schema.string(),
20534
+ task_id: tool.schema.number(),
20535
+ text: tool.schema.string()
20536
+ },
20537
+ execute: async ({ confirmed, advisories_path, task_id, text }, context2) => {
20538
+ const rejected = requireConfirmed2(confirmed);
20539
+ if (rejected)
20540
+ return rejected;
20541
+ const gated = await gateSddControl(context2.directory, advisories_path, context2, client);
20542
+ if (gated)
20543
+ return gated;
20544
+ return invoke(() => {
20545
+ relativePath(context2.directory, advisories_path);
20546
+ return sddAppendAdvisory({
20547
+ advisories_path,
20548
+ task_id,
20549
+ text,
20550
+ workspace_root: context2.directory
20551
+ });
20552
+ });
20553
+ }
20333
20554
  })
20334
20555
  };
20335
20556
  }
@@ -20341,12 +20562,12 @@ import path32 from "node:path";
20341
20562
  import path31 from "node:path";
20342
20563
 
20343
20564
  // packages/workit-core/src/core/handoff-context.ts
20344
- import { existsSync as existsSync18, readFileSync as readFileSync20, readdirSync as readdirSync7, statSync as statSync8 } from "node:fs";
20565
+ import { existsSync as existsSync18, readFileSync as readFileSync20, readdirSync as readdirSync8, statSync as statSync8 } from "node:fs";
20345
20566
  import path30 from "node:path";
20346
20567
  var DOC_RE = /docs\/([A-Za-z0-9][A-Za-z0-9._-]*)\/(spec|plan)\.md/g;
20347
20568
  var listMd = (dir) => {
20348
20569
  try {
20349
- return readdirSync7(dir).filter((f) => f.endsWith(".md")).sort();
20570
+ return readdirSync8(dir).filter((f) => f.endsWith(".md")).sort();
20350
20571
  } catch {
20351
20572
  return [];
20352
20573
  }
@@ -20372,7 +20593,7 @@ var resolveActivePair = (root2) => {
20372
20593
  let best = null;
20373
20594
  let entries = [];
20374
20595
  try {
20375
- entries = readdirSync7(docsDir);
20596
+ entries = readdirSync8(docsDir);
20376
20597
  } catch {
20377
20598
  return { error: "no pair" };
20378
20599
  }
@@ -20539,7 +20760,7 @@ var idempotentMarkDestination = (root2, slug, planPath) => {
20539
20760
  };
20540
20761
  function createHandoffTools(client, state) {
20541
20762
  return {
20542
- workflow_handoff_session: tool({
20763
+ workit_handoff_session: tool({
20543
20764
  description: "Create, seed, and select a continuation session; --stay in the message skips selection",
20544
20765
  args: { message: tool.schema.string() },
20545
20766
  execute: async ({ message: userMessage }, context2) => {
@@ -20549,21 +20770,13 @@ function createHandoffTools(client, state) {
20549
20770
  const active = built;
20550
20771
  const slug = slugFromPath(active.plan);
20551
20772
  try {
20552
- const gate = assertFlowGates(context2.directory, active.plan);
20553
- if (!gate.ok)
20554
- return output4(fail(gate.error));
20555
- const preflight = readEffectiveFlowState(context2.directory, slug);
20556
- if (!preflight.ok)
20557
- return output4(fail(preflight.error, { code: preflight.code }));
20558
- if (preflight.state.handoff_destination) {
20559
- return output4(fail("this flow is already a handoff destination — a second handoff is rejected", {
20560
- code: "recursive_handoff"
20561
- }));
20562
- }
20773
+ const ready = assertHandoffReady(context2.directory, active.plan);
20774
+ if (!ready.ok)
20775
+ return output4(fail(ready.error, { code: ready.code }));
20563
20776
  state.set(context2.sessionID, { spec: active.spec, plan: active.plan, sdd: active.sdd });
20564
20777
  return output4(await handoffSession(client, {
20565
20778
  directory: context2.directory,
20566
- title: `Continue ${path32.basename(path32.dirname(active.plan))}`,
20779
+ title: `Workit: ${path32.basename(path32.dirname(active.plan))}`,
20567
20780
  prompt: active.prompt,
20568
20781
  stay: /(?:^|\s)--stay(?:\s|$)/.test(userMessage),
20569
20782
  afterSeed: () => idempotentMarkDestination(context2.directory, slug, active.plan)
@@ -20647,7 +20860,7 @@ async function postUpdate2(input, operations = defaultOperations) {
20647
20860
  postedComment: false,
20648
20861
  loggedMinutes: 0,
20649
20862
  outcome: "not_applied",
20650
- retry: "workflow_youtrack_post"
20863
+ retry: "workit_youtrack_post"
20651
20864
  });
20652
20865
  unwrap(comment);
20653
20866
  } catch (error45) {
@@ -20673,7 +20886,7 @@ async function postUpdate2(input, operations = defaultOperations) {
20673
20886
  postedComment: true,
20674
20887
  loggedMinutes: 0,
20675
20888
  outcome: "not_applied",
20676
- retry: "workflow_youtrack_log_time"
20889
+ retry: "workit_youtrack_log_time"
20677
20890
  });
20678
20891
  unwrap(time3);
20679
20892
  } catch (error45) {
@@ -20700,7 +20913,7 @@ async function logTimeUpdate(input, operation = defaultOperations) {
20700
20913
  issueId: input.issueId,
20701
20914
  loggedMinutes: 0,
20702
20915
  outcome: "not_applied",
20703
- retry: "workflow_youtrack_log_time"
20916
+ retry: "workit_youtrack_log_time"
20704
20917
  });
20705
20918
  return ok(unwrap(value));
20706
20919
  } catch (error45) {
@@ -20777,13 +20990,13 @@ var rejectedTimeInput = (issueId, minutes) => {
20777
20990
  issueId,
20778
20991
  loggedMinutes: 0,
20779
20992
  outcome: "not_applied",
20780
- retry: "workflow_youtrack_log_time",
20781
- instructions: "Correct the invalid input, then retry workflow_youtrack_log_time once."
20993
+ retry: "workit_youtrack_log_time",
20994
+ instructions: "Correct the invalid input, then retry workit_youtrack_log_time once."
20782
20995
  })) : null;
20783
20996
  };
20784
20997
  function createYouTrackTools(operations = defaultOperations) {
20785
20998
  return {
20786
- workflow_youtrack_verify_token: tool({
20999
+ workit_youtrack_verify_token: tool({
20787
21000
  description: "Verify the configured YouTrack token with a read-only request",
20788
21001
  args: {},
20789
21002
  execute: async () => {
@@ -20799,12 +21012,12 @@ function createYouTrackTools(operations = defaultOperations) {
20799
21012
  return invoke2(() => operations.verifyToken(), token);
20800
21013
  }
20801
21014
  }),
20802
- workflow_youtrack_parse_issue: tool({
21015
+ workit_youtrack_parse_issue: tool({
20803
21016
  description: "Parse an existing YouTrack issue URL or id",
20804
21017
  args: { issue_ref: tool.schema.string() },
20805
21018
  execute: async ({ issue_ref }) => invoke2(() => parseIssueRef(issue_ref))
20806
21019
  }),
20807
- workflow_youtrack_context: tool({
21020
+ workit_youtrack_context: tool({
20808
21021
  description: "Load YouTrack context for the configured meeting issue or an existing task issue",
20809
21022
  args: {
20810
21023
  mode: tool.schema.enum(["meetings", "task"]).optional(),
@@ -20837,12 +21050,12 @@ function createYouTrackTools(operations = defaultOperations) {
20837
21050
  return invoke2(async () => normalizeContext(await operations.context({ ...input, workspace_root: context2.directory }), input.mode), token);
20838
21051
  }
20839
21052
  }),
20840
- workflow_youtrack_parse_duration: tool({
21053
+ workit_youtrack_parse_duration: tool({
20841
21054
  description: "Parse duration text into integer minutes",
20842
21055
  args: { text: tool.schema.string() },
20843
21056
  execute: async ({ text }, context2) => invoke2(() => operations.parseDuration(text, context2.directory))
20844
21057
  }),
20845
- workflow_youtrack_draft: tool({
21058
+ workit_youtrack_draft: tool({
20846
21059
  description: "Build an es-CL update comment without posting it",
20847
21060
  args: {
20848
21061
  issueId: tool.schema.string(),
@@ -20858,7 +21071,7 @@ function createYouTrackTools(operations = defaultOperations) {
20858
21071
  },
20859
21072
  execute: async (input) => invoke2(() => buildDraft(input))
20860
21073
  }),
20861
- workflow_youtrack_log_time: tool({
21074
+ workit_youtrack_log_time: tool({
20862
21075
  description: "Log confirmed time on an existing YouTrack issue without posting a comment",
20863
21076
  args: {
20864
21077
  confirmed: tool.schema.boolean(),
@@ -20887,7 +21100,7 @@ function createYouTrackTools(operations = defaultOperations) {
20887
21100
  return output5(result.ok ? result : { ...result, error: redact(result.error, token) });
20888
21101
  }
20889
21102
  }),
20890
- workflow_youtrack_post: tool({
21103
+ workit_youtrack_post: tool({
20891
21104
  description: "Post a confirmed es-CL comment, then optionally log time",
20892
21105
  args: {
20893
21106
  confirmed: tool.schema.boolean(),
@@ -21017,7 +21230,7 @@ function flowDiagram(spec) {
21017
21230
  var output6 = (value) => JSON.stringify(value, null, 2);
21018
21231
  function createPresentTools() {
21019
21232
  return {
21020
- workflow_present_ascii: tool({
21233
+ workit_present_ascii: tool({
21021
21234
  description: "Render deterministic ASCII UI wireframe from JSON spec",
21022
21235
  args: {
21023
21236
  title: tool.schema.string().optional(),
@@ -21031,7 +21244,7 @@ function createPresentTools() {
21031
21244
  return output6(ok(result.data));
21032
21245
  }
21033
21246
  }),
21034
- workflow_present_flow: tool({
21247
+ workit_present_flow: tool({
21035
21248
  description: "Render mermaid flowchart from JSON nodes/edges",
21036
21249
  args: {
21037
21250
  title: tool.schema.string().optional(),
@@ -21058,7 +21271,7 @@ function createPresentTools() {
21058
21271
  }
21059
21272
 
21060
21273
  // packages/workit-core/src/core/docs-repo.ts
21061
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync12, readdirSync as readdirSync8 } from "node:fs";
21274
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync22, writeFileSync as writeFileSync12, readdirSync as readdirSync9 } from "node:fs";
21062
21275
  import { execFileSync as execFileSync5 } from "node:child_process";
21063
21276
  import path35 from "node:path";
21064
21277
  var configPath2 = () => process.env.WORKFLOW_DOCS_REPO_CONFIG ?? path35.join(configDir(), "docs-repo.json");
@@ -21104,7 +21317,7 @@ var listSpecs = (workspaceRoot) => {
21104
21317
  const specs = [];
21105
21318
  const docsDir = path35.join(workspaceRoot, "docs");
21106
21319
  if (existsSync19(docsDir)) {
21107
- for (const slug of readdirSync8(docsDir)) {
21320
+ for (const slug of readdirSync9(docsDir)) {
21108
21321
  if (slug.startsWith("."))
21109
21322
  continue;
21110
21323
  const spec = path35.posix.join("docs", slug, "spec.md");
@@ -21115,7 +21328,7 @@ var listSpecs = (workspaceRoot) => {
21115
21328
  if (repoPath) {
21116
21329
  const featuresDir = path35.join(repoPath, "features");
21117
21330
  if (existsSync19(featuresDir)) {
21118
- const match = readdirSync8(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d));
21331
+ const match = readdirSync9(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d));
21119
21332
  if (match) {
21120
21333
  promoted = true;
21121
21334
  target = path35.join(repoPath, "features", match);
@@ -21159,7 +21372,7 @@ var promoteSpec = (workspaceRoot, slug, opts) => {
21159
21372
  const workspaceRootCanonical = resolved.layout.workspace;
21160
21373
  const repoPath = docsRepoPath();
21161
21374
  if (!repoPath)
21162
- return { ok: false, error: "docs repo not linked — run workflow_docs_repo_link" };
21375
+ return { ok: false, error: "docs repo not linked — run workit_docs_repo_link" };
21163
21376
  const repoValid = validateDocsRepo(repoPath);
21164
21377
  if (!repoValid.ok)
21165
21378
  return { ok: false, error: repoValid.error };
@@ -21207,7 +21420,7 @@ var promoteSpec = (workspaceRoot, slug, opts) => {
21207
21420
  }
21208
21421
  const prefix = monthPrefix();
21209
21422
  const featuresDir = path35.join(repoPath, "features");
21210
- const existing = existsSync19(featuresDir) ? readdirSync8(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d)) : undefined;
21423
+ const existing = existsSync19(featuresDir) ? readdirSync9(featuresDir).find((d) => new RegExp(`^20\\d{2}-\\d{2}-${slug}$`).test(d)) : undefined;
21211
21424
  const targetDir = path35.join(featuresDir, existing ?? `${prefix}-${slug}`);
21212
21425
  mkdirSync10(targetDir, { recursive: true });
21213
21426
  const files = ["spec.md"];
@@ -21269,7 +21482,7 @@ import {
21269
21482
  existsSync as existsSync20,
21270
21483
  lstatSync as lstatSync2,
21271
21484
  mkdirSync as mkdirSync11,
21272
- readdirSync as readdirSync9,
21485
+ readdirSync as readdirSync10,
21273
21486
  readFileSync as readFileSync23,
21274
21487
  realpathSync as realpathSync5,
21275
21488
  renameSync as renameSync3,
@@ -21324,7 +21537,7 @@ var detectLegacyDocs = (workspace_root) => {
21324
21537
  const root2 = legacyRoot(workspace_root);
21325
21538
  const raw = [];
21326
21539
  if (existsSync20(root2)) {
21327
- for (const entry of readdirSync9(root2, { withFileTypes: true })) {
21540
+ for (const entry of readdirSync10(root2, { withFileTypes: true })) {
21328
21541
  const abs = path36.join(root2, entry.name);
21329
21542
  const rel = posix3(path36.relative(workspace_root, abs));
21330
21543
  if (entry.isDirectory()) {
@@ -21466,7 +21679,7 @@ var planTree = (workspace, legacyAbs, legacyRel, destRelRoot, destAbsRoot, legac
21466
21679
  if (visits.has(real))
21467
21680
  return { ok: true };
21468
21681
  visits.add(real);
21469
- for (const entry of readdirSync9(real, { withFileTypes: true })) {
21682
+ for (const entry of readdirSync10(real, { withFileTypes: true })) {
21470
21683
  const fromAbs = path36.join(real, entry.name);
21471
21684
  const fromRel = posix3(path36.join(legacyRel, entry.name));
21472
21685
  const toRel = posix3(path36.join(destRelRoot, entry.name));
@@ -21656,7 +21869,7 @@ var migrateLegacyDocs = (input) => {
21656
21869
  var output7 = (value) => JSON.stringify(value, null, 2);
21657
21870
  function createDocsRepoTools() {
21658
21871
  return {
21659
- workflow_docs_layout: tool({
21872
+ workit_docs_layout: tool({
21660
21873
  description: "Canonical docs layout: prepare creates missing docs/ and docs/<slug>/; migrate detects legacy docs/superpowers/ and copies safe pairs after a native Migrate safely / Not now question",
21661
21874
  args: {
21662
21875
  action: tool.schema.enum(["prepare", "migrate"]).optional(),
@@ -21703,7 +21916,7 @@ function createDocsRepoTools() {
21703
21916
  return output7(result.ok ? ok({ layout: result.layout, created: result.created, legacy: result.legacy }) : fail(result.error));
21704
21917
  }
21705
21918
  }),
21706
- workflow_docs_repo_link: tool({
21919
+ workit_docs_repo_link: tool({
21707
21920
  description: "Link the component docs repo in the toolkit config (validates git repo + features/)",
21708
21921
  args: {
21709
21922
  path: tool.schema.string(),
@@ -21714,7 +21927,7 @@ function createDocsRepoTools() {
21714
21927
  return output7(result.ok ? ok({ path: result.path }) : fail(result.error));
21715
21928
  }
21716
21929
  }),
21717
- workflow_docs_list: tool({
21930
+ workit_docs_list: tool({
21718
21931
  description: "List local specs (docs/<slug>/spec.md) with docs-repo promotion status",
21719
21932
  args: {},
21720
21933
  execute: async (_input, context2) => {
@@ -21722,7 +21935,7 @@ function createDocsRepoTools() {
21722
21935
  return output7(ok(result));
21723
21936
  }
21724
21937
  }),
21725
- workflow_docs_promote: tool({
21938
+ workit_docs_promote: tool({
21726
21939
  description: "Promote a spec (+plan) to the linked docs repo features/YYYY-MM-<slug>/ with quality gate",
21727
21940
  args: {
21728
21941
  slug: tool.schema.string(),
@@ -21747,12 +21960,12 @@ function createDocsRepoTools() {
21747
21960
  var output8 = (value) => JSON.stringify(value, null, 2);
21748
21961
  function createTemplateTools() {
21749
21962
  return {
21750
- workflow_template_list: tool({
21963
+ workit_template_list: tool({
21751
21964
  description: "List editable templates (issue-update, greeting, headers) with their source",
21752
21965
  args: {},
21753
21966
  execute: async () => output8(ok({ templates: listTemplates() }))
21754
21967
  }),
21755
- workflow_template_edit: tool({
21968
+ workit_template_edit: tool({
21756
21969
  description: "Write an edited template to the toolkit config dir (agent-assisted)",
21757
21970
  args: {
21758
21971
  name: tool.schema.enum(["issue-update", "greeting", "headers"]),
@@ -21771,12 +21984,12 @@ function createTemplateTools() {
21771
21984
  var output9 = (value) => JSON.stringify(value, null, 2);
21772
21985
  function createRuleTools() {
21773
21986
  return {
21774
- workflow_rule_list: tool({
21987
+ workit_rule_list: tool({
21775
21988
  description: "List canonical rules (config) with platforms and source",
21776
21989
  args: {},
21777
21990
  execute: async () => output9(ok({ rules: listRules() }))
21778
21991
  }),
21779
- workflow_rule_edit: tool({
21992
+ workit_rule_edit: tool({
21780
21993
  description: "Write a canonical rule to the toolkit config dir (agent-assisted)",
21781
21994
  args: {
21782
21995
  name: tool.schema.string(),
@@ -21796,7 +22009,7 @@ function createRuleTools() {
21796
22009
 
21797
22010
  // packages/workit-opencode/src/tools/doctor.ts
21798
22011
  var createDoctorTool = () => ({
21799
- workflow_doctor: tool({
22012
+ workit_doctor: tool({
21800
22013
  description: "Run the offline workit doctor and report installation health",
21801
22014
  args: {},
21802
22015
  execute: async (_input, context2) => JSON.stringify(ok(runDoctor({ host: "opencode", cwd: context2.directory })), null, 2)
@@ -21837,7 +22050,7 @@ SDD: ${value.sdd}` : null;
21837
22050
  }
21838
22051
 
21839
22052
  // packages/workit-core/src/core/logger.ts
21840
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync10, unlinkSync as unlinkSync3 } from "node:fs";
22053
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync12, readdirSync as readdirSync11, unlinkSync as unlinkSync3 } from "node:fs";
21841
22054
  import os4 from "node:os";
21842
22055
  import path37 from "node:path";
21843
22056
  var REDACTED = "[REDACTED]";
@@ -21972,7 +22185,7 @@ var dailyFileName = (date5) => {
21972
22185
  };
21973
22186
  var pruneOldFiles = (dir) => {
21974
22187
  try {
21975
- const files = readdirSync10(dir).filter((name) => DAY_FILE.test(name)).sort().reverse();
22188
+ const files = readdirSync11(dir).filter((name) => DAY_FILE.test(name)).sort().reverse();
21976
22189
  for (const file2 of files.slice(RETAINED_DAYS)) {
21977
22190
  try {
21978
22191
  unlinkSync3(path37.join(dir, file2));
@@ -22139,7 +22352,7 @@ var hasMarkedDestination = (root3) => {
22139
22352
  return false;
22140
22353
  let slugs;
22141
22354
  try {
22142
- slugs = readdirSync11(docsDir);
22355
+ slugs = readdirSync12(docsDir);
22143
22356
  } catch {
22144
22357
  return false;
22145
22358
  }
@@ -22171,6 +22384,32 @@ var questionAnswerLabel = (result) => {
22171
22384
  const label = first[0];
22172
22385
  return typeof label === "string" ? label : undefined;
22173
22386
  };
22387
+ var classifyQuestion = (input, label) => {
22388
+ const args = input;
22389
+ const qs = args?.questions;
22390
+ if (Array.isArray(qs)) {
22391
+ if (qs.length !== 1)
22392
+ return null;
22393
+ const q = qs[0];
22394
+ if (!q || typeof q !== "object")
22395
+ return null;
22396
+ if (q.multiple === true)
22397
+ return null;
22398
+ const opts = q.options;
22399
+ if (Array.isArray(opts) && opts.length === 0 && q.custom === true)
22400
+ return null;
22401
+ if (!Array.isArray(opts) || opts.length === 0)
22402
+ return null;
22403
+ const purpose2 = receiptPurposeForLabel(label);
22404
+ if (purpose2 === undefined)
22405
+ return null;
22406
+ return { purpose: purpose2, questionText: typeof q.question === "string" ? q.question : undefined };
22407
+ }
22408
+ const purpose = receiptPurposeForLabel(label);
22409
+ if (purpose !== undefined)
22410
+ return { purpose, questionText: undefined };
22411
+ return null;
22412
+ };
22174
22413
  var plugin = async ({ client, directory }) => {
22175
22414
  openCodeClient = client;
22176
22415
  installUncaughtHandlers();
@@ -22180,6 +22419,17 @@ var plugin = async ({ client, directory }) => {
22180
22419
  setDiagnosticLogger(logger);
22181
22420
  const state = new WorkflowStateStore;
22182
22421
  const receipts = new HostReceiptStore;
22422
+ const authorizedWorkerFor = async (sessionID) => {
22423
+ const ids = findActiveSubagentDrivenContexts(directory).map((c) => c.coordinator_session_id).filter((id) => typeof id === "string" && id !== "");
22424
+ if (ids.length !== 1)
22425
+ return false;
22426
+ try {
22427
+ const session = await client.session.get({ path: { id: sessionID } });
22428
+ return session?.data?.parentID === ids[0];
22429
+ } catch {
22430
+ return false;
22431
+ }
22432
+ };
22183
22433
  return {
22184
22434
  tool: createTools(adaptPluginHandoffClient(client), state, client, receipts),
22185
22435
  "tool.execute.after": async (input, output10) => {
@@ -22188,9 +22438,11 @@ var plugin = async ({ client, directory }) => {
22188
22438
  const label = questionAnswerLabel(output10);
22189
22439
  if (label === undefined)
22190
22440
  return;
22191
- const args = input.args;
22192
- const questionText = args?.question ?? args?.title;
22193
- receipts.record(input.sessionID, input.callID, label, Date.now(), questionText);
22441
+ const classified = classifyQuestion(input.args, label);
22442
+ if (!classified)
22443
+ return;
22444
+ const questionText = classified.questionText ?? input.args?.question ?? input.args?.title;
22445
+ receipts.record(input.sessionID, input.callID, label, Date.now(), questionText, classified.purpose);
22194
22446
  },
22195
22447
  "tool.execute.before": async (input, output10) => {
22196
22448
  if (input.tool !== "bash" && !COORDINATOR_WRITE_TOOLS.includes(input.tool))
@@ -22202,12 +22454,12 @@ var plugin = async ({ client, directory }) => {
22202
22454
  parentID = session?.data?.parentID;
22203
22455
  sessionDirectory = session?.data?.directory;
22204
22456
  } catch {}
22205
- const active = findActiveSubagentDrivenPlans(sessionDirectory ?? directory).length > 0;
22457
+ const activeCoordinatorIds = findActiveSubagentDrivenContexts(sessionDirectory ?? directory).map((c) => c.coordinator_session_id).filter((id) => typeof id === "string" && id !== "");
22206
22458
  const decision = subagentDrivenInterception({
22207
22459
  tool: input.tool,
22208
22460
  command: output10.args?.command,
22209
22461
  parentID,
22210
- active
22462
+ activeCoordinatorIds
22211
22463
  });
22212
22464
  if (!decision.ok)
22213
22465
  throw new Error(decision.error);
@@ -22260,7 +22512,8 @@ var plugin = async ({ client, directory }) => {
22260
22512
  const firstAnchor = firstUser.parts.find((part) => part.type === "text") ?? firstUser.parts[0];
22261
22513
  const firstText = firstUser.parts.filter((part) => part.type === "text").map((part) => part.text ?? "").join(`
22262
22514
  `);
22263
- const bootstrap = getWorkitBootstrap();
22515
+ const workerAuthorized = await authorizedWorkerFor(firstAnchor.sessionID);
22516
+ const bootstrap = workerAuthorized ? null : getWorkitBootstrap();
22264
22517
  if (bootstrap && !firstText.includes("<workit-contract>")) {
22265
22518
  firstUser.parts.unshift({
22266
22519
  id: firstAnchor.id,
@@ -22269,6 +22522,14 @@ var plugin = async ({ client, directory }) => {
22269
22522
  type: "text",
22270
22523
  text: bootstrap
22271
22524
  });
22525
+ } else if (workerAuthorized && !firstText.includes("workflow-sdd-worker")) {
22526
+ firstUser.parts.unshift({
22527
+ id: firstAnchor.id,
22528
+ sessionID: firstAnchor.sessionID,
22529
+ messageID: firstAnchor.messageID,
22530
+ type: "text",
22531
+ text: SDD_WORKER_REMINDER_TEXT
22532
+ });
22272
22533
  }
22273
22534
  }
22274
22535
  const anchor = currentUser.parts.find((part) => part.type === "text") ?? currentUser.parts[0];
@@ -22303,8 +22564,12 @@ var plugin = async ({ client, directory }) => {
22303
22564
  }
22304
22565
  }
22305
22566
  const activePlans = findActiveSubagentDrivenPlans(directory);
22306
- if (activePlans.length > 0 && shouldInjectSddReminder(currentText)) {
22307
- currentUser.parts.unshift(makePart(SDD_REMINDER_TEXT, "sdd"));
22567
+ if (activePlans.length > 0) {
22568
+ const workerAuthorized = await authorizedWorkerFor(anchor.sessionID);
22569
+ const sddText = workerAuthorized ? SDD_WORKER_REMINDER_TEXT : SDD_REMINDER_TEXT;
22570
+ if (!currentText.includes(sddText)) {
22571
+ currentUser.parts.unshift(makePart(sddText, "sdd"));
22572
+ }
22308
22573
  }
22309
22574
  if (detectConfigGapError(assistantText) && shouldInjectConfigGuard(currentText)) {
22310
22575
  currentUser.parts.unshift(makePart(CONFIG_GUARD_TEXT, "cg"));