harnessed 2.0.1 → 3.0.1

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 (68) hide show
  1. package/README.md +246 -47
  2. package/dist/cli.mjs +416 -98
  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 +1 -1
  7. package/workflows/capabilities.yaml +468 -0
  8. package/workflows/defaults.yaml +71 -4
  9. package/workflows/disciplines/karpathy.yaml +47 -0
  10. package/workflows/disciplines/language.yaml +38 -0
  11. package/workflows/disciplines/operational.yaml +61 -0
  12. package/workflows/disciplines/output-style.yaml +62 -0
  13. package/workflows/disciplines/priority.yaml +28 -0
  14. package/workflows/disciplines/protocols.yaml +70 -0
  15. package/workflows/discuss/auto/.gitkeep +0 -0
  16. package/workflows/discuss/auto/SKILL.md +63 -0
  17. package/workflows/discuss/auto/workflow.yaml +40 -0
  18. package/workflows/discuss/phase/SKILL.md +61 -0
  19. package/workflows/discuss/phase/workflow.yaml +35 -0
  20. package/workflows/discuss/strategic/SKILL.md +66 -0
  21. package/workflows/discuss/strategic/workflow.yaml +47 -0
  22. package/workflows/discuss/subtask/SKILL.md +67 -0
  23. package/workflows/discuss/subtask/workflow.yaml +33 -0
  24. package/workflows/judgments/stage-routing.yaml +93 -0
  25. package/workflows/judgments/web-design-routing.yaml +37 -0
  26. package/workflows/judgments/web-search-routing.yaml +52 -0
  27. package/workflows/judgments/web-testing-routing.yaml +50 -0
  28. package/workflows/plan/architecture/SKILL.md +62 -0
  29. package/workflows/plan/architecture/workflow.yaml +33 -0
  30. package/workflows/plan/auto/.gitkeep +0 -0
  31. package/workflows/plan/auto/SKILL.md +63 -0
  32. package/workflows/plan/auto/workflow.yaml +41 -0
  33. package/workflows/plan/phase/SKILL.md +64 -0
  34. package/workflows/plan/phase/workflow.yaml +37 -0
  35. package/workflows/research/SKILL.md +6 -2
  36. package/workflows/research/workflow.yaml +34 -3
  37. package/workflows/retro/SKILL.md +68 -0
  38. package/workflows/retro/workflow.yaml +40 -0
  39. package/workflows/task/auto/.gitkeep +0 -0
  40. package/workflows/task/auto/SKILL.md +68 -0
  41. package/workflows/task/auto/workflow.yaml +57 -0
  42. package/workflows/task/clarify/SKILL.md +83 -0
  43. package/workflows/task/clarify/workflow.yaml +39 -0
  44. package/workflows/task/code/SKILL.md +89 -0
  45. package/workflows/task/code/workflow.yaml +55 -0
  46. package/workflows/task/deliver/SKILL.md +118 -0
  47. package/workflows/task/deliver/workflow.yaml +77 -0
  48. package/workflows/task/test/SKILL.md +93 -0
  49. package/workflows/task/test/workflow.yaml +44 -0
  50. package/workflows/verify/auto/.gitkeep +0 -0
  51. package/workflows/verify/auto/SKILL.md +77 -0
  52. package/workflows/verify/auto/workflow.yaml +74 -0
  53. package/workflows/verify/code-review/SKILL.md +69 -0
  54. package/workflows/verify/code-review/workflow.yaml +32 -0
  55. package/workflows/verify/design/SKILL.md +72 -0
  56. package/workflows/verify/design/workflow.yaml +33 -0
  57. package/workflows/verify/multispec/SKILL.md +86 -0
  58. package/workflows/verify/multispec/workflow.yaml +58 -0
  59. package/workflows/verify/paranoid/SKILL.md +71 -0
  60. package/workflows/verify/paranoid/workflow.yaml +30 -0
  61. package/workflows/verify/progress/SKILL.md +67 -0
  62. package/workflows/verify/progress/workflow.yaml +44 -0
  63. package/workflows/verify/qa/SKILL.md +73 -0
  64. package/workflows/verify/qa/workflow.yaml +31 -0
  65. package/workflows/verify/security/SKILL.md +67 -0
  66. package/workflows/verify/security/workflow.yaml +31 -0
  67. package/workflows/verify/simplify/SKILL.md +67 -0
  68. package/workflows/verify/simplify/workflow.yaml +31 -0
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync, spawn } from 'child_process';
2
+ import { execSync, spawnSync, spawn } from 'child_process';
3
3
  import { writeFileSync, existsSync, readFileSync, mkdirSync, appendFileSync, readdirSync } from 'fs';
4
4
  import { resolve, join, dirname, relative } from 'path';
5
5
  import { Type } from '@sinclair/typebox';
@@ -169,17 +169,21 @@ var init_schemaVersion = __esm({
169
169
  governance: "harnessed.governance.v1",
170
170
  // ← Phase 3.2 W1 T1.1 ADD 10th surface (D-04 PUSH veto status)
171
171
  planFeature: "harnessed.plan-feature.v1",
172
- // ← Phase 3.3 W0 T0.5 BACKFILL 11th surface (sister Phase 3.2 W2 T2.2 b875e21 commit msg claim "11th surface" was LATENT STALE — never registered; T0.5 surgical fix per sister Phase 3.2 W2 T2.6 latent W1 c37ee29 Rule 1 pattern)
172
+ // ← Phase 3.3 W0 T0.5 BACKFILL 11th surface
173
173
  aliases: "harnessed.aliases.v1",
174
- // ← Phase 3.3 W1 T1.1 ADD 12th surface (D-01 RICH manifests/aliases.yaml upstream rename redirect + metadata)
174
+ // ← Phase 3.3 W1 T1.1 ADD 12th surface (D-01 RICH)
175
175
  knownGood: "harnessed.known-good.v1",
176
- // ← Phase 3.3 W1 T1.1 ADD 13th surface (D-03 YAML versions/<harnessed-ver>-known-good.yaml per-version lock)
176
+ // ← Phase 3.3 W1 T1.1 ADD 13th surface (D-03 YAML manifest)
177
177
  capabilities: "harnessed.capabilities.v1",
178
- // ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 14th surface (R20.2 flat yaml capabilities manifest validate)
178
+ // ← Phase v2.0-2.3 W0 ADD 14th surface (R20.2 flat yaml capabilities manifest validate)
179
179
  judgment: "harnessed.judgment.v1",
180
- // ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 15th surface (R20.4 multi-file judgments triggers/rules validate)
181
- workflow: "harnessed.workflow.v2"
182
- // ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (R20.1 + R20.2 + R20.9 workflow.yaml v2 schema: gate / on / capability / args / fallback / parallelism 字段, sister 4 workflow.yaml: plan-feature + execute-task + research + verify-work)
180
+ // ← Phase v2.0-2.3 W0 ADD 15th surface (R20.4 multi-file judgments validate)
181
+ workflow: "harnessed.workflow.v2",
182
+ // ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (R20.1+R20.2+R20.9 workflow.yaml v2)
183
+ workflow_v3: "harnessed.workflow.v3",
184
+ // ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD 17th surface (D-09 disciplines_applied + D-05 tools_available + master delegates_to per Pattern A B.1 LOCK)
185
+ discipline: "harnessed.discipline.v1"
186
+ // ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD 18th surface (D-09 L0 Discipline Substrate, sister judgment.v1 multi-file pattern)
183
187
  };
184
188
  Type.Union([
185
189
  Type.Literal(SCHEMA_VERSIONS.routingSnapshot),
@@ -202,11 +206,15 @@ var init_schemaVersion = __esm({
202
206
  Type.Literal(SCHEMA_VERSIONS.knownGood),
203
207
  // ← Phase 3.3 W1 T1.1 ADD 13th surface
204
208
  Type.Literal(SCHEMA_VERSIONS.capabilities),
205
- // ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 14th surface
209
+ // ← Phase v2.0-2.3 W0 ADD 14th surface
206
210
  Type.Literal(SCHEMA_VERSIONS.judgment),
207
- // ← Phase v2.0-2.3 W0 T2.3.W0.6 ADD 15th surface
208
- Type.Literal(SCHEMA_VERSIONS.workflow)
209
- // ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (workflow.yaml v2 schema, NOTE first .v2 surface in union)
211
+ // ← Phase v2.0-2.3 W0 ADD 15th surface
212
+ Type.Literal(SCHEMA_VERSIONS.workflow),
213
+ // ← Phase v2.0-2.4 W0 T2.4.W0.1 ADD 16th surface (first .v2 in union)
214
+ Type.Literal(SCHEMA_VERSIONS.workflow_v3),
215
+ // ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD 17th surface (first .v3 in union)
216
+ Type.Literal(SCHEMA_VERSIONS.discipline)
217
+ // ← Phase v3.0-3.3 W0 T3.3.W0.11 ADD 18th surface
210
218
  ]);
211
219
  }
212
220
  });
@@ -692,10 +700,10 @@ __export(knownGood_exports, {
692
700
  loadKnownGood: () => loadKnownGood
693
701
  });
694
702
  function loadKnownGood(harnessedVer) {
695
- if (_cache.has(harnessedVer)) return _cache.get(harnessedVer) ?? null;
703
+ if (_cache2.has(harnessedVer)) return _cache2.get(harnessedVer) ?? null;
696
704
  const path = join(versionsDir(), `${harnessedVer}-known-good.yaml`);
697
705
  if (!existsSync(path)) {
698
- _cache.set(harnessedVer, null);
706
+ _cache2.set(harnessedVer, null);
699
707
  return null;
700
708
  }
701
709
  const raw = readFileSync(path, "utf8");
@@ -706,7 +714,7 @@ function loadKnownGood(harnessedVer) {
706
714
  `${path} schema invalid: ${errs.map((e) => `${e.path} ${e.message}`).join("; ")}`
707
715
  );
708
716
  }
709
- _cache.set(harnessedVer, parsed);
717
+ _cache2.set(harnessedVer, parsed);
710
718
  return parsed;
711
719
  }
712
720
  function getPinnedVersion(upstreamName, harnessedVer) {
@@ -715,12 +723,12 @@ function getPinnedVersion(upstreamName, harnessedVer) {
715
723
  const entry = kg.upstreams.find((u) => u.name === upstreamName);
716
724
  return entry?.version ?? null;
717
725
  }
718
- var versionsDir, _cache;
726
+ var versionsDir, _cache2;
719
727
  var init_knownGood = __esm({
720
728
  "src/manifest/knownGood.ts"() {
721
729
  init_known_good_v1();
722
730
  versionsDir = () => join(process.cwd(), "versions");
723
- _cache = /* @__PURE__ */ new Map();
731
+ _cache2 = /* @__PURE__ */ new Map();
724
732
  }
725
733
  });
726
734
 
@@ -785,7 +793,7 @@ var init_resume = __esm({
785
793
 
786
794
  // package.json
787
795
  var package_default = {
788
- version: "2.0.1"};
796
+ version: "3.0.1"};
789
797
 
790
798
  // src/manifest/errors.ts
791
799
  function instancePathToKeyPath(instancePath) {
@@ -1566,7 +1574,7 @@ function renderHumanTable(records) {
1566
1574
  }
1567
1575
  }
1568
1576
  function pipeToJq(filterExpr, lines) {
1569
- return new Promise((resolve8, reject) => {
1577
+ return new Promise((resolve9, reject) => {
1570
1578
  const child = spawn("jq", [filterExpr], {
1571
1579
  stdio: ["pipe", "inherit", "inherit"],
1572
1580
  windowsHide: true
@@ -1575,12 +1583,12 @@ function pipeToJq(filterExpr, lines) {
1575
1583
  const e = err2;
1576
1584
  if (e.code === "ENOENT") {
1577
1585
  console.error("\u2717 jq not found in PATH \u2014 run: harnessed doctor");
1578
- resolve8(1);
1586
+ resolve9(1);
1579
1587
  } else {
1580
1588
  reject(err2);
1581
1589
  }
1582
1590
  });
1583
- child.on("close", (code) => resolve8(code ?? 0));
1591
+ child.on("close", (code) => resolve9(code ?? 0));
1584
1592
  child.stdin.write(lines.join("\n"));
1585
1593
  child.stdin.end();
1586
1594
  });
@@ -1932,6 +1940,107 @@ function registerDoctor(program2) {
1932
1940
  });
1933
1941
  }
1934
1942
 
1943
+ // src/workflow/schema/discipline.ts
1944
+ init_schemaVersion();
1945
+ var EnforcementLayer = Type.Union([
1946
+ Type.Literal("code-writing"),
1947
+ // karpathy 心法 — write code phase
1948
+ Type.Literal("output"),
1949
+ // BLUF / language / no-emoji — emit response phase
1950
+ Type.Literal("commit"),
1951
+ // biome / A7 / commit safety — pre-commit phase
1952
+ Type.Literal("workflow"),
1953
+ // priority hierarchy / protocols — workflow-level arbitration
1954
+ Type.Literal("tool")
1955
+ // tool invoke discipline (reserved for v3.x extension)
1956
+ ]);
1957
+ var Enforcement = Type.Union([
1958
+ Type.Literal("halt"),
1959
+ // process.exit non-zero, sister fallbackHandlers
1960
+ Type.Literal("warn"),
1961
+ // console.warn emit, continue
1962
+ Type.Literal("auto-fix"),
1963
+ // run auto_fix_cmd then continue (biome --write pattern)
1964
+ Type.Literal("info")
1965
+ // log only, no action
1966
+ ]);
1967
+ var DisciplineRule = Type.Object(
1968
+ {
1969
+ id: Type.String({ minLength: 1 }),
1970
+ // kebab-case
1971
+ description: Type.String(),
1972
+ // human-readable
1973
+ enforcement: Enforcement,
1974
+ trigger: Type.Union([Type.String(), Type.Array(Type.String())]),
1975
+ // expr OR always-on list
1976
+ check_method: Type.String(),
1977
+ // heuristic / regex / external-cmd / llm-judge / file-content-match
1978
+ auto_fix_cmd: Type.Optional(Type.String())
1979
+ // only enforcement=auto-fix
1980
+ },
1981
+ { additionalProperties: false }
1982
+ );
1983
+ var PriorityHierarchy = Type.Array(Type.String(), { minItems: 1 });
1984
+ var ProtocolShape = Type.Object(
1985
+ {
1986
+ description: Type.String(),
1987
+ required_fields: Type.Optional(Type.Array(Type.String())),
1988
+ forbidden_phrases: Type.Optional(Type.Array(Type.String())),
1989
+ file_ownership: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))),
1990
+ rules: Type.Optional(Type.Array(DisciplineRule))
1991
+ },
1992
+ { additionalProperties: false }
1993
+ );
1994
+ var Discipline = Type.Object(
1995
+ {
1996
+ schema_version: Type.Literal(SCHEMA_VERSIONS.discipline),
1997
+ discipline: Type.String({ minLength: 1 }),
1998
+ // basename (karpathy / output-style / ...)
1999
+ enforcement_layer: EnforcementLayer,
2000
+ auto_enforce: Type.Boolean(),
2001
+ rules: Type.Array(DisciplineRule),
2002
+ priority_hierarchy: Type.Optional(PriorityHierarchy),
2003
+ // priority.yaml only
2004
+ protocols: Type.Optional(Type.Record(Type.String(), ProtocolShape))
2005
+ // protocols.yaml only
2006
+ },
2007
+ { additionalProperties: false }
2008
+ );
2009
+
2010
+ // src/workflow/disciplineLoader.ts
2011
+ var _cache = /* @__PURE__ */ new Map();
2012
+ async function loadDiscipline(basename2, packageRoot) {
2013
+ const cached = _cache.get(basename2);
2014
+ if (cached) return cached;
2015
+ const yamlPath = resolve(packageRoot, "workflows", "disciplines", `${basename2}.yaml`);
2016
+ const raw = await readFile(yamlPath, "utf8");
2017
+ const parsedRaw = parse(raw);
2018
+ if (!Value.Check(Discipline, parsedRaw)) {
2019
+ const errors = [...Value.Errors(Discipline, parsedRaw)].slice(0, 3).map((e) => `${e.path} ${e.message}`).join("; ");
2020
+ throw new Error(`Invalid discipline file ${basename2}.yaml: ${errors}`);
2021
+ }
2022
+ const parsed = parsedRaw;
2023
+ _cache.set(basename2, parsed);
2024
+ return parsed;
2025
+ }
2026
+
2027
+ // src/discipline/enforcement/before-commit.ts
2028
+ var TS_JS_RE = /\.(ts|tsx|js|mjs)$/;
2029
+ async function runBeforeCommitHook(ctx) {
2030
+ const d = await loadDiscipline("operational", ctx.packageRoot);
2031
+ if (ctx.changedFiles.some((f) => TS_JS_RE.test(f))) {
2032
+ const rule = d.rules.find((r) => r.id === "biome-preempt");
2033
+ if (rule?.auto_fix_cmd) {
2034
+ console.warn("\u26A0\uFE0F biome preempt \u2014 running auto-fix before commit");
2035
+ execSync(rule.auto_fix_cmd, { cwd: ctx.packageRoot, stdio: "inherit" });
2036
+ }
2037
+ }
2038
+ if (ctx.cmdArgs.includes("--no-verify")) {
2039
+ console.error("\u274C no-skip-hooks violated: --no-verify forbidden");
2040
+ process.exit(2);
2041
+ }
2042
+ }
2043
+
1935
2044
  // src/routing/completionSchema.ts
1936
2045
  var COMPLETION_SCHEMA = {
1937
2046
  type: "object",
@@ -2636,15 +2745,100 @@ var PhasesSchema = Type.Object(
2636
2745
 
2637
2746
  // src/workflow/schema/workflow.ts
2638
2747
  init_schemaVersion();
2748
+
2749
+ // src/workflow/schema/workflow.v2.ts
2750
+ init_schemaVersion();
2639
2751
  var ModelTier2 = Type.Union([Type.Literal("haiku"), Type.Literal("sonnet"), Type.Literal("opus")]);
2640
2752
  var OnAction = Type.Union([Type.Literal("skip"), Type.Literal("invoke")]);
2753
+ var OnClauseV2 = Type.Object(
2754
+ {
2755
+ if: Type.String(),
2756
+ invoke: Type.Optional(Type.String()),
2757
+ action: Type.Optional(OnAction)
2758
+ },
2759
+ { additionalProperties: false }
2760
+ );
2761
+ var FallbackMaxIterationsExceededV2 = Type.Object(
2762
+ {
2763
+ action: Type.Literal("emit_warning_and_halt"),
2764
+ message: Type.String(),
2765
+ exit_code: Type.Number()
2766
+ },
2767
+ { additionalProperties: false }
2768
+ );
2769
+ var PhaseFallbackV2 = Type.Object(
2770
+ {
2771
+ max_iterations_exceeded: Type.Optional(FallbackMaxIterationsExceededV2)
2772
+ },
2773
+ { additionalProperties: false }
2774
+ );
2775
+ var WorkflowPhaseV2 = Type.Object(
2776
+ {
2777
+ id: Type.String({ minLength: 1 }),
2778
+ name: Type.Optional(Type.String()),
2779
+ upstream: Type.Optional(Type.String()),
2780
+ capability: Type.Optional(Type.String()),
2781
+ model: Type.Optional(ModelTier2),
2782
+ invokes: Type.Optional(Type.String()),
2783
+ args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
2784
+ gate: Type.Optional(Type.String()),
2785
+ on: Type.Optional(Type.Array(OnClauseV2)),
2786
+ parallelism: Type.Optional(Type.String()),
2787
+ fallback: Type.Optional(PhaseFallbackV2),
2788
+ max_iterations: Type.Optional(Type.Union([Type.Number(), Type.String()])),
2789
+ artifacts_expected: Type.Optional(Type.Array(Type.String()))
2790
+ },
2791
+ { additionalProperties: false }
2792
+ );
2793
+ var WorkflowSchemaV2 = Type.Object(
2794
+ {
2795
+ schema_version: Type.Literal(SCHEMA_VERSIONS.workflow),
2796
+ workflow: Type.String({ minLength: 1 }),
2797
+ description: Type.Optional(Type.String()),
2798
+ phases: Type.Array(WorkflowPhaseV2, { minItems: 1 })
2799
+ },
2800
+ { additionalProperties: false }
2801
+ );
2802
+
2803
+ // src/workflow/schema/workflow.ts
2804
+ var ModelTier3 = Type.Union([Type.Literal("haiku"), Type.Literal("sonnet"), Type.Literal("opus")]);
2805
+ var OnAction2 = Type.Union([Type.Literal("skip"), Type.Literal("invoke")]);
2806
+ var DisciplineName = Type.Union([
2807
+ Type.Literal("karpathy"),
2808
+ Type.Literal("output-style"),
2809
+ Type.Literal("language"),
2810
+ Type.Literal("operational"),
2811
+ Type.Literal("priority"),
2812
+ Type.Literal("protocols")
2813
+ ]);
2641
2814
  var OnClause = Type.Object(
2642
2815
  {
2643
2816
  if: Type.String(),
2644
2817
  // expr-eval expression OR judgments.<file>.<gate>.fires ref
2645
2818
  invoke: Type.Optional(Type.String()),
2646
2819
  // '{{ capabilities.<name>.cmd }}' OR literal
2647
- action: Type.Optional(OnAction)
2820
+ action: Type.Optional(OnAction2)
2821
+ },
2822
+ { additionalProperties: false }
2823
+ );
2824
+ var InvokeToolClause = Type.Object(
2825
+ {
2826
+ if: Type.Optional(Type.String()),
2827
+ // optional — 无 if = unconditional fire
2828
+ tool: Type.String({ minLength: 1 })
2829
+ // capabilities.yaml entry name (cross-validate T3.3.W0.10)
2830
+ },
2831
+ { additionalProperties: false }
2832
+ );
2833
+ var DelegationClause = Type.Object(
2834
+ {
2835
+ sub: Type.String({ minLength: 1 }),
2836
+ // sub-stage workflow name e.g. 'strategic' / 'phase' / 'subtask'
2837
+ gate: Type.Optional(Type.String()),
2838
+ // judgments.<file>.<trigger>.fires 4-level ref
2839
+ mode: Type.Optional(Type.Union([Type.Literal("parallel"), Type.Literal("serial")])),
2840
+ order: Type.Optional(Type.Number())
2841
+ // serial-only: explicit ordering (K9 mitigation enforced in check-workflow-schema)
2648
2842
  },
2649
2843
  { additionalProperties: false }
2650
2844
  );
@@ -2663,14 +2857,14 @@ var PhaseFallback = Type.Object(
2663
2857
  },
2664
2858
  { additionalProperties: false }
2665
2859
  );
2666
- var WorkflowPhaseV2 = Type.Object(
2860
+ var WorkflowPhaseV3 = Type.Object(
2667
2861
  {
2668
2862
  id: Type.String({ minLength: 1 }),
2669
2863
  name: Type.Optional(Type.String()),
2670
2864
  upstream: Type.Optional(Type.String()),
2671
2865
  capability: Type.Optional(Type.String()),
2672
2866
  // '{{ capabilities.ralph-loop.cmd }}'
2673
- model: Type.Optional(ModelTier2),
2867
+ model: Type.Optional(ModelTier3),
2674
2868
  invokes: Type.Optional(Type.String()),
2675
2869
  // legacy slash-cmd OR JINJA template
2676
2870
  args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
@@ -2684,16 +2878,25 @@ var WorkflowPhaseV2 = Type.Object(
2684
2878
  Type.Union([Type.Number(), Type.String()])
2685
2879
  // numeric literal OR jinja '{{ defaults.x.y }}'
2686
2880
  ),
2687
- artifacts_expected: Type.Optional(Type.Array(Type.String()))
2881
+ artifacts_expected: Type.Optional(Type.Array(Type.String())),
2882
+ invokes_tools: Type.Optional(Type.Array(InvokeToolClause))
2883
+ // NEW v3 D-05 phase-level conditional fire
2688
2884
  },
2689
2885
  { additionalProperties: false }
2690
2886
  );
2691
- var WorkflowSchemaV2 = Type.Object(
2887
+ Type.Object(
2692
2888
  {
2693
- schema_version: Type.Literal(SCHEMA_VERSIONS.workflow),
2889
+ schema_version: Type.Literal(SCHEMA_VERSIONS.workflow_v3),
2694
2890
  workflow: Type.String({ minLength: 1 }),
2695
2891
  description: Type.Optional(Type.String()),
2696
- phases: Type.Array(WorkflowPhaseV2, { minItems: 1 })
2892
+ disciplines_applied: Type.Optional(Type.Array(DisciplineName)),
2893
+ // NEW v3 D-09 (Pattern A A.1 strict Literal Union)
2894
+ tools_available: Type.Optional(Type.Array(Type.String())),
2895
+ // NEW v3 D-05 (cross-validate T3.3.W0.10)
2896
+ delegates_to: Type.Optional(Type.Array(DelegationClause)),
2897
+ // NEW v3 D-01 (master orchestrator only)
2898
+ phases: Type.Optional(Type.Array(WorkflowPhaseV3, { minItems: 1 }))
2899
+ // 改 Optional — master 无 phases, sub/standalone 必有
2697
2900
  },
2698
2901
  { additionalProperties: false }
2699
2902
  );
@@ -2737,7 +2940,12 @@ function validateNonInteractiveFlags(raw, cmdName) {
2737
2940
 
2738
2941
  // src/cli/execute-task.ts
2739
2942
  function registerExecuteTask(program2) {
2740
- program2.command("execute-task").description("Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE)").requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option("--apply", "execute the spawn (default: dry-run preview)").option("--dry-run", "force dry-run (overrides --apply if both set)").option("--non-interactive", "CI / scripts \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
2943
+ program2.command("execute-task").description(
2944
+ "Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
2945
+ ).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option(
2946
+ "--apply",
2947
+ "(deprecated; kept for backward compat \u2014 execute-task spawns immediately by default)"
2948
+ ).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
2741
2949
  validateNonInteractiveFlags(raw, "execute-task --task <text>");
2742
2950
  if (!raw.task) {
2743
2951
  console.error("error: --task <text> is required");
@@ -2760,7 +2968,7 @@ function registerExecuteTask(program2) {
2760
2968
  };
2761
2969
  }
2762
2970
  const taskCtx = { task: raw.task, task_type: "execute-task" };
2763
- const isDryRun = raw.dryRun === true || !raw.apply && !raw.nonInteractive;
2971
+ const isDryRun = raw.dryRun === true;
2764
2972
  if (isDryRun) {
2765
2973
  console.log(
2766
2974
  JSON.stringify({ workflow: phases.workflow, phases: phases.phases, taskCtx }, null, 2)
@@ -2776,6 +2984,22 @@ function registerExecuteTask(program2) {
2776
2984
  break;
2777
2985
  }
2778
2986
  }
2987
+ try {
2988
+ const stagedOut = execSync("git status --porcelain", { encoding: "utf8" });
2989
+ const changedFiles = stagedOut.split("\n").filter((l) => l.trim().length > 0).map((l) => l.slice(3).trim());
2990
+ await runBeforeCommitHook({
2991
+ changedFiles,
2992
+ cmdArgs: [],
2993
+ packageRoot: process.cwd(),
2994
+ cmdType: "git-commit",
2995
+ hasUserApproval: true
2996
+ // --apply explicit
2997
+ });
2998
+ } catch (err2) {
2999
+ console.warn(
3000
+ `\u26A0\uFE0F before-commit pre-flight skipped (${err2.message}); subagent will biome-check at commit time.`
3001
+ );
3002
+ }
2779
3003
  const result = await runRouting(taskCtx, {
2780
3004
  maxIterations: raw.maxIterations ?? 20,
2781
3005
  ...raw.model ? { agentOpts: { modelOverride: raw.model } } : {},
@@ -2822,8 +3046,10 @@ async function dirSizeKb(dir) {
2822
3046
  return Math.round(total / 1024);
2823
3047
  }
2824
3048
  function registerGc(program2) {
2825
- program2.command("gc").description("Garbage-collect old backup snapshots (dry-run by default)").option("--older-than <duration>", "delete snapshots older than (e.g. 30d / 24h / 4w)", "30d").option("--keep-last <N>", "always keep the most recent N snapshots", "0").option("--apply", "actually delete (default: dry-run preview only)").option("--dry-run", "force dry-run (overrides --apply)").action(async (opts) => {
2826
- const dryRun = opts.dryRun === true || opts.apply !== true;
3049
+ program2.command("gc").description(
3050
+ "Garbage-collect old backup snapshots (immediate by default \u2014 use --dry-run for preview)"
3051
+ ).option("--older-than <duration>", "delete snapshots older than (e.g. 30d / 24h / 4w)", "30d").option("--keep-last <N>", "always keep the most recent N snapshots", "0").option("--apply", "(deprecated; kept for backward compat \u2014 gc deletes immediately by default)").option("--dry-run", "preview only \u2014 do not delete (opt-in for advanced users)").action(async (opts) => {
3052
+ const dryRun = opts.dryRun === true;
2827
3053
  const olderMs = parseDuration(opts.olderThan ?? "30d");
2828
3054
  if (olderMs == null) {
2829
3055
  console.error(
@@ -2873,7 +3099,7 @@ function registerGc(program2) {
2873
3099
  console.log(` ${c.ts} ${c.manifest} (${c.sizeKb} KB)`);
2874
3100
  if (!dryRun) await rm(c.path, { recursive: true, force: true });
2875
3101
  }
2876
- if (dryRun) console.log("\n(dry-run \u2014 re-run with --apply to actually delete)");
3102
+ if (dryRun) console.log("\n(dry-run \u2014 re-run without --dry-run to actually delete)");
2877
3103
  });
2878
3104
  }
2879
3105
  async function confirmAt(level, ctx) {
@@ -3157,7 +3383,7 @@ var installCcHookAdd = async (ctx) => {
3157
3383
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
3158
3384
  };
3159
3385
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3160
- return new Promise((resolve8) => {
3386
+ return new Promise((resolve9) => {
3161
3387
  const isWin = process.platform === "win32";
3162
3388
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
3163
3389
  let stderr = "";
@@ -3166,15 +3392,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3166
3392
  });
3167
3393
  const timer = setTimeout(() => {
3168
3394
  child.kill("SIGKILL");
3169
- resolve8({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3395
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3170
3396
  }, timeoutMs);
3171
3397
  child.on("error", (e) => {
3172
3398
  clearTimeout(timer);
3173
- resolve8({ exitCode: -1, stderr: `${stderr}${e.message}` });
3399
+ resolve9({ exitCode: -1, stderr: `${stderr}${e.message}` });
3174
3400
  });
3175
3401
  child.on("close", (code) => {
3176
3402
  clearTimeout(timer);
3177
- resolve8({ exitCode: code ?? -1, stderr });
3403
+ resolve9({ exitCode: code ?? -1, stderr });
3178
3404
  });
3179
3405
  });
3180
3406
  }
@@ -3315,7 +3541,7 @@ ${newEntry}
3315
3541
  )
3316
3542
  };
3317
3543
  }
3318
- const vr = await new Promise((resolve8) => {
3544
+ const vr = await new Promise((resolve9) => {
3319
3545
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3320
3546
  let stderr = "";
3321
3547
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3323,15 +3549,15 @@ ${newEntry}
3323
3549
  });
3324
3550
  const timer = setTimeout(() => {
3325
3551
  child.kill("SIGKILL");
3326
- resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
3552
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3327
3553
  }, 15e3);
3328
3554
  child.on("error", (e) => {
3329
3555
  clearTimeout(timer);
3330
- resolve8({ exitCode: -1, stderr: e.message });
3556
+ resolve9({ exitCode: -1, stderr: e.message });
3331
3557
  });
3332
3558
  child.on("close", (code) => {
3333
3559
  clearTimeout(timer);
3334
- resolve8({ exitCode: code ?? -1, stderr });
3560
+ resolve9({ exitCode: code ?? -1, stderr });
3335
3561
  });
3336
3562
  });
3337
3563
  if (vr.exitCode !== 0) {
@@ -3387,10 +3613,10 @@ async function spawnCmd(ctx, cmd, args) {
3387
3613
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
3388
3614
  stderr += chunk;
3389
3615
  });
3390
- return await new Promise((resolve8) => {
3616
+ return await new Promise((resolve9) => {
3391
3617
  const timer = setTimeout(() => {
3392
3618
  child.kill("SIGKILL");
3393
- resolve8({
3619
+ resolve9({
3394
3620
  ok: false,
3395
3621
  phase: "spawn",
3396
3622
  error: {
@@ -3405,7 +3631,7 @@ async function spawnCmd(ctx, cmd, args) {
3405
3631
  }, timeoutMs);
3406
3632
  child.on("error", (err2) => {
3407
3633
  clearTimeout(timer);
3408
- resolve8({
3634
+ resolve9({
3409
3635
  ok: false,
3410
3636
  phase: "spawn",
3411
3637
  error: {
@@ -3420,14 +3646,14 @@ async function spawnCmd(ctx, cmd, args) {
3420
3646
  });
3421
3647
  child.on("close", (code) => {
3422
3648
  clearTimeout(timer);
3423
- resolve8({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3649
+ resolve9({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3424
3650
  });
3425
3651
  });
3426
3652
  }
3427
3653
 
3428
3654
  // src/installers/gitCloneWithSetup.ts
3429
3655
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
3430
- return new Promise((resolve8) => {
3656
+ return new Promise((resolve9) => {
3431
3657
  const isWin = process.platform === "win32";
3432
3658
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
3433
3659
  let stdout2 = "";
@@ -3436,15 +3662,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
3436
3662
  });
3437
3663
  const timer = setTimeout(() => {
3438
3664
  child.kill("SIGKILL");
3439
- resolve8({ sha: "", exit: -1 });
3665
+ resolve9({ sha: "", exit: -1 });
3440
3666
  }, timeoutMs);
3441
3667
  child.on("error", () => {
3442
3668
  clearTimeout(timer);
3443
- resolve8({ sha: "", exit: -1 });
3669
+ resolve9({ sha: "", exit: -1 });
3444
3670
  });
3445
3671
  child.on("close", (code) => {
3446
3672
  clearTimeout(timer);
3447
- resolve8({ sha: stdout2.trim(), exit: code ?? -1 });
3673
+ resolve9({ sha: stdout2.trim(), exit: code ?? -1 });
3448
3674
  });
3449
3675
  });
3450
3676
  }
@@ -3784,7 +4010,7 @@ ${newEntry}
3784
4010
  )
3785
4011
  };
3786
4012
  }
3787
- const vr = await new Promise((resolve8) => {
4013
+ const vr = await new Promise((resolve9) => {
3788
4014
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3789
4015
  let stderr = "";
3790
4016
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3792,15 +4018,15 @@ ${newEntry}
3792
4018
  });
3793
4019
  const timer = setTimeout(() => {
3794
4020
  child.kill("SIGKILL");
3795
- resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
4021
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3796
4022
  }, 15e3);
3797
4023
  child.on("error", (e) => {
3798
4024
  clearTimeout(timer);
3799
- resolve8({ exitCode: -1, stderr: e.message });
4025
+ resolve9({ exitCode: -1, stderr: e.message });
3800
4026
  });
3801
4027
  child.on("close", (code) => {
3802
4028
  clearTimeout(timer);
3803
- resolve8({ exitCode: code ?? -1, stderr });
4029
+ resolve9({ exitCode: code ?? -1, stderr });
3804
4030
  });
3805
4031
  });
3806
4032
  if (vr.exitCode !== 0) {
@@ -3932,7 +4158,7 @@ ${newEntry}
3932
4158
  )
3933
4159
  };
3934
4160
  }
3935
- const vr = await new Promise((resolve8) => {
4161
+ const vr = await new Promise((resolve9) => {
3936
4162
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3937
4163
  let stderr = "";
3938
4164
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3940,15 +4166,15 @@ ${newEntry}
3940
4166
  });
3941
4167
  const timer = setTimeout(() => {
3942
4168
  child.kill("SIGKILL");
3943
- resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
4169
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3944
4170
  }, 15e3);
3945
4171
  child.on("error", (e) => {
3946
4172
  clearTimeout(timer);
3947
- resolve8({ exitCode: -1, stderr: e.message });
4173
+ resolve9({ exitCode: -1, stderr: e.message });
3948
4174
  });
3949
4175
  child.on("close", (code) => {
3950
4176
  clearTimeout(timer);
3951
- resolve8({ exitCode: code ?? -1, stderr });
4177
+ resolve9({ exitCode: code ?? -1, stderr });
3952
4178
  });
3953
4179
  });
3954
4180
  if (vr.exitCode !== 0) {
@@ -4246,7 +4472,7 @@ function formatError(e) {
4246
4472
  return `${head}${where}${tip}`;
4247
4473
  }
4248
4474
  function registerInstall(program2) {
4249
- program2.command("install <name>").description("Install an upstream (dry-run by default \u2014 pass --apply to execute)").option("--apply", "execute the install (default: dry-run preview only)").option("--dry-run", "force dry-run (overrides --apply if both are set)").option("--system", "allow L4 system-wide install (e.g. global npm install)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--full-diff", "expand diffs longer than 200 lines").option("--no-color", "disable ANSI colors (auto-detected when piped)").option(
4475
+ program2.command("install <name>").description("Install an upstream (immediate by default \u2014 use --dry-run for preview)").option("--apply", "(deprecated; kept for backward compat \u2014 install is immediate by default)").option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--system", "allow L4 system-wide install (e.g. global npm install)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--full-diff", "expand diffs longer than 200 lines").option("--no-color", "disable ANSI colors (auto-detected when piped)").option(
4250
4476
  "--known-good",
4251
4477
  "use known-good version lock from versions/<harnessed-ver>-known-good.yaml"
4252
4478
  ).action(async (name, raw) => {
@@ -4278,9 +4504,10 @@ function registerInstall(program2) {
4278
4504
  console.error(` fix: run 'harnessed audit' to inspect manifest issues`);
4279
4505
  process.exit(1);
4280
4506
  }
4507
+ const dryRun = raw.dryRun === true;
4281
4508
  const opts = {
4282
- apply: raw.apply === true,
4283
- dryRun: raw.dryRun === true,
4509
+ apply: !dryRun,
4510
+ dryRun,
4284
4511
  system: raw.system === true,
4285
4512
  nonInteractive: raw.nonInteractive === true,
4286
4513
  fullDiff: raw.fullDiff === true,
@@ -4326,11 +4553,17 @@ async function listBaseManifests(cwd) {
4326
4553
  return out;
4327
4554
  }
4328
4555
  function registerInstallBase(program2) {
4329
- program2.command("install-base").description("Install the phase 1.3 base profile (auto-glob manifests; dry-run by default)").option("--apply", "execute the install (default: dry-run preview only)").option("--dry-run", "force dry-run (overrides --apply if both are set)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").action(async (raw) => {
4556
+ program2.command("install-base").description(
4557
+ "Install the phase 1.3 base profile (immediate by default \u2014 use --dry-run for preview)"
4558
+ ).option(
4559
+ "--apply",
4560
+ "(deprecated; kept for backward compat \u2014 install-base is immediate by default)"
4561
+ ).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").action(async (raw) => {
4330
4562
  validateNonInteractiveFlags(raw, "install-base");
4563
+ const dryRun = raw.dryRun === true;
4331
4564
  const opts = {
4332
- apply: raw.apply === true,
4333
- dryRun: raw.dryRun === true,
4565
+ apply: !dryRun,
4566
+ dryRun,
4334
4567
  system: false,
4335
4568
  nonInteractive: raw.nonInteractive === true,
4336
4569
  fullDiff: false,
@@ -4390,7 +4623,12 @@ function basename(upstream) {
4390
4623
  return (upstream.split("/").pop() ?? upstream).replace(/\.git$/, "");
4391
4624
  }
4392
4625
  function registerManifestAdd(program2) {
4393
- program2.command("manifest-add <upstream>").description("Add a new upstream adapter (EE-5 5-question merge gate, D-03 BOTH dry-run/apply)").option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option("--apply", "persist EE-5 answers (default: dry-run preview)").option("--dry-run", "force dry-run (overrides --apply if both set)").option("--non-interactive", "CI/scripts \u2014 requires --apply or --dry-run; WARN-only dry-run").action(async (upstream, raw) => {
4626
+ program2.command("manifest-add <upstream>").description(
4627
+ "Add a new upstream adapter (EE-5 5-question merge gate; immediate by default \u2014 use --dry-run for preview)"
4628
+ ).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option(
4629
+ "--apply",
4630
+ "(deprecated; kept for backward compat \u2014 manifest-add persists immediately by default)"
4631
+ ).option("--dry-run", "preview only \u2014 do not write JSON (opt-in for advanced users)").option("--non-interactive", "CI/scripts \u2014 requires --apply or --dry-run; WARN-only dry-run").action(async (upstream, raw) => {
4394
4632
  validateNonInteractiveFlags(raw, "manifest-add <upstream>");
4395
4633
  const name = raw.name ?? basename(upstream);
4396
4634
  const category = raw.category ?? "skill-packs";
@@ -4419,7 +4657,8 @@ function registerManifestAdd(program2) {
4419
4657
  payload[f] = a;
4420
4658
  }
4421
4659
  rl.close();
4422
- if (raw.apply) {
4660
+ const dryRun = raw.dryRun === true;
4661
+ if (!dryRun) {
4423
4662
  writeFileSync(outPath, `${JSON.stringify(payload, null, 2)}
4424
4663
  `, "utf8");
4425
4664
  console.log(`[manifest-add] EE-5 gate passed; wrote ${outPath}`);
@@ -4433,14 +4672,19 @@ function registerManifestAdd(program2) {
4433
4672
 
4434
4673
  // src/cli/research.ts
4435
4674
  function registerResearch(program2) {
4436
- program2.command("research").description("Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE)").requiredOption("--query <text>", "research prompt (required)").option("--apply", "execute the spawn (default: dry-run preview only)").option("--dry-run", "force dry-run (overrides --apply if both are set)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
4675
+ program2.command("research").description(
4676
+ "Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
4677
+ ).requiredOption("--query <text>", "research prompt (required)").option(
4678
+ "--apply",
4679
+ "(deprecated; kept for backward compat \u2014 research spawns immediately by default)"
4680
+ ).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
4437
4681
  validateNonInteractiveFlags(raw, "research --query <text>");
4438
4682
  if (!raw.query) {
4439
4683
  console.error("error: --query <text> is required");
4440
4684
  process.exit(2);
4441
4685
  }
4442
4686
  const taskCtx = { task: raw.query, task_type: "search" };
4443
- if (raw.dryRun === true || !raw.apply && !raw.nonInteractive) {
4687
+ if (raw.dryRun === true) {
4444
4688
  const preview = await runRouting(taskCtx, {
4445
4689
  skillsRoot: void 0,
4446
4690
  // Stub spawn — dry-run never reaches it; explicit COMPLETE keeps shape happy.
@@ -4458,7 +4702,9 @@ function registerResearch(program2) {
4458
4702
  }
4459
4703
  console.log(`[dry-run] matched_rule: ${preview.matchedRule?.id ?? "(fallback supervisor)"}`);
4460
4704
  console.log(`[dry-run] query: ${raw.query}`);
4461
- console.log(" (use --apply to spawn the subagent and emit verbatim COMPLETE round-trip)");
4705
+ console.log(
4706
+ " (run without --dry-run to spawn the subagent and emit verbatim COMPLETE round-trip)"
4707
+ );
4462
4708
  process.exit(0);
4463
4709
  }
4464
4710
  const result = await runRouting(taskCtx, {
@@ -4562,6 +4808,82 @@ function registerRollback(program2) {
4562
4808
  });
4563
4809
  }
4564
4810
  init_checkAgentTeams();
4811
+ var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
4812
+ var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro"]);
4813
+ var NON_WORKFLOW_DIRS = /* @__PURE__ */ new Set(["disciplines", "judgments"]);
4814
+ async function scanWorkflowsNested(workflowsDir, entries) {
4815
+ const workflows = [];
4816
+ const deprecated = [];
4817
+ for (const entry of entries.sort()) {
4818
+ if (NON_WORKFLOW_DIRS.has(entry)) continue;
4819
+ const src = join(workflowsDir, entry);
4820
+ let s;
4821
+ try {
4822
+ s = await stat(src);
4823
+ } catch {
4824
+ continue;
4825
+ }
4826
+ if (!s.isDirectory()) continue;
4827
+ let hasFlatSkill = false;
4828
+ try {
4829
+ await stat(join(src, "SKILL.md"));
4830
+ hasFlatSkill = true;
4831
+ } catch {
4832
+ hasFlatSkill = false;
4833
+ }
4834
+ if (hasFlatSkill) {
4835
+ if (FLAT_LEGACY_DEPRECATED.has(entry)) {
4836
+ deprecated.push(entry);
4837
+ continue;
4838
+ }
4839
+ if (FLAT_LEGACY_KEEP.has(entry)) {
4840
+ workflows.push({ name: entry, relPath: entry, isMaster: false });
4841
+ continue;
4842
+ }
4843
+ workflows.push({ name: entry, relPath: entry, isMaster: false });
4844
+ continue;
4845
+ }
4846
+ let subEntries;
4847
+ try {
4848
+ subEntries = await readdir(src);
4849
+ } catch {
4850
+ continue;
4851
+ }
4852
+ for (const sub of subEntries.sort()) {
4853
+ const subDir = join(src, sub);
4854
+ let ss;
4855
+ try {
4856
+ ss = await stat(subDir);
4857
+ } catch {
4858
+ continue;
4859
+ }
4860
+ if (!ss.isDirectory()) continue;
4861
+ try {
4862
+ await stat(join(subDir, "SKILL.md"));
4863
+ } catch {
4864
+ continue;
4865
+ }
4866
+ const name = sub === "auto" ? entry : `${entry}-${sub}`;
4867
+ workflows.push({ name, relPath: `${entry}/${sub}`, isMaster: sub === "auto" });
4868
+ }
4869
+ }
4870
+ return { workflows, deprecated };
4871
+ }
4872
+ function renderDeprecationBlock(deprecated) {
4873
+ if (deprecated.length === 0) return "";
4874
+ return [
4875
+ "\u26A0\uFE0F v3.0 BREAKING \u2014 v2 legacy slash cmd deprecated:",
4876
+ " /plan-feature \u2192 /plan (master) | /plan-phase (sub)",
4877
+ " /execute-task \u2192 /task (master) | /task-{clarify,code,test,deliver} (sub)",
4878
+ " /verify-work \u2192 /verify (master) | /verify-{progress,paranoid,qa,security,design,simplify,multispec} (sub)",
4879
+ " /research, /retro \u4E0D\u53D8",
4880
+ " \u8BE6\u89C1 CHANGELOG [3.0.0]",
4881
+ ` skipped install: ${deprecated.sort().join(", ")}`,
4882
+ ""
4883
+ ].join("\n");
4884
+ }
4885
+
4886
+ // src/cli/lib/setup-helpers.ts
4565
4887
  var PHASE_212 = /* @__PURE__ */ new Set([
4566
4888
  "cc-plugin-marketplace",
4567
4889
  "git-clone-with-setup",
@@ -4581,18 +4903,7 @@ async function warnIfAgentTeamsMissing() {
4581
4903
  );
4582
4904
  }
4583
4905
  async function scanWorkflowsWithSkill(workflowsDir, entries) {
4584
- const out = [];
4585
- for (const entry of entries.sort()) {
4586
- const src = join(workflowsDir, entry);
4587
- try {
4588
- const s = await stat(src);
4589
- if (!s.isDirectory()) continue;
4590
- await stat(join(src, "SKILL.md"));
4591
- out.push(entry);
4592
- } catch {
4593
- }
4594
- }
4595
- return out;
4906
+ return scanWorkflowsNested(workflowsDir, entries);
4596
4907
  }
4597
4908
  async function runStepBInstall(manifestPaths) {
4598
4909
  const opts = {
@@ -4678,7 +4989,12 @@ function registerSetup(program2) {
4678
4989
  console.error(`error: workflows directory not found at ${workflowsDir}`);
4679
4990
  process.exit(1);
4680
4991
  }
4681
- const toInstall = await scanWorkflowsWithSkill(workflowsDir, entries);
4992
+ const { workflows: toInstall, deprecated } = await scanWorkflowsWithSkill(
4993
+ workflowsDir,
4994
+ entries
4995
+ );
4996
+ const depBlock = renderDeprecationBlock(deprecated);
4997
+ if (depBlock) console.log(depBlock);
4682
4998
  if (toInstall.length === 0) {
4683
4999
  console.log("setup: no workflow directories with SKILL.md found \u2014 nothing to install");
4684
5000
  process.exit(2);
@@ -4687,22 +5003,24 @@ function registerSetup(program2) {
4687
5003
  console.log(
4688
5004
  `[dry-run] setup would install ${toInstall.length} workflow(s) to ${skillsBase}:`
4689
5005
  );
4690
- for (const name of toInstall) {
4691
- console.log(` ${name} \u2192 ${join(skillsBase, name)}`);
5006
+ for (const wf of toInstall) {
5007
+ const masterTag = wf.isMaster ? " (master)" : "";
5008
+ console.log(` ${wf.name} \u2192 ${join(skillsBase, wf.name)}${masterTag}`);
4692
5009
  }
4693
5010
  console.log(` run without --dry-run to execute`);
4694
5011
  process.exit(0);
4695
5012
  }
4696
5013
  let skillsInstalled = 0;
4697
- for (const name of toInstall) {
4698
- const src = join(workflowsDir, name);
4699
- const dst = join(skillsBase, name);
5014
+ for (const wf of toInstall) {
5015
+ const src = join(workflowsDir, wf.relPath);
5016
+ const dst = join(skillsBase, wf.name);
4700
5017
  try {
4701
5018
  await cp(src, dst, { recursive: true, force: true });
4702
- console.log(` [A] installed ${name} \u2192 ${dst}`);
5019
+ const masterTag = wf.isMaster ? " (master)" : "";
5020
+ console.log(` [A] installed ${wf.name} \u2192 ${dst}${masterTag}`);
4703
5021
  skillsInstalled++;
4704
5022
  } catch (e) {
4705
- console.error(` error: failed to copy ${name}: ${e.message}`);
5023
+ console.error(` error: failed to copy ${wf.name}: ${e.message}`);
4706
5024
  process.exit(1);
4707
5025
  }
4708
5026
  }
@@ -4963,7 +5281,7 @@ var uninstallNpmCli = async (ctx) => {
4963
5281
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
4964
5282
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
4965
5283
  const isWin = process.platform === "win32";
4966
- const result = await new Promise((resolve8) => {
5284
+ const result = await new Promise((resolve9) => {
4967
5285
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
4968
5286
  let stderr = "";
4969
5287
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -4971,15 +5289,15 @@ var uninstallNpmCli = async (ctx) => {
4971
5289
  });
4972
5290
  const timer = setTimeout(() => {
4973
5291
  child.kill("SIGKILL");
4974
- resolve8({ exitCode: -1, stderr: `${stderr}[timeout]` });
5292
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
4975
5293
  }, 3e4);
4976
5294
  child.on("error", (e) => {
4977
5295
  clearTimeout(timer);
4978
- resolve8({ exitCode: -1, stderr: e.message });
5296
+ resolve9({ exitCode: -1, stderr: e.message });
4979
5297
  });
4980
5298
  child.on("close", (code) => {
4981
5299
  clearTimeout(timer);
4982
- resolve8({ exitCode: code ?? -1, stderr });
5300
+ resolve9({ exitCode: code ?? -1, stderr });
4983
5301
  });
4984
5302
  });
4985
5303
  if (result.exitCode !== 0) {
@@ -5029,12 +5347,12 @@ async function runUninstall(manifest, opts) {
5029
5347
 
5030
5348
  // src/cli/uninstall.ts
5031
5349
  function registerUninstall(program2) {
5032
- program2.command("uninstall <name>").description("Uninstall an upstream (dry-run by default \u2014 pass --apply to execute)").option("--apply", "execute the uninstall (default: dry-run preview only)").option("--dry-run", "force dry-run (overrides --apply if both are set)").option("--yes", "skip interactive confirm \u2014 requires --apply (CI / scripts)").option("--non-interactive", "alias for --yes (CI compat)").action(async (name, raw) => {
5350
+ program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").option("--apply", "(deprecated; kept for backward compat \u2014 uninstall is immediate by default)").option("--dry-run", "preview only \u2014 do not delete files (opt-in for advanced users)").option("--yes", "skip interactive confirm (CI / scripts) \u2014 fatal with --dry-run").option("--non-interactive", "alias for --yes (CI compat)").action(async (name, raw) => {
5033
5351
  const yes = raw.yes === true || raw.nonInteractive === true;
5034
- if (yes && !raw.apply) {
5352
+ if (yes && raw.dryRun) {
5035
5353
  console.error(
5036
- `error: --yes requires --apply to execute
5037
- fix: harnessed uninstall ${name} --yes --apply`
5354
+ `error: --yes is incompatible with --dry-run (dry-run does not mutate)
5355
+ fix: harnessed uninstall ${name} --yes (immediate) OR harnessed uninstall ${name} --dry-run (preview)`
5038
5356
  );
5039
5357
  process.exit(2);
5040
5358
  }
@@ -5065,10 +5383,10 @@ function registerUninstall(program2) {
5065
5383
  process.exit(1);
5066
5384
  }
5067
5385
  const method = v.manifest.spec.install.method;
5068
- const dryRun = raw.dryRun === true || !raw.apply;
5386
+ const dryRun = raw.dryRun === true;
5069
5387
  if (dryRun) {
5070
5388
  console.log(`[dry-run] would uninstall '${resolvedName}' via method '${method}'`);
5071
- console.log(` run with --apply to execute`);
5389
+ console.log(` run without --dry-run to execute`);
5072
5390
  process.exit(2);
5073
5391
  }
5074
5392
  if (!yes) {