harnessed 2.0.0 → 3.0.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 (68) hide show
  1. package/README.md +263 -28
  2. package/dist/cli.mjs +486 -190
  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.0"};
796
+ version: "3.0.0"};
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((resolve11, 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
- resolve11(1);
1586
+ resolve9(1);
1579
1587
  } else {
1580
1588
  reject(err2);
1581
1589
  }
1582
1590
  });
1583
- child.on("close", (code) => resolve11(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
  });
@@ -1638,19 +1646,132 @@ function registerAuditLog(program2) {
1638
1646
  }
1639
1647
  );
1640
1648
  }
1649
+ function getBackupRoot() {
1650
+ return join(homedir(), ".harnessed", "backups");
1651
+ }
1652
+ var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
1653
+ function mirrorPath(target, scope, backupDir) {
1654
+ const root = scope === "HOME" ? HOME_DIR : ".";
1655
+ const rel = root ? relative(root, target) : target;
1656
+ if (!rel || rel.startsWith("..")) {
1657
+ const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
1658
+ return join(backupDir, scope, flat);
1659
+ }
1660
+ return join(backupDir, scope, rel);
1661
+ }
1662
+ function detectEol(buf) {
1663
+ return buf.includes("\r\n") ? "crlf" : "lf";
1664
+ }
1665
+ async function backup(plan, ctx) {
1666
+ const filename = ctx.manifest.metadata.name;
1667
+ const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
1668
+ const backupDir = join(getBackupRoot(), backupId);
1669
+ try {
1670
+ await mkdir(backupDir, { recursive: true });
1671
+ } catch (err2) {
1672
+ return {
1673
+ ok: false,
1674
+ error: {
1675
+ file: filename,
1676
+ path: "/",
1677
+ message: `failed to create backup dir ${backupDir}: ${err2.message}`,
1678
+ line: null,
1679
+ column: null,
1680
+ keyword: "backup-mkdir-failed"
1681
+ }
1682
+ };
1683
+ }
1684
+ const entries = [];
1685
+ for (const file of plan.files) {
1686
+ let buf;
1687
+ try {
1688
+ buf = await readFile(file.target);
1689
+ } catch (err2) {
1690
+ const code = err2.code;
1691
+ if (code === "ENOENT" && file.oldText === "") {
1692
+ entries.push({
1693
+ target: file.target,
1694
+ backup: "",
1695
+ // sentinel: no backup written; rollback should unlink target
1696
+ sha1: "",
1697
+ eol: "lf"
1698
+ // moot for non-existent file; default to lf
1699
+ });
1700
+ continue;
1701
+ }
1702
+ return {
1703
+ ok: false,
1704
+ error: {
1705
+ file: filename,
1706
+ path: file.target,
1707
+ message: `failed to read original file for backup: ${err2.message}`,
1708
+ line: null,
1709
+ column: null,
1710
+ keyword: "backup-read-failed"
1711
+ }
1712
+ };
1713
+ }
1714
+ const sha1 = createHash("sha1").update(buf).digest("hex");
1715
+ const eol = detectEol(buf);
1716
+ const dest = mirrorPath(file.target, file.scope, backupDir);
1717
+ try {
1718
+ await mkdir(dirname(dest), { recursive: true });
1719
+ await writeFile(dest, buf);
1720
+ } catch (err2) {
1721
+ return {
1722
+ ok: false,
1723
+ error: {
1724
+ file: filename,
1725
+ path: dest,
1726
+ message: `failed to write backup copy: ${err2.message}`,
1727
+ line: null,
1728
+ column: null,
1729
+ keyword: "backup-write-failed"
1730
+ }
1731
+ };
1732
+ }
1733
+ entries.push({ target: file.target, backup: dest, sha1, eol });
1734
+ }
1735
+ const metadata = {
1736
+ installer: filename,
1737
+ manifest: filename,
1738
+ timestamp: backupId,
1739
+ files: entries
1740
+ };
1741
+ const metadataPath = join(backupDir, "metadata.json");
1742
+ try {
1743
+ await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
1744
+ `, "utf8");
1745
+ } catch (err2) {
1746
+ return {
1747
+ ok: false,
1748
+ error: {
1749
+ file: filename,
1750
+ path: metadataPath,
1751
+ message: `failed to write metadata.json: ${err2.message}`,
1752
+ line: null,
1753
+ column: null,
1754
+ keyword: "backup-metadata-failed"
1755
+ }
1756
+ };
1757
+ }
1758
+ return { ok: true, backupId, backupDir };
1759
+ }
1760
+
1761
+ // src/cli/backup-list.ts
1641
1762
  function registerBackupList(program2) {
1642
1763
  const backup2 = program2.command("backup").description("Backup snapshot operations");
1643
1764
  backup2.command("list").description("List backup snapshots under .harnessed-backup/").action(async () => {
1644
- const root = resolve(process.cwd(), ".harnessed-backup");
1765
+ const root = getBackupRoot();
1645
1766
  let dirs;
1646
1767
  try {
1647
1768
  dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
1648
1769
  } catch {
1649
- console.log("no backups found (.harnessed-backup/ absent)");
1770
+ console.log(`no backups found (${root} absent)`);
1650
1771
  return;
1651
1772
  }
1652
1773
  if (dirs.length === 0) {
1653
- console.log("no backups found (.harnessed-backup/ empty)");
1774
+ console.log(`no backups found (${root} empty)`);
1654
1775
  return;
1655
1776
  }
1656
1777
  for (const ts of dirs) {
@@ -1819,6 +1940,107 @@ function registerDoctor(program2) {
1819
1940
  });
1820
1941
  }
1821
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
+
1822
2044
  // src/routing/completionSchema.ts
1823
2045
  var COMPLETION_SCHEMA = {
1824
2046
  type: "object",
@@ -2523,15 +2745,100 @@ var PhasesSchema = Type.Object(
2523
2745
 
2524
2746
  // src/workflow/schema/workflow.ts
2525
2747
  init_schemaVersion();
2748
+
2749
+ // src/workflow/schema/workflow.v2.ts
2750
+ init_schemaVersion();
2526
2751
  var ModelTier2 = Type.Union([Type.Literal("haiku"), Type.Literal("sonnet"), Type.Literal("opus")]);
2527
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
+ ]);
2528
2814
  var OnClause = Type.Object(
2529
2815
  {
2530
2816
  if: Type.String(),
2531
2817
  // expr-eval expression OR judgments.<file>.<gate>.fires ref
2532
2818
  invoke: Type.Optional(Type.String()),
2533
2819
  // '{{ capabilities.<name>.cmd }}' OR literal
2534
- 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)
2535
2842
  },
2536
2843
  { additionalProperties: false }
2537
2844
  );
@@ -2550,14 +2857,14 @@ var PhaseFallback = Type.Object(
2550
2857
  },
2551
2858
  { additionalProperties: false }
2552
2859
  );
2553
- var WorkflowPhaseV2 = Type.Object(
2860
+ var WorkflowPhaseV3 = Type.Object(
2554
2861
  {
2555
2862
  id: Type.String({ minLength: 1 }),
2556
2863
  name: Type.Optional(Type.String()),
2557
2864
  upstream: Type.Optional(Type.String()),
2558
2865
  capability: Type.Optional(Type.String()),
2559
2866
  // '{{ capabilities.ralph-loop.cmd }}'
2560
- model: Type.Optional(ModelTier2),
2867
+ model: Type.Optional(ModelTier3),
2561
2868
  invokes: Type.Optional(Type.String()),
2562
2869
  // legacy slash-cmd OR JINJA template
2563
2870
  args: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
@@ -2571,16 +2878,25 @@ var WorkflowPhaseV2 = Type.Object(
2571
2878
  Type.Union([Type.Number(), Type.String()])
2572
2879
  // numeric literal OR jinja '{{ defaults.x.y }}'
2573
2880
  ),
2574
- 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
2575
2884
  },
2576
2885
  { additionalProperties: false }
2577
2886
  );
2578
- var WorkflowSchemaV2 = Type.Object(
2887
+ Type.Object(
2579
2888
  {
2580
- schema_version: Type.Literal(SCHEMA_VERSIONS.workflow),
2889
+ schema_version: Type.Literal(SCHEMA_VERSIONS.workflow_v3),
2581
2890
  workflow: Type.String({ minLength: 1 }),
2582
2891
  description: Type.Optional(Type.String()),
2583
- 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 必有
2584
2900
  },
2585
2901
  { additionalProperties: false }
2586
2902
  );
@@ -2663,6 +2979,22 @@ function registerExecuteTask(program2) {
2663
2979
  break;
2664
2980
  }
2665
2981
  }
2982
+ try {
2983
+ const stagedOut = execSync("git status --porcelain", { encoding: "utf8" });
2984
+ const changedFiles = stagedOut.split("\n").filter((l) => l.trim().length > 0).map((l) => l.slice(3).trim());
2985
+ await runBeforeCommitHook({
2986
+ changedFiles,
2987
+ cmdArgs: [],
2988
+ packageRoot: process.cwd(),
2989
+ cmdType: "git-commit",
2990
+ hasUserApproval: true
2991
+ // --apply explicit
2992
+ });
2993
+ } catch (err2) {
2994
+ console.warn(
2995
+ `\u26A0\uFE0F before-commit pre-flight skipped (${err2.message}); subagent will biome-check at commit time.`
2996
+ );
2997
+ }
2666
2998
  const result = await runRouting(taskCtx, {
2667
2999
  maxIterations: raw.maxIterations ?? 20,
2668
3000
  ...raw.model ? { agentOpts: { modelOverride: raw.model } } : {},
@@ -2721,12 +3053,12 @@ function registerGc(program2) {
2721
3053
  return;
2722
3054
  }
2723
3055
  const keepLast = Number.parseInt(opts.keepLast ?? "0", 10);
2724
- const root = resolve(process.cwd(), ".harnessed-backup");
3056
+ const root = getBackupRoot();
2725
3057
  let dirs;
2726
3058
  try {
2727
3059
  dirs = (await readdir(root, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name).sort();
2728
3060
  } catch {
2729
- console.log("no backups found (.harnessed-backup/ absent) \u2014 nothing to gc");
3061
+ console.log(`no backups found (${root} absent) \u2014 nothing to gc`);
2730
3062
  return;
2731
3063
  }
2732
3064
  const cutoff = Date.now() - olderMs;
@@ -2763,114 +3095,6 @@ function registerGc(program2) {
2763
3095
  if (dryRun) console.log("\n(dry-run \u2014 re-run with --apply to actually delete)");
2764
3096
  });
2765
3097
  }
2766
- var HOME_DIR = process.env.HOME ?? process.env.USERPROFILE ?? "";
2767
- function mirrorPath(target, scope, backupDir) {
2768
- const root = scope === "HOME" ? HOME_DIR : ".";
2769
- const rel = root ? relative(root, target) : target;
2770
- if (!rel || rel.startsWith("..")) {
2771
- const flat = createHash("sha1").update(target).digest("hex").slice(0, 16);
2772
- return join(backupDir, scope, flat);
2773
- }
2774
- return join(backupDir, scope, rel);
2775
- }
2776
- function detectEol(buf) {
2777
- return buf.includes("\r\n") ? "crlf" : "lf";
2778
- }
2779
- async function backup(plan, ctx) {
2780
- const filename = ctx.manifest.metadata.name;
2781
- const backupId = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
2782
- const backupDir = join(ctx.cwd, ".harnessed-backup", backupId);
2783
- try {
2784
- await mkdir(backupDir, { recursive: true });
2785
- } catch (err2) {
2786
- return {
2787
- ok: false,
2788
- error: {
2789
- file: filename,
2790
- path: "/",
2791
- message: `failed to create backup dir ${backupDir}: ${err2.message}`,
2792
- line: null,
2793
- column: null,
2794
- keyword: "backup-mkdir-failed"
2795
- }
2796
- };
2797
- }
2798
- const entries = [];
2799
- for (const file of plan.files) {
2800
- let buf;
2801
- try {
2802
- buf = await readFile(file.target);
2803
- } catch (err2) {
2804
- const code = err2.code;
2805
- if (code === "ENOENT" && file.oldText === "") {
2806
- entries.push({
2807
- target: file.target,
2808
- backup: "",
2809
- // sentinel: no backup written; rollback should unlink target
2810
- sha1: "",
2811
- eol: "lf"
2812
- // moot for non-existent file; default to lf
2813
- });
2814
- continue;
2815
- }
2816
- return {
2817
- ok: false,
2818
- error: {
2819
- file: filename,
2820
- path: file.target,
2821
- message: `failed to read original file for backup: ${err2.message}`,
2822
- line: null,
2823
- column: null,
2824
- keyword: "backup-read-failed"
2825
- }
2826
- };
2827
- }
2828
- const sha1 = createHash("sha1").update(buf).digest("hex");
2829
- const eol = detectEol(buf);
2830
- const dest = mirrorPath(file.target, file.scope, backupDir);
2831
- try {
2832
- await mkdir(dirname(dest), { recursive: true });
2833
- await writeFile(dest, buf);
2834
- } catch (err2) {
2835
- return {
2836
- ok: false,
2837
- error: {
2838
- file: filename,
2839
- path: dest,
2840
- message: `failed to write backup copy: ${err2.message}`,
2841
- line: null,
2842
- column: null,
2843
- keyword: "backup-write-failed"
2844
- }
2845
- };
2846
- }
2847
- entries.push({ target: file.target, backup: dest, sha1, eol });
2848
- }
2849
- const metadata = {
2850
- installer: filename,
2851
- manifest: filename,
2852
- timestamp: backupId,
2853
- files: entries
2854
- };
2855
- const metadataPath = join(backupDir, "metadata.json");
2856
- try {
2857
- await writeFile(metadataPath, `${JSON.stringify(metadata, null, 2)}
2858
- `, "utf8");
2859
- } catch (err2) {
2860
- return {
2861
- ok: false,
2862
- error: {
2863
- file: filename,
2864
- path: metadataPath,
2865
- message: `failed to write metadata.json: ${err2.message}`,
2866
- line: null,
2867
- column: null,
2868
- keyword: "backup-metadata-failed"
2869
- }
2870
- };
2871
- }
2872
- return { ok: true, backupId, backupDir };
2873
- }
2874
3098
  async function confirmAt(level, ctx) {
2875
3099
  if (level === "L4" && !ctx.opts.system) {
2876
3100
  if (!ctx.opts.nonInteractive) {
@@ -3152,7 +3376,7 @@ var installCcHookAdd = async (ctx) => {
3152
3376
  return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath] };
3153
3377
  };
3154
3378
  function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3155
- return new Promise((resolve11) => {
3379
+ return new Promise((resolve9) => {
3156
3380
  const isWin = process.platform === "win32";
3157
3381
  const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
3158
3382
  let stderr = "";
@@ -3161,15 +3385,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
3161
3385
  });
3162
3386
  const timer = setTimeout(() => {
3163
3387
  child.kill("SIGKILL");
3164
- resolve11({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3388
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
3165
3389
  }, timeoutMs);
3166
3390
  child.on("error", (e) => {
3167
3391
  clearTimeout(timer);
3168
- resolve11({ exitCode: -1, stderr: `${stderr}${e.message}` });
3392
+ resolve9({ exitCode: -1, stderr: `${stderr}${e.message}` });
3169
3393
  });
3170
3394
  child.on("close", (code) => {
3171
3395
  clearTimeout(timer);
3172
- resolve11({ exitCode: code ?? -1, stderr });
3396
+ resolve9({ exitCode: code ?? -1, stderr });
3173
3397
  });
3174
3398
  });
3175
3399
  }
@@ -3310,7 +3534,7 @@ ${newEntry}
3310
3534
  )
3311
3535
  };
3312
3536
  }
3313
- const vr = await new Promise((resolve11) => {
3537
+ const vr = await new Promise((resolve9) => {
3314
3538
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3315
3539
  let stderr = "";
3316
3540
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3318,15 +3542,15 @@ ${newEntry}
3318
3542
  });
3319
3543
  const timer = setTimeout(() => {
3320
3544
  child.kill("SIGKILL");
3321
- resolve11({ exitCode: -1, stderr: `${stderr}[timeout]` });
3545
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3322
3546
  }, 15e3);
3323
3547
  child.on("error", (e) => {
3324
3548
  clearTimeout(timer);
3325
- resolve11({ exitCode: -1, stderr: e.message });
3549
+ resolve9({ exitCode: -1, stderr: e.message });
3326
3550
  });
3327
3551
  child.on("close", (code) => {
3328
3552
  clearTimeout(timer);
3329
- resolve11({ exitCode: code ?? -1, stderr });
3553
+ resolve9({ exitCode: code ?? -1, stderr });
3330
3554
  });
3331
3555
  });
3332
3556
  if (vr.exitCode !== 0) {
@@ -3382,10 +3606,10 @@ async function spawnCmd(ctx, cmd, args) {
3382
3606
  child.stderr?.setEncoding("utf8").on("data", (chunk) => {
3383
3607
  stderr += chunk;
3384
3608
  });
3385
- return await new Promise((resolve11) => {
3609
+ return await new Promise((resolve9) => {
3386
3610
  const timer = setTimeout(() => {
3387
3611
  child.kill("SIGKILL");
3388
- resolve11({
3612
+ resolve9({
3389
3613
  ok: false,
3390
3614
  phase: "spawn",
3391
3615
  error: {
@@ -3400,7 +3624,7 @@ async function spawnCmd(ctx, cmd, args) {
3400
3624
  }, timeoutMs);
3401
3625
  child.on("error", (err2) => {
3402
3626
  clearTimeout(timer);
3403
- resolve11({
3627
+ resolve9({
3404
3628
  ok: false,
3405
3629
  phase: "spawn",
3406
3630
  error: {
@@ -3415,14 +3639,14 @@ async function spawnCmd(ctx, cmd, args) {
3415
3639
  });
3416
3640
  child.on("close", (code) => {
3417
3641
  clearTimeout(timer);
3418
- resolve11({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3642
+ resolve9({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
3419
3643
  });
3420
3644
  });
3421
3645
  }
3422
3646
 
3423
3647
  // src/installers/gitCloneWithSetup.ts
3424
3648
  function gitRevParseHead(cwd, timeoutMs = 1e4) {
3425
- return new Promise((resolve11) => {
3649
+ return new Promise((resolve9) => {
3426
3650
  const isWin = process.platform === "win32";
3427
3651
  const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
3428
3652
  let stdout2 = "";
@@ -3431,15 +3655,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
3431
3655
  });
3432
3656
  const timer = setTimeout(() => {
3433
3657
  child.kill("SIGKILL");
3434
- resolve11({ sha: "", exit: -1 });
3658
+ resolve9({ sha: "", exit: -1 });
3435
3659
  }, timeoutMs);
3436
3660
  child.on("error", () => {
3437
3661
  clearTimeout(timer);
3438
- resolve11({ sha: "", exit: -1 });
3662
+ resolve9({ sha: "", exit: -1 });
3439
3663
  });
3440
3664
  child.on("close", (code) => {
3441
3665
  clearTimeout(timer);
3442
- resolve11({ sha: stdout2.trim(), exit: code ?? -1 });
3666
+ resolve9({ sha: stdout2.trim(), exit: code ?? -1 });
3443
3667
  });
3444
3668
  });
3445
3669
  }
@@ -3779,7 +4003,7 @@ ${newEntry}
3779
4003
  )
3780
4004
  };
3781
4005
  }
3782
- const vr = await new Promise((resolve11) => {
4006
+ const vr = await new Promise((resolve9) => {
3783
4007
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3784
4008
  let stderr = "";
3785
4009
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3787,15 +4011,15 @@ ${newEntry}
3787
4011
  });
3788
4012
  const timer = setTimeout(() => {
3789
4013
  child.kill("SIGKILL");
3790
- resolve11({ exitCode: -1, stderr: `${stderr}[timeout]` });
4014
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3791
4015
  }, 15e3);
3792
4016
  child.on("error", (e) => {
3793
4017
  clearTimeout(timer);
3794
- resolve11({ exitCode: -1, stderr: e.message });
4018
+ resolve9({ exitCode: -1, stderr: e.message });
3795
4019
  });
3796
4020
  child.on("close", (code) => {
3797
4021
  clearTimeout(timer);
3798
- resolve11({ exitCode: code ?? -1, stderr });
4022
+ resolve9({ exitCode: code ?? -1, stderr });
3799
4023
  });
3800
4024
  });
3801
4025
  if (vr.exitCode !== 0) {
@@ -3927,7 +4151,7 @@ ${newEntry}
3927
4151
  )
3928
4152
  };
3929
4153
  }
3930
- const vr = await new Promise((resolve11) => {
4154
+ const vr = await new Promise((resolve9) => {
3931
4155
  const child = spawn(verifyShell, [verifyFlag, verifyLine], { cwd: ctx.cwd, windowsHide: true });
3932
4156
  let stderr = "";
3933
4157
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -3935,15 +4159,15 @@ ${newEntry}
3935
4159
  });
3936
4160
  const timer = setTimeout(() => {
3937
4161
  child.kill("SIGKILL");
3938
- resolve11({ exitCode: -1, stderr: `${stderr}[timeout]` });
4162
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
3939
4163
  }, 15e3);
3940
4164
  child.on("error", (e) => {
3941
4165
  clearTimeout(timer);
3942
- resolve11({ exitCode: -1, stderr: e.message });
4166
+ resolve9({ exitCode: -1, stderr: e.message });
3943
4167
  });
3944
4168
  child.on("close", (code) => {
3945
4169
  clearTimeout(timer);
3946
- resolve11({ exitCode: code ?? -1, stderr });
4170
+ resolve9({ exitCode: code ?? -1, stderr });
3947
4171
  });
3948
4172
  });
3949
4173
  if (vr.exitCode !== 0) {
@@ -4515,7 +4739,7 @@ function normalizeEol(buf, eol) {
4515
4739
  }
4516
4740
  function registerRollback(program2) {
4517
4741
  program2.command("rollback <timestamp>").description("Restore files from a backup snapshot (preserves original LF/CRLF)").action(async (timestamp) => {
4518
- const dir = resolve(process.cwd(), ".harnessed-backup", timestamp);
4742
+ const dir = join(getBackupRoot(), timestamp);
4519
4743
  const metaPath = join(dir, "metadata.json");
4520
4744
  let meta;
4521
4745
  try {
@@ -4557,6 +4781,82 @@ function registerRollback(program2) {
4557
4781
  });
4558
4782
  }
4559
4783
  init_checkAgentTeams();
4784
+ var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
4785
+ var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro"]);
4786
+ var NON_WORKFLOW_DIRS = /* @__PURE__ */ new Set(["disciplines", "judgments"]);
4787
+ async function scanWorkflowsNested(workflowsDir, entries) {
4788
+ const workflows = [];
4789
+ const deprecated = [];
4790
+ for (const entry of entries.sort()) {
4791
+ if (NON_WORKFLOW_DIRS.has(entry)) continue;
4792
+ const src = join(workflowsDir, entry);
4793
+ let s;
4794
+ try {
4795
+ s = await stat(src);
4796
+ } catch {
4797
+ continue;
4798
+ }
4799
+ if (!s.isDirectory()) continue;
4800
+ let hasFlatSkill = false;
4801
+ try {
4802
+ await stat(join(src, "SKILL.md"));
4803
+ hasFlatSkill = true;
4804
+ } catch {
4805
+ hasFlatSkill = false;
4806
+ }
4807
+ if (hasFlatSkill) {
4808
+ if (FLAT_LEGACY_DEPRECATED.has(entry)) {
4809
+ deprecated.push(entry);
4810
+ continue;
4811
+ }
4812
+ if (FLAT_LEGACY_KEEP.has(entry)) {
4813
+ workflows.push({ name: entry, relPath: entry, isMaster: false });
4814
+ continue;
4815
+ }
4816
+ workflows.push({ name: entry, relPath: entry, isMaster: false });
4817
+ continue;
4818
+ }
4819
+ let subEntries;
4820
+ try {
4821
+ subEntries = await readdir(src);
4822
+ } catch {
4823
+ continue;
4824
+ }
4825
+ for (const sub of subEntries.sort()) {
4826
+ const subDir = join(src, sub);
4827
+ let ss;
4828
+ try {
4829
+ ss = await stat(subDir);
4830
+ } catch {
4831
+ continue;
4832
+ }
4833
+ if (!ss.isDirectory()) continue;
4834
+ try {
4835
+ await stat(join(subDir, "SKILL.md"));
4836
+ } catch {
4837
+ continue;
4838
+ }
4839
+ const name = sub === "auto" ? entry : `${entry}-${sub}`;
4840
+ workflows.push({ name, relPath: `${entry}/${sub}`, isMaster: sub === "auto" });
4841
+ }
4842
+ }
4843
+ return { workflows, deprecated };
4844
+ }
4845
+ function renderDeprecationBlock(deprecated) {
4846
+ if (deprecated.length === 0) return "";
4847
+ return [
4848
+ "\u26A0\uFE0F v3.0 BREAKING \u2014 v2 legacy slash cmd deprecated:",
4849
+ " /plan-feature \u2192 /plan (master) | /plan-phase (sub)",
4850
+ " /execute-task \u2192 /task (master) | /task-{clarify,code,test,deliver} (sub)",
4851
+ " /verify-work \u2192 /verify (master) | /verify-{progress,paranoid,qa,security,design,simplify,multispec} (sub)",
4852
+ " /research, /retro \u4E0D\u53D8",
4853
+ " \u8BE6\u89C1 CHANGELOG [3.0.0]",
4854
+ ` skipped install: ${deprecated.sort().join(", ")}`,
4855
+ ""
4856
+ ].join("\n");
4857
+ }
4858
+
4859
+ // src/cli/lib/setup-helpers.ts
4560
4860
  var PHASE_212 = /* @__PURE__ */ new Set([
4561
4861
  "cc-plugin-marketplace",
4562
4862
  "git-clone-with-setup",
@@ -4576,18 +4876,7 @@ async function warnIfAgentTeamsMissing() {
4576
4876
  );
4577
4877
  }
4578
4878
  async function scanWorkflowsWithSkill(workflowsDir, entries) {
4579
- const out = [];
4580
- for (const entry of entries.sort()) {
4581
- const src = join(workflowsDir, entry);
4582
- try {
4583
- const s = await stat(src);
4584
- if (!s.isDirectory()) continue;
4585
- await stat(join(src, "SKILL.md"));
4586
- out.push(entry);
4587
- } catch {
4588
- }
4589
- }
4590
- return out;
4879
+ return scanWorkflowsNested(workflowsDir, entries);
4591
4880
  }
4592
4881
  async function runStepBInstall(manifestPaths) {
4593
4882
  const opts = {
@@ -4673,7 +4962,12 @@ function registerSetup(program2) {
4673
4962
  console.error(`error: workflows directory not found at ${workflowsDir}`);
4674
4963
  process.exit(1);
4675
4964
  }
4676
- const toInstall = await scanWorkflowsWithSkill(workflowsDir, entries);
4965
+ const { workflows: toInstall, deprecated } = await scanWorkflowsWithSkill(
4966
+ workflowsDir,
4967
+ entries
4968
+ );
4969
+ const depBlock = renderDeprecationBlock(deprecated);
4970
+ if (depBlock) console.log(depBlock);
4677
4971
  if (toInstall.length === 0) {
4678
4972
  console.log("setup: no workflow directories with SKILL.md found \u2014 nothing to install");
4679
4973
  process.exit(2);
@@ -4682,22 +4976,24 @@ function registerSetup(program2) {
4682
4976
  console.log(
4683
4977
  `[dry-run] setup would install ${toInstall.length} workflow(s) to ${skillsBase}:`
4684
4978
  );
4685
- for (const name of toInstall) {
4686
- console.log(` ${name} \u2192 ${join(skillsBase, name)}`);
4979
+ for (const wf of toInstall) {
4980
+ const masterTag = wf.isMaster ? " (master)" : "";
4981
+ console.log(` ${wf.name} \u2192 ${join(skillsBase, wf.name)}${masterTag}`);
4687
4982
  }
4688
4983
  console.log(` run without --dry-run to execute`);
4689
4984
  process.exit(0);
4690
4985
  }
4691
4986
  let skillsInstalled = 0;
4692
- for (const name of toInstall) {
4693
- const src = join(workflowsDir, name);
4694
- const dst = join(skillsBase, name);
4987
+ for (const wf of toInstall) {
4988
+ const src = join(workflowsDir, wf.relPath);
4989
+ const dst = join(skillsBase, wf.name);
4695
4990
  try {
4696
4991
  await cp(src, dst, { recursive: true, force: true });
4697
- console.log(` [A] installed ${name} \u2192 ${dst}`);
4992
+ const masterTag = wf.isMaster ? " (master)" : "";
4993
+ console.log(` [A] installed ${wf.name} \u2192 ${dst}${masterTag}`);
4698
4994
  skillsInstalled++;
4699
4995
  } catch (e) {
4700
- console.error(` error: failed to copy ${name}: ${e.message}`);
4996
+ console.error(` error: failed to copy ${wf.name}: ${e.message}`);
4701
4997
  process.exit(1);
4702
4998
  }
4703
4999
  }
@@ -4958,7 +5254,7 @@ var uninstallNpmCli = async (ctx) => {
4958
5254
  const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
4959
5255
  const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
4960
5256
  const isWin = process.platform === "win32";
4961
- const result = await new Promise((resolve11) => {
5257
+ const result = await new Promise((resolve9) => {
4962
5258
  const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
4963
5259
  let stderr = "";
4964
5260
  child.stderr?.setEncoding("utf8").on("data", (c) => {
@@ -4966,15 +5262,15 @@ var uninstallNpmCli = async (ctx) => {
4966
5262
  });
4967
5263
  const timer = setTimeout(() => {
4968
5264
  child.kill("SIGKILL");
4969
- resolve11({ exitCode: -1, stderr: `${stderr}[timeout]` });
5265
+ resolve9({ exitCode: -1, stderr: `${stderr}[timeout]` });
4970
5266
  }, 3e4);
4971
5267
  child.on("error", (e) => {
4972
5268
  clearTimeout(timer);
4973
- resolve11({ exitCode: -1, stderr: e.message });
5269
+ resolve9({ exitCode: -1, stderr: e.message });
4974
5270
  });
4975
5271
  child.on("close", (code) => {
4976
5272
  clearTimeout(timer);
4977
- resolve11({ exitCode: code ?? -1, stderr });
5273
+ resolve9({ exitCode: code ?? -1, stderr });
4978
5274
  });
4979
5275
  });
4980
5276
  if (result.exitCode !== 0) {