@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/README.md +4 -4
- package/assets/skills/wk-changelog/SKILL.md +2 -2
- package/assets/skills/wk-commit/SKILL.md +3 -3
- package/assets/skills/wk-docs-refresh/SKILL.md +2 -2
- package/assets/skills/wk-handoff/SKILL.md +5 -3
- package/assets/skills/wk-implement/SKILL.md +13 -11
- package/assets/skills/wk-issue-update/SKILL.md +5 -5
- package/assets/skills/wk-issue-update/references/youtrack-update-style.md +1 -1
- package/assets/skills/wk-meetings/SKILL.md +5 -5
- package/assets/skills/wk-pr/SKILL.md +4 -4
- package/assets/skills/wk-release-notes/SKILL.md +1 -1
- package/assets/skills/wk-verify/SKILL.md +3 -3
- package/assets/templates/execution-contract.md +20 -18
- package/assets/templates/plan-template.md +1 -1
- package/assets/templates/spec-template.md +2 -2
- package/assets/templates/superpowers-doc-contract.md +13 -10
- package/assets/vendor/superpowers/skills/subagent-driven-development/SKILL.md +4 -4
- package/dist/plugin.js +429 -164
- package/package.json +2 -2
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
|
|
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 \`
|
|
352
|
-
- For process or architecture flows use \`
|
|
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 \`
|
|
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 \`
|
|
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 \`
|
|
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\` / \`
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
5766
|
-
if (!
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 ? {
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
6253
|
-
return err2("
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
"
|
|
6301
|
-
"
|
|
6302
|
-
"
|
|
6303
|
-
"
|
|
6304
|
-
"
|
|
6305
|
-
"
|
|
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
|
-
"
|
|
6308
|
-
"
|
|
6309
|
-
"
|
|
6310
|
-
"
|
|
6311
|
-
"
|
|
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
|
-
|
|
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 (
|
|
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|\
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
20111
|
-
|
|
20112
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
20553
|
-
if (!
|
|
20554
|
-
return output4(fail(
|
|
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: `
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
20781
|
-
instructions: "Correct the invalid input, then retry
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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) ?
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
22192
|
-
|
|
22193
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
22307
|
-
|
|
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"));
|