harnessed 3.4.4 → 3.6.0

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.
Files changed (72) hide show
  1. package/THIRD-PARTY-NOTICES.md +34 -0
  2. package/dist/cli.mjs +259 -27
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +3 -2
  7. package/workflows/capabilities.yaml +17 -18
  8. package/workflows/disciplines/karpathy.yaml +1 -1
  9. package/workflows/disciplines/language.yaml +1 -1
  10. package/workflows/disciplines/operational.yaml +20 -2
  11. package/workflows/disciplines/output-style.yaml +1 -1
  12. package/workflows/disciplines/priority.yaml +1 -1
  13. package/workflows/disciplines/protocols.yaml +1 -1
  14. package/workflows/discuss/auto/SKILL.md +1 -2
  15. package/workflows/discuss/auto/workflow.yaml +1 -2
  16. package/workflows/discuss/phase/SKILL.md +0 -1
  17. package/workflows/discuss/phase/workflow.yaml +1 -1
  18. package/workflows/discuss/strategic/SKILL.md +1 -2
  19. package/workflows/discuss/strategic/workflow.yaml +2 -3
  20. package/workflows/discuss/subtask/SKILL.md +0 -1
  21. package/workflows/discuss/subtask/workflow.yaml +1 -1
  22. package/workflows/judgments/fallback.yaml +1 -1
  23. package/workflows/judgments/parallelism-gate.yaml +4 -3
  24. package/workflows/judgments/phase-gate.yaml +2 -2
  25. package/workflows/judgments/strategic-gate.yaml +2 -2
  26. package/workflows/judgments/subtask-gate.yaml +2 -2
  27. package/workflows/judgments/tdd-gate.yaml +2 -2
  28. package/workflows/judgments/user-overrides.yaml +82 -0
  29. package/workflows/judgments/web-design-routing.yaml +1 -1
  30. package/workflows/judgments/web-search-routing.yaml +1 -1
  31. package/workflows/judgments/web-testing-routing.yaml +1 -1
  32. package/workflows/plan/architecture/SKILL.md +2 -3
  33. package/workflows/plan/architecture/workflow.yaml +2 -2
  34. package/workflows/plan/auto/SKILL.md +1 -2
  35. package/workflows/plan/auto/workflow.yaml +1 -2
  36. package/workflows/plan/phase/SKILL.md +3 -4
  37. package/workflows/plan/phase/workflow.yaml +3 -3
  38. package/workflows/plan-feature/SKILL.md +4 -4
  39. package/workflows/research/SKILL.md +5 -5
  40. package/workflows/research/workflow.yaml +4 -4
  41. package/workflows/retro/SKILL.md +2 -3
  42. package/workflows/retro/workflow.yaml +1 -2
  43. package/workflows/role-prompts.yaml +51 -5
  44. package/workflows/task/auto/SKILL.md +2 -3
  45. package/workflows/task/auto/workflow.yaml +2 -3
  46. package/workflows/task/clarify/SKILL.md +0 -1
  47. package/workflows/task/code/SKILL.md +3 -4
  48. package/workflows/task/code/workflow.yaml +0 -1
  49. package/workflows/task/deliver/SKILL.md +3 -6
  50. package/workflows/task/deliver/workflow.yaml +7 -6
  51. package/workflows/task/test/SKILL.md +0 -1
  52. package/workflows/task/test/workflow.yaml +1 -2
  53. package/workflows/verify/auto/SKILL.md +5 -6
  54. package/workflows/verify/auto/workflow.yaml +4 -5
  55. package/workflows/verify/code-review/SKILL.md +3 -5
  56. package/workflows/verify/code-review/workflow.yaml +1 -3
  57. package/workflows/verify/design/SKILL.md +3 -5
  58. package/workflows/verify/design/workflow.yaml +4 -5
  59. package/workflows/verify/multispec/SKILL.md +6 -8
  60. package/workflows/verify/multispec/workflow.yaml +5 -8
  61. package/workflows/verify/paranoid/SKILL.md +2 -3
  62. package/workflows/verify/paranoid/workflow.yaml +1 -2
  63. package/workflows/verify/progress/SKILL.md +2 -3
  64. package/workflows/verify/progress/workflow.yaml +0 -1
  65. package/workflows/verify/qa/SKILL.md +4 -5
  66. package/workflows/verify/qa/workflow.yaml +1 -2
  67. package/workflows/verify/security/SKILL.md +1 -2
  68. package/workflows/verify/security/workflow.yaml +1 -2
  69. package/workflows/verify/simplify/SKILL.md +2 -3
  70. package/workflows/verify/simplify/workflow.yaml +1 -2
  71. package/workflows/verify-work/SKILL.md +5 -7
  72. package/workflows/verify-work/workflow.yaml +5 -7
@@ -0,0 +1,34 @@
1
+ # Third-Party Notices
2
+
3
+ `harnessed` itself is licensed under the Apache License 2.0 (see `LICENSE` and
4
+ `NOTICE` at the repo root). This file enumerates third-party material whose
5
+ text — verbatim or paraphrased — is incorporated into shipped harnessed source
6
+ files. Upstream attributions for each capability *manifest* (and its installed
7
+ side effects on the user's machine) continue to be tracked separately in
8
+ `NOTICES.md` (auto-populated by `harnessed install` from each manifest's
9
+ `metadata.upstream.notice` field).
10
+
11
+ ---
12
+
13
+ ## mattpocock/skills
14
+
15
+ - **Source**: https://github.com/mattpocock/skills
16
+ - **Commit SHA pinned**: `b8be62ffacb0118fa3eaa29a0923c87c8c11985c`
17
+ - **License**: MIT
18
+ - **Used in**: `workflows/role-prompts.yaml` — paraphrased methodology
19
+ excerpts inlined into the `task-clarify`, `task-code`, and `discuss-subtask`
20
+ sub-workflow role-prompt entries (introduced in v3.6.0 Phase 1). Each
21
+ paraphrased block carries an inline attribution comment naming the source
22
+ SKILL.md path and the pinned commit SHA.
23
+ - **Excerpts paraphrased (not redistributed verbatim)**:
24
+ - `skills/engineering/grill-with-docs/SKILL.md` → `task-clarify.responsibility` + checklist
25
+ - `skills/engineering/zoom-out/SKILL.md` → `task-code.checklist`
26
+ - `skills/engineering/improve-codebase-architecture/SKILL.md` → `task-code.checklist`
27
+ - `skills/productivity/grill-me/SKILL.md` → `discuss-subtask.responsibility` + checklist
28
+ - **Full upstream license text**: preserved at
29
+ `.planning/v3.6.0/mattpocock-source/LICENSE` (not shipped in the npm
30
+ tarball; retained for audit + license-compliance evidence). The SHA
31
+ metadata + re-fetch instructions live in
32
+ `.planning/v3.6.0/mattpocock-source/SHA.txt`.
33
+ - **Scope of redistribution**: only the paraphrased excerpts above are
34
+ redistributed (no verbatim SKILL.md content ships in the npm tarball).
package/dist/cli.mjs CHANGED
@@ -621,6 +621,105 @@ var init_check_planning_with_files = __esm({
621
621
  REMEDIATION = "install via Claude Code plugin marketplace: `claude plugin install planning-with-files` (requires >=2.2.0 per R20.15 + D-15)";
622
622
  }
623
623
  });
624
+
625
+ // src/cli/lib/check-mattpocock-skills.ts
626
+ var check_mattpocock_skills_exports = {};
627
+ __export(check_mattpocock_skills_exports, {
628
+ checkMattpocockSkills: () => checkMattpocockSkills
629
+ });
630
+ async function checkMattpocockSkills() {
631
+ const pluginRoot = join(
632
+ homedir(),
633
+ ".claude",
634
+ "plugins",
635
+ "cache",
636
+ "mattpocock-skills",
637
+ "mattpocock-skills"
638
+ );
639
+ const skillRoot = join(homedir(), ".claude", "skills", "mattpocock-skills");
640
+ try {
641
+ const entries = await readdir(pluginRoot);
642
+ const versions = entries.filter((e) => /^\d+\.\d+/.test(e));
643
+ if (versions.length > 0) {
644
+ return {
645
+ name: "mattpocock-skills",
646
+ status: "pass",
647
+ message: `installed as plugin (version ${versions.join(", ")})`
648
+ };
649
+ }
650
+ } catch {
651
+ }
652
+ try {
653
+ await stat(skillRoot);
654
+ return {
655
+ name: "mattpocock-skills",
656
+ status: "pass",
657
+ message: `installed as user-skill (${skillRoot})`
658
+ };
659
+ } catch {
660
+ }
661
+ return {
662
+ name: "mattpocock-skills",
663
+ status: "warn",
664
+ message: "not installed (plugin cache + user-skill paths both missing)",
665
+ fix: REMEDIATION2
666
+ };
667
+ }
668
+ var REMEDIATION2;
669
+ var init_check_mattpocock_skills = __esm({
670
+ "src/cli/lib/check-mattpocock-skills.ts"() {
671
+ REMEDIATION2 = "install via Claude Code plugin marketplace: `claude plugin install mattpocock-skills` (or git clone https://github.com/mattpocock/skills ~/.claude/skills/mattpocock-skills); methodology fallback already inline in role-prompts.yaml per v3.6.0 Phase 1 \u2014 install is optional but enables /grill-with-docs /zoom-out etc. SlashCommand acceleration";
672
+ }
673
+ });
674
+
675
+ // src/cli/lib/check-mcp-availability.ts
676
+ var check_mcp_availability_exports = {};
677
+ __export(check_mcp_availability_exports, {
678
+ checkMcpAvailability: () => checkMcpAvailability
679
+ });
680
+ async function checkMcpAvailability() {
681
+ const settingsPath3 = join(homedir(), ".claude", "settings.json");
682
+ let installed = [];
683
+ let missing = [...TARGET_SERVERS];
684
+ try {
685
+ const raw = await readFile(settingsPath3, "utf8");
686
+ const parsed = JSON.parse(raw);
687
+ const servers = parsed.mcpServers ?? {};
688
+ const serverNames = Object.keys(servers);
689
+ installed = TARGET_SERVERS.filter(
690
+ (s) => serverNames.some((n) => n.includes(s) || s.includes(n))
691
+ );
692
+ missing = TARGET_SERVERS.filter((s) => !installed.includes(s));
693
+ } catch {
694
+ }
695
+ if (missing.length === 0) {
696
+ return {
697
+ name: "MCP servers (tavily/exa/chrome-devtools)",
698
+ status: "pass",
699
+ message: `all 3 installed: ${installed.join(", ")}`
700
+ };
701
+ }
702
+ if (installed.length === 0) {
703
+ return {
704
+ name: "MCP servers (tavily/exa/chrome-devtools)",
705
+ status: "warn",
706
+ message: "none of 3 target MCP servers installed in ~/.claude/settings.json",
707
+ fix: "install via `claude mcp add <server-name>`; harnessed routes web-search to tavily/exa per workflows/judgments/web-search-routing.yaml \u2014 without them, falls back to WebFetch/WebSearch built-in (degraded but functional)"
708
+ };
709
+ }
710
+ return {
711
+ name: "MCP servers (tavily/exa/chrome-devtools)",
712
+ status: "warn",
713
+ message: `${installed.length}/3 installed: ${installed.join(", ")}; missing: ${missing.join(", ")}`,
714
+ fix: `install missing via \`claude mcp add ${missing.join(" && claude mcp add ")}\``
715
+ };
716
+ }
717
+ var TARGET_SERVERS;
718
+ var init_check_mcp_availability = __esm({
719
+ "src/cli/lib/check-mcp-availability.ts"() {
720
+ TARGET_SERVERS = ["tavily-mcp", "exa-mcp", "chrome-devtools"];
721
+ }
722
+ });
624
723
  function statePath() {
625
724
  return harnessedFile("current-workflow.json");
626
725
  }
@@ -853,7 +952,7 @@ var init_resume = __esm({
853
952
 
854
953
  // package.json
855
954
  var package_default = {
856
- version: "3.4.4"};
955
+ version: "3.6.0"};
857
956
 
858
957
  // src/manifest/errors.ts
859
958
  function instancePathToKeyPath(instancePath) {
@@ -1704,7 +1803,7 @@ function renderHumanTable(records) {
1704
1803
  }
1705
1804
  }
1706
1805
  function pipeToJq(filterExpr, lines) {
1707
- return new Promise((resolve14, reject) => {
1806
+ return new Promise((resolve15, reject) => {
1708
1807
  const child = spawn("jq", [filterExpr], {
1709
1808
  stdio: ["pipe", "inherit", "inherit"],
1710
1809
  windowsHide: true
@@ -1713,12 +1812,12 @@ function pipeToJq(filterExpr, lines) {
1713
1812
  const e = err2;
1714
1813
  if (e.code === "ENOENT") {
1715
1814
  console.error(t("audit_log.jq_missing"));
1716
- resolve14(1);
1815
+ resolve15(1);
1717
1816
  } else {
1718
1817
  reject(err2);
1719
1818
  }
1720
1819
  });
1721
- child.on("close", (code) => resolve14(code ?? 0));
1820
+ child.on("close", (code) => resolve15(code ?? 0));
1722
1821
  child.stdin.write(lines.join("\n"));
1723
1822
  child.stdin.end();
1724
1823
  });
@@ -2026,18 +2125,26 @@ async function checkAgentTeamsEnv() {
2026
2125
  async function checkPlanningPlugin() {
2027
2126
  return (await Promise.resolve().then(() => (init_check_planning_with_files(), check_planning_with_files_exports))).checkPlanningWithFiles();
2028
2127
  }
2128
+ async function checkMattpocockSkillsInstall() {
2129
+ return (await Promise.resolve().then(() => (init_check_mattpocock_skills(), check_mattpocock_skills_exports))).checkMattpocockSkills();
2130
+ }
2131
+ async function checkMcpAvailabilityCheck() {
2132
+ return (await Promise.resolve().then(() => (init_check_mcp_availability(), check_mcp_availability_exports))).checkMcpAvailability();
2133
+ }
2029
2134
  function registerDoctor(program2) {
2030
2135
  program2.command("doctor").description(
2031
- "Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget / Agent Teams / planning-with-files)"
2136
+ "Preflight checks (Node / MCP scope / jq / Win bash / origin URL / gstack prefix / deprecations / token budget / Agent Teams / planning-with-files / mattpocock-skills / MCP availability)"
2032
2137
  ).option("--json", "output JSON instead of human-readable").action(async (opts) => {
2033
- const [mcp, origin, gstack, dep, tok, at, ppwf] = await Promise.all([
2138
+ const [mcp, origin, gstack, dep, tok, at, ppwf, matt, mcpAvail] = await Promise.all([
2034
2139
  checkMcpScope(),
2035
2140
  checkOriginUrl(),
2036
2141
  checkGstackPrefix(),
2037
2142
  checkDeprecations2(),
2038
2143
  checkTokenBudget2(),
2039
2144
  checkAgentTeamsEnv(),
2040
- checkPlanningPlugin()
2145
+ checkPlanningPlugin(),
2146
+ checkMattpocockSkillsInstall(),
2147
+ checkMcpAvailabilityCheck()
2041
2148
  ]);
2042
2149
  const results = [
2043
2150
  checkNodeVersion(),
@@ -2049,7 +2156,9 @@ function registerDoctor(program2) {
2049
2156
  dep,
2050
2157
  tok,
2051
2158
  at,
2052
- ppwf
2159
+ ppwf,
2160
+ matt,
2161
+ mcpAvail
2053
2162
  ];
2054
2163
  const hasFail = results.some((r) => r.status === "fail");
2055
2164
  const hasWarn = results.some((r) => r.status === "warn");
@@ -2530,6 +2639,22 @@ var JudgmentRulesFile = Type.Object(
2530
2639
  { additionalProperties: false }
2531
2640
  );
2532
2641
  Type.Union([JudgmentTriggersFile, JudgmentRulesFile]);
2642
+ var UserOverrideEntry = Type.Object(
2643
+ {
2644
+ id: Type.String({ minLength: 1 }),
2645
+ // kebab-case (e.g. 'brainstorm', 'arch-review')
2646
+ keywords: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
2647
+ triggers: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 })
2648
+ },
2649
+ { additionalProperties: false }
2650
+ );
2651
+ var UserOverridesFile = Type.Object(
2652
+ {
2653
+ schema_version: Type.Literal("harnessed.user-overrides.v1"),
2654
+ overrides: Type.Array(UserOverrideEntry, { minItems: 1 })
2655
+ },
2656
+ { additionalProperties: false }
2657
+ );
2533
2658
 
2534
2659
  // src/workflow/judgmentResolver.ts
2535
2660
  var TriggerNotFoundError = class extends Error {
@@ -2544,6 +2669,10 @@ var TriggerNotFoundError = class extends Error {
2544
2669
  };
2545
2670
  var _fileCache = /* @__PURE__ */ new Map();
2546
2671
  async function resolveJudgmentGate(gateRef, context, packageRoot) {
2672
+ const userOverrides = context.user_overrides;
2673
+ if (Array.isArray(userOverrides) && userOverrides.includes(gateRef)) {
2674
+ return true;
2675
+ }
2547
2676
  const parts = gateRef.split(".");
2548
2677
  if (parts.length !== 4 || parts[0] !== "judgments") {
2549
2678
  throw new Error(`Invalid gate ref: ${gateRef}`);
@@ -2648,7 +2777,14 @@ var COMPLETION_SCHEMA = {
2648
2777
  status: { type: "string", enum: ["COMPLETE", "PARTIAL", "BLOCKED"] },
2649
2778
  phase: { type: "string", enum: ["01-clarify", "02-code", "03-test", "04-deliver"] },
2650
2779
  summary: { type: "string" },
2651
- blockers: { type: "array", items: { type: "string" } }
2780
+ blockers: { type: "array", items: { type: "string" } },
2781
+ // v3.5.0 Phase 2 — Option 1-Lite signal-driven Agent Teams escalation.
2782
+ // spawned subagent SHOULD set this when any of parallelism-gate.yaml 5
2783
+ // upgrade triggers fire. harnessed runtime propagates to stderr hint;
2784
+ // user opens team in main Claude Code session (TeamCreate not exposed to
2785
+ // spawned subagents via SDK v0.3.142 — see PHASE-2-SPEC.md § Why).
2786
+ needs_teams_escalation: { type: "boolean" },
2787
+ escalation_reason: { type: "string" }
2652
2788
  },
2653
2789
  required: ["status", "phase"]
2654
2790
  };
@@ -3171,12 +3307,48 @@ async function runMasterOrchestrator(masterName, context, packageRoot, spawnDriv
3171
3307
  var MASTER_NAMES = ["discuss", "plan", "task", "verify", "auto"];
3172
3308
  var RALPH_DEFAULT_MAX_ITER = 20;
3173
3309
  var RALPH_HARD_UPPER_LIMIT = 100;
3310
+ var ESCALATION_RULES = `If during this task you detect ANY of the following 5 conditions, set \`needs_teams_escalation: true\` in your structured output and fill \`escalation_reason\` with the trigger name + one-sentence specifics. These are signals to the human user in the main Claude Code session \u2014 do NOT attempt to call TeamCreate/SendMessage/TeamDelete yourself (those tools are not available to you).
3311
+
3312
+ Five triggers (any one suffices):
3313
+
3314
+ 1. **teammate_send_message_needed** \u2014 the task requires two or more subagents to exchange messages mid-task (e.g., reconciling API contract proposals across frontend and backend), not just fan-out + report.
3315
+
3316
+ 2. **subagent_context_overflow** \u2014 your context budget is filling and a separate subagent is needed to take over a portion of the work.
3317
+
3318
+ 3. **shared_task_list** \u2014 multiple subagents need to coordinate self-assignment from a shared task list (not pre-partitioned work).
3319
+
3320
+ 4. **opposing_hypothesis_debate** \u2014 the task requires two subagents to defend opposing hypotheses to a lead arbiter (e.g., root-cause debugging where two competing theories need separate evidence-gathering).
3321
+
3322
+ 5. **fullstack_three_way** \u2014 the task is a synchronized fullstack push (frontend + backend + tests) requiring API contract alignment across three roles simultaneously.
3323
+
3324
+ If none of the five apply, omit \`needs_teams_escalation\` (defaults to false) and proceed normally.`;
3325
+ var TRANSPARENT_SKIP_RULES = `When you encounter a phase gate or routing decision where the input context is missing key fields, default-valued, or contradictory, do NOT proceed silently. Instead, skip the phase and emit a one-line transparent explanation:
3326
+
3327
+ "Skipped <phase>, because <reason>. Tell me if you actually need it."
3328
+ (\u4E2D\u6587: "\u8FD9\u6B21\u8DF3\u8FC7\u4E86 <phase>, \u56E0\u4E3A <reason>. \u5982\u679C\u4F60\u8BA4\u4E3A\u9700\u8981\u8BF7\u660E\u8BF4.")
3329
+
3330
+ This applies to: strategic-layer review skip / phase-layer clarification skip / subtask-brainstorming skip / TDD enforcement skip / Agent Teams escalation skip. Chain-isolation rule: skipping one layer does NOT mandate skipping subsequent layers \u2014 each layer is independently evaluated.`;
3331
+ var AGENT_TEAMS_PREVENTION_RULES = `If you signal needs_teams_escalation=true, ALSO advise the user on these 4 Agent Teams prevention rules in your escalation_reason or summary (the user will be the one calling TeamCreate / SendMessage / TeamDelete; remind them upfront):
3332
+
3333
+ 1. **Session-scoped**: Teams live only in the current Claude Code session. \`/resume\` loses all teammates. Do not treat teams as persistent state \u2014 finish team work within one session.
3334
+
3335
+ 2. **Cleanup mandatory**: Before session ends, send \`SendMessage(to=<teammate>, content="shutdown_request")\` to each teammate, then call \`TeamDelete\`. Orphan teammates consume resources. This is a hard rule, not advisory.
3336
+
3337
+ 3. **Token cost estimation**: Before creating a team, estimate \`team_cost \u2248 N_teammates \xD7 N_rounds \xD7 avg_tokens_per_round + N_teammates \xD7 initial_brief_tokens\`. Compare to subagent fan-out cost (\`\u2248 N_subagents \xD7 (initial_brief + summary_tokens)\`). Only open a team when \`team_cost < 2 \xD7 subagent_cost\` \u2014 otherwise prefer fan-out.
3338
+
3339
+ 4. **Brief must be self-contained**: Each teammate launches WITHOUT main-session context. The Agent() prompt must include enough background, file paths, success criteria, and counter-positions so the teammate can work independently. Generic prompts produce shallow output.`;
3340
+ var CRITICAL_SYSTEM_REMINDER = `${ESCALATION_RULES}
3341
+
3342
+ ${TRANSPARENT_SKIP_RULES}
3343
+
3344
+ ${AGENT_TEAMS_PREVENTION_RULES}`;
3174
3345
  function buildAgentDef(skillName, rolePrompts, workflowName, modelTierOverride) {
3175
3346
  const rp = rolePrompts?.[skillName] ?? (workflowName ? rolePrompts?.[workflowName] : void 0);
3176
3347
  if (!rp) {
3177
3348
  return {
3178
3349
  description: `harnessed workflow phase: ${skillName}`,
3179
3350
  prompt: `You are executing the '${skillName}' workflow phase. Follow the phase intent and emit a structured COMPLETE signal when done.`,
3351
+ criticalSystemReminder_EXPERIMENTAL: CRITICAL_SYSTEM_REMINDER,
3180
3352
  ...modelTierOverride ? { model: modelTierOverride } : {}
3181
3353
  };
3182
3354
  }
@@ -3197,6 +3369,7 @@ ${rp.checklist.map((c, i) => ` ${i + 1}. ${c}`).join("\n")}` : "";
3197
3369
  return {
3198
3370
  description: rp.description,
3199
3371
  prompt,
3372
+ criticalSystemReminder_EXPERIMENTAL: CRITICAL_SYSTEM_REMINDER,
3200
3373
  ...modelTierOverride ? { model: modelTierOverride } : {}
3201
3374
  };
3202
3375
  }
@@ -3261,10 +3434,15 @@ var _dispatchSkillStub = {
3261
3434
  }
3262
3435
  const env = JSON.parse(envelopeJson);
3263
3436
  const status = env.structured_output?.status === "COMPLETE" || env.subtype === "success" ? "ok" : "fail";
3437
+ const escalation = env.structured_output?.needs_teams_escalation === true;
3264
3438
  return {
3265
3439
  status,
3266
3440
  output: env.text ?? env.result ?? "",
3267
- ...env.structured_output?.status ? { decision: env.structured_output.status } : {}
3441
+ ...env.structured_output?.status ? { decision: env.structured_output.status } : {},
3442
+ ...escalation ? {
3443
+ needsTeamsEscalation: true,
3444
+ ...env.structured_output?.escalation_reason ? { escalationReason: env.structured_output.escalation_reason } : {}
3445
+ } : {}
3268
3446
  };
3269
3447
  }
3270
3448
  };
@@ -3353,6 +3531,12 @@ async function runWorkflow(yamlPath, vars, opts = {}) {
3353
3531
  if (r.status !== "ok") {
3354
3532
  return { status: "failed", phasesRun: i, lastPhaseId: ph.id };
3355
3533
  }
3534
+ if (r.needsTeamsEscalation === true) {
3535
+ const reason = r.escalationReason ?? "unspecified trigger";
3536
+ console.error(
3537
+ `\u26A0\uFE0F phase ${ph.id} suggests Agent Teams escalation \u2014 ${reason}. Consider opening a team in your main Claude Code session (TeamCreate) if continuing this work benefits from teammate coordination. See workflows/judgments/parallelism-gate.yaml for the 5 upgrade triggers.`
3538
+ );
3539
+ }
3356
3540
  if (r.target === "chat") {
3357
3541
  try {
3358
3542
  await runAfterOutputHook({
@@ -3401,6 +3585,46 @@ function getPackageRoot() {
3401
3585
  }
3402
3586
  return resolve(thisDir, "..", "..", "..");
3403
3587
  }
3588
+ async function loadUserOverrides(packageRoot) {
3589
+ const yamlPath = resolve(packageRoot, "workflows", "judgments", "user-overrides.yaml");
3590
+ let raw;
3591
+ try {
3592
+ raw = await readFile(yamlPath, "utf8");
3593
+ } catch {
3594
+ return [];
3595
+ }
3596
+ let parsed;
3597
+ try {
3598
+ parsed = parse(raw);
3599
+ } catch {
3600
+ return [];
3601
+ }
3602
+ if (!Value.Check(UserOverridesFile, parsed)) {
3603
+ return [];
3604
+ }
3605
+ const valid = parsed;
3606
+ return valid.overrides.map((o) => ({
3607
+ id: o.id,
3608
+ keywords: [...o.keywords],
3609
+ triggers: [...o.triggers]
3610
+ }));
3611
+ }
3612
+ function extractMatchedTriggers(userText, overrides) {
3613
+ if (!userText || overrides.length === 0) return [];
3614
+ const haystack = userText.toLowerCase();
3615
+ const matched = /* @__PURE__ */ new Set();
3616
+ for (const entry of overrides) {
3617
+ for (const kw of entry.keywords) {
3618
+ if (haystack.includes(kw.toLowerCase())) {
3619
+ for (const trigger of entry.triggers) matched.add(trigger);
3620
+ break;
3621
+ }
3622
+ }
3623
+ }
3624
+ return [...matched];
3625
+ }
3626
+
3627
+ // src/cli/run.ts
3404
3628
  var PACKAGE_ROOT = getPackageRoot();
3405
3629
  var WORKFLOWS_DIR = join(PACKAGE_ROOT, "workflows");
3406
3630
  var _autoChainCache = null;
@@ -3438,11 +3662,19 @@ function registerRun(program2) {
3438
3662
  );
3439
3663
  process.exit(2);
3440
3664
  }
3665
+ const overrides = await loadUserOverrides(PACKAGE_ROOT);
3666
+ const matchedTriggers = extractMatchedTriggers(task, overrides);
3667
+ if (matchedTriggers.length > 0) {
3668
+ console.error(
3669
+ `\u2139 user-override detected: ${matchedTriggers.length} trigger(s) forced fires=true via keyword match (${matchedTriggers.join(", ")})`
3670
+ );
3671
+ }
3441
3672
  const gateContext = {
3442
3673
  task,
3443
3674
  ...raw.model ? { modelOverride: raw.model } : {},
3444
3675
  ...raw.maxIterations ? { maxIterations: raw.maxIterations } : {},
3445
- ...raw.staged ? { staged: true } : {}
3676
+ ...raw.staged ? { staged: true } : {},
3677
+ ...matchedTriggers.length > 0 ? { user_overrides: matchedTriggers } : {}
3446
3678
  };
3447
3679
  if (raw.dryRun) {
3448
3680
  console.log(JSON.stringify({ workflow: name, yamlPath, gateContext }, null, 2));
@@ -4017,7 +4249,7 @@ async function isPluginRegistered(pluginName) {
4017
4249
  return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
4018
4250
  }
4019
4251
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
4020
- return new Promise((resolve14) => {
4252
+ return new Promise((resolve15) => {
4021
4253
  const isWin = process.platform === "win32";
4022
4254
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
4023
4255
  let stderr = "";
@@ -4026,15 +4258,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
4026
4258
  });
4027
4259
  const timer = setTimeout(() => {
4028
4260
  child.kill("SIGKILL");
4029
- resolve14({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
4261
+ resolve15({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
4030
4262
  }, timeoutMs);
4031
4263
  child.on("error", (e) => {
4032
4264
  clearTimeout(timer);
4033
- resolve14({ exitCode: -1, stderr: `${stderr}${e.message}` });
4265
+ resolve15({ exitCode: -1, stderr: `${stderr}${e.message}` });
4034
4266
  });
4035
4267
  child.on("close", (code) => {
4036
4268
  clearTimeout(timer);
4037
- resolve14({ exitCode: code ?? -1, stderr });
4269
+ resolve15({ exitCode: code ?? -1, stderr });
4038
4270
  });
4039
4271
  });
4040
4272
  }
@@ -4213,10 +4445,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4213
4445
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
4214
4446
  stderr += chunk;
4215
4447
  });
4216
- return await new Promise((resolve14) => {
4448
+ return await new Promise((resolve15) => {
4217
4449
  const timer = setTimeout(() => {
4218
4450
  child.kill("SIGKILL");
4219
- resolve14({
4451
+ resolve15({
4220
4452
  ok: false,
4221
4453
  phase: "spawn",
4222
4454
  error: {
@@ -4231,7 +4463,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4231
4463
  }, effectiveTimeoutMs);
4232
4464
  child.on("error", (err2) => {
4233
4465
  clearTimeout(timer);
4234
- resolve14({
4466
+ resolve15({
4235
4467
  ok: false,
4236
4468
  phase: "spawn",
4237
4469
  error: {
@@ -4246,14 +4478,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
4246
4478
  });
4247
4479
  child.on("close", (code) => {
4248
4480
  clearTimeout(timer);
4249
- resolve14({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
4481
+ resolve15({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
4250
4482
  });
4251
4483
  });
4252
4484
  }
4253
4485
 
4254
4486
  // src/installers/gitCloneWithSetup.ts
4255
4487
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
4256
- return new Promise((resolve14) => {
4488
+ return new Promise((resolve15) => {
4257
4489
  const isWin = process.platform === "win32";
4258
4490
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
4259
4491
  let stdout2 = "";
@@ -4262,15 +4494,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
4262
4494
  });
4263
4495
  const timer = setTimeout(() => {
4264
4496
  child.kill("SIGKILL");
4265
- resolve14({ sha: "", exit: -1 });
4497
+ resolve15({ sha: "", exit: -1 });
4266
4498
  }, timeoutMs);
4267
4499
  child.on("error", () => {
4268
4500
  clearTimeout(timer);
4269
- resolve14({ sha: "", exit: -1 });
4501
+ resolve15({ sha: "", exit: -1 });
4270
4502
  });
4271
4503
  child.on("close", (code) => {
4272
4504
  clearTimeout(timer);
4273
- resolve14({ sha: stdout2.trim(), exit: code ?? -1 });
4505
+ resolve15({ sha: stdout2.trim(), exit: code ?? -1 });
4274
4506
  });
4275
4507
  });
4276
4508
  }
@@ -6203,7 +6435,7 @@ var uninstallNpmCli = async (ctx) => {
6203
6435
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
6204
6436
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
6205
6437
  const isWin = process.platform === "win32";
6206
- const result = await new Promise((resolve14) => {
6438
+ const result = await new Promise((resolve15) => {
6207
6439
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
6208
6440
  let stderr = "";
6209
6441
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -6211,15 +6443,15 @@ var uninstallNpmCli = async (ctx) => {
6211
6443
  });
6212
6444
  const timer = setTimeout(() => {
6213
6445
  child.kill("SIGKILL");
6214
- resolve14({ exitCode: -1, stderr: `${stderr}[timeout]` });
6446
+ resolve15({ exitCode: -1, stderr: `${stderr}[timeout]` });
6215
6447
  }, 3e4);
6216
6448
  child.on("error", (e) => {
6217
6449
  clearTimeout(timer);
6218
- resolve14({ exitCode: -1, stderr: e.message });
6450
+ resolve15({ exitCode: -1, stderr: e.message });
6219
6451
  });
6220
6452
  child.on("close", (code) => {
6221
6453
  clearTimeout(timer);
6222
- resolve14({ exitCode: code ?? -1, stderr });
6454
+ resolve15({ exitCode: code ?? -1, stderr });
6223
6455
  });
6224
6456
  });
6225
6457
  if (result.exitCode !== 0) {