@deftai/directive 0.55.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 (261) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.js +5 -0
  3. package/dist/branch-parity.d.ts +47 -0
  4. package/dist/branch-parity.js +263 -0
  5. package/dist/cache-parity.d.ts +36 -0
  6. package/dist/cache-parity.js +165 -0
  7. package/dist/cache.d.ts +4 -0
  8. package/dist/cache.js +10 -0
  9. package/dist/capacity-backfill.d.ts +3 -0
  10. package/dist/capacity-backfill.js +9 -0
  11. package/dist/capacity-show.d.ts +3 -0
  12. package/dist/capacity-show.js +9 -0
  13. package/dist/check.d.ts +10 -0
  14. package/dist/check.js +60 -0
  15. package/dist/cli-router/index.d.ts +8 -0
  16. package/dist/cli-router/index.js +37 -0
  17. package/dist/cli-router/route-argv.d.ts +32 -0
  18. package/dist/cli-router/route-argv.js +226 -0
  19. package/dist/codebase-default-extractor.d.ts +3 -0
  20. package/dist/codebase-default-extractor.js +9 -0
  21. package/dist/codebase-map-fresh.d.ts +3 -0
  22. package/dist/codebase-map-fresh.js +9 -0
  23. package/dist/codebase-map.d.ts +3 -0
  24. package/dist/codebase-map.js +9 -0
  25. package/dist/codebase-parity.d.ts +30 -0
  26. package/dist/codebase-parity.js +294 -0
  27. package/dist/codebase-projection-registry.d.ts +3 -0
  28. package/dist/codebase-projection-registry.js +9 -0
  29. package/dist/codebase-provider.d.ts +3 -0
  30. package/dist/codebase-provider.js +9 -0
  31. package/dist/content-validate-cli/validate-links.d.ts +11 -0
  32. package/dist/content-validate-cli/validate-links.js +37 -0
  33. package/dist/content-validate-cli/validate-strategy-output.d.ts +13 -0
  34. package/dist/content-validate-cli/validate-strategy-output.js +56 -0
  35. package/dist/content-validate-cli/verify-capacity.d.ts +12 -0
  36. package/dist/content-validate-cli/verify-capacity.js +54 -0
  37. package/dist/dispatch.d.ts +26 -0
  38. package/dist/dispatch.js +569 -0
  39. package/dist/doctor-parity.d.ts +38 -0
  40. package/dist/doctor-parity.js +151 -0
  41. package/dist/doctor.d.ts +3 -0
  42. package/dist/doctor.js +10 -0
  43. package/dist/gates-cli/_helpers.d.ts +18 -0
  44. package/dist/gates-cli/_helpers.js +71 -0
  45. package/dist/index.d.ts +11 -0
  46. package/dist/index.js +15 -0
  47. package/dist/init-cli/constants.d.ts +5 -0
  48. package/dist/init-cli/constants.js +5 -0
  49. package/dist/init-cli/init.d.ts +3 -0
  50. package/dist/init-cli/init.js +11 -0
  51. package/dist/init-cli/resolve-binary.d.ts +34 -0
  52. package/dist/init-cli/resolve-binary.js +78 -0
  53. package/dist/init-cli/run-deft-install.d.ts +23 -0
  54. package/dist/init-cli/run-deft-install.js +71 -0
  55. package/dist/init-cli/update.d.ts +3 -0
  56. package/dist/init-cli/update.js +11 -0
  57. package/dist/install-cli/coverage-map.d.ts +14 -0
  58. package/dist/install-cli/coverage-map.js +111 -0
  59. package/dist/intake-parity.d.ts +30 -0
  60. package/dist/intake-parity.js +203 -0
  61. package/dist/lifecycle-cli/coverage-map.d.ts +16 -0
  62. package/dist/lifecycle-cli/coverage-map.js +254 -0
  63. package/dist/lifecycle-cli/helpers.d.ts +8 -0
  64. package/dist/lifecycle-cli/helpers.js +20 -0
  65. package/dist/lifecycle-packs-parity.d.ts +30 -0
  66. package/dist/lifecycle-packs-parity.js +377 -0
  67. package/dist/orchestration-cli/coverage-map.d.ts +21 -0
  68. package/dist/orchestration-cli/coverage-map.js +225 -0
  69. package/dist/orchestration-cli/helpers.d.ts +11 -0
  70. package/dist/orchestration-cli/helpers.js +35 -0
  71. package/dist/orchestration-parity.d.ts +38 -0
  72. package/dist/orchestration-parity.js +364 -0
  73. package/dist/parity.d.ts +36 -0
  74. package/dist/parity.js +176 -0
  75. package/dist/platform-parity.d.ts +26 -0
  76. package/dist/platform-parity.js +309 -0
  77. package/dist/policy-parity.d.ts +35 -0
  78. package/dist/policy-parity.js +214 -0
  79. package/dist/policy.d.ts +27 -0
  80. package/dist/policy.js +316 -0
  81. package/dist/pr-closing-keywords-parity.d.ts +45 -0
  82. package/dist/pr-closing-keywords-parity.js +259 -0
  83. package/dist/pr-closing-keywords.d.ts +3 -0
  84. package/dist/pr-closing-keywords.js +10 -0
  85. package/dist/pr-merge-readiness-parity.d.ts +44 -0
  86. package/dist/pr-merge-readiness-parity.js +296 -0
  87. package/dist/pr-merge-readiness.d.ts +3 -0
  88. package/dist/pr-merge-readiness.js +10 -0
  89. package/dist/pr-monitor-parity.d.ts +44 -0
  90. package/dist/pr-monitor-parity.js +283 -0
  91. package/dist/pr-monitor.d.ts +3 -0
  92. package/dist/pr-monitor.js +10 -0
  93. package/dist/pr-protected-issues-parity.d.ts +41 -0
  94. package/dist/pr-protected-issues-parity.js +220 -0
  95. package/dist/pr-protected-issues.d.ts +3 -0
  96. package/dist/pr-protected-issues.js +10 -0
  97. package/dist/pr-wait-mergeable-parity.d.ts +45 -0
  98. package/dist/pr-wait-mergeable-parity.js +340 -0
  99. package/dist/pr-wait-mergeable.d.ts +3 -0
  100. package/dist/pr-wait-mergeable.js +10 -0
  101. package/dist/preflight-cache.d.ts +16 -0
  102. package/dist/preflight-cache.js +106 -0
  103. package/dist/preflight-gh.d.ts +12 -0
  104. package/dist/preflight-gh.js +188 -0
  105. package/dist/probe-session.d.ts +3 -0
  106. package/dist/probe-session.js +10 -0
  107. package/dist/release-e2e-parity.d.ts +31 -0
  108. package/dist/release-e2e-parity.js +114 -0
  109. package/dist/release-e2e.d.ts +3 -0
  110. package/dist/release-e2e.js +10 -0
  111. package/dist/release-parity.d.ts +40 -0
  112. package/dist/release-parity.js +223 -0
  113. package/dist/release-publish-parity.d.ts +36 -0
  114. package/dist/release-publish-parity.js +138 -0
  115. package/dist/release-publish.d.ts +3 -0
  116. package/dist/release-publish.js +10 -0
  117. package/dist/release-rollback-parity.d.ts +37 -0
  118. package/dist/release-rollback-parity.js +161 -0
  119. package/dist/release-rollback.d.ts +3 -0
  120. package/dist/release-rollback.js +10 -0
  121. package/dist/release.d.ts +3 -0
  122. package/dist/release.js +10 -0
  123. package/dist/render-cli/deft-ts-runner.d.ts +20 -0
  124. package/dist/render-cli/deft-ts-runner.js +35 -0
  125. package/dist/render-cli/prd-render-cli.d.ts +2 -0
  126. package/dist/render-cli/prd-render-cli.js +7 -0
  127. package/dist/render-cli/project-render-cli.d.ts +2 -0
  128. package/dist/render-cli/project-render-cli.js +6 -0
  129. package/dist/render-cli/spec-render-cli.d.ts +2 -0
  130. package/dist/render-cli/spec-render-cli.js +6 -0
  131. package/dist/render-cli/spec-validate-cli.d.ts +2 -0
  132. package/dist/render-cli/spec-validate-cli.js +6 -0
  133. package/dist/render-parity.d.ts +35 -0
  134. package/dist/render-parity.js +383 -0
  135. package/dist/scm-parity.d.ts +39 -0
  136. package/dist/scm-parity.js +181 -0
  137. package/dist/scope-lifecycle-parity.d.ts +35 -0
  138. package/dist/scope-lifecycle-parity.js +177 -0
  139. package/dist/scope-lifecycle.d.ts +4 -0
  140. package/dist/scope-lifecycle.js +29 -0
  141. package/dist/session-parity.d.ts +39 -0
  142. package/dist/session-parity.js +260 -0
  143. package/dist/slice-parity.d.ts +36 -0
  144. package/dist/slice-parity.js +304 -0
  145. package/dist/slice.d.ts +4 -0
  146. package/dist/slice.js +29 -0
  147. package/dist/story-ready-parity.d.ts +49 -0
  148. package/dist/story-ready-parity.js +254 -0
  149. package/dist/subagent-monitor.d.ts +3 -0
  150. package/dist/subagent-monitor.js +10 -0
  151. package/dist/swarm-parity.d.ts +28 -0
  152. package/dist/swarm-parity.js +326 -0
  153. package/dist/toolchain-check.d.ts +3 -0
  154. package/dist/toolchain-check.js +14 -0
  155. package/dist/triage-actions-parity.d.ts +33 -0
  156. package/dist/triage-actions-parity.js +201 -0
  157. package/dist/triage-actions.d.ts +17 -0
  158. package/dist/triage-actions.js +144 -0
  159. package/dist/triage-aux-a-parity.d.ts +35 -0
  160. package/dist/triage-aux-a-parity.js +172 -0
  161. package/dist/triage-aux-b-parity.d.ts +32 -0
  162. package/dist/triage-aux-b-parity.js +308 -0
  163. package/dist/triage-bootstrap-parity.d.ts +44 -0
  164. package/dist/triage-bootstrap-parity.js +226 -0
  165. package/dist/triage-bootstrap.d.ts +24 -0
  166. package/dist/triage-bootstrap.js +172 -0
  167. package/dist/triage-bulk.d.ts +17 -0
  168. package/dist/triage-bulk.js +154 -0
  169. package/dist/triage-classify-parity.d.ts +40 -0
  170. package/dist/triage-classify-parity.js +227 -0
  171. package/dist/triage-classify.d.ts +13 -0
  172. package/dist/triage-classify.js +76 -0
  173. package/dist/triage-help.d.ts +3 -0
  174. package/dist/triage-help.js +10 -0
  175. package/dist/triage-queue-parity.d.ts +59 -0
  176. package/dist/triage-queue-parity.js +363 -0
  177. package/dist/triage-queue.d.ts +17 -0
  178. package/dist/triage-queue.js +174 -0
  179. package/dist/triage-reconcile.d.ts +11 -0
  180. package/dist/triage-reconcile.js +76 -0
  181. package/dist/triage-refresh.d.ts +7 -0
  182. package/dist/triage-refresh.js +37 -0
  183. package/dist/triage-scope-drift.d.ts +12 -0
  184. package/dist/triage-scope-drift.js +117 -0
  185. package/dist/triage-scope-parity.d.ts +34 -0
  186. package/dist/triage-scope-parity.js +218 -0
  187. package/dist/triage-scope.d.ts +4 -0
  188. package/dist/triage-scope.js +29 -0
  189. package/dist/triage-smoketest.d.ts +3 -0
  190. package/dist/triage-smoketest.js +43 -0
  191. package/dist/triage-subscribe.d.ts +15 -0
  192. package/dist/triage-subscribe.js +157 -0
  193. package/dist/triage-summary-parity.d.ts +50 -0
  194. package/dist/triage-summary-parity.js +306 -0
  195. package/dist/triage-summary.d.ts +14 -0
  196. package/dist/triage-summary.js +77 -0
  197. package/dist/triage-welcome.d.ts +12 -0
  198. package/dist/triage-welcome.js +78 -0
  199. package/dist/ts-check-lane.d.ts +9 -0
  200. package/dist/ts-check-lane.js +49 -0
  201. package/dist/validate-content-parity.d.ts +33 -0
  202. package/dist/validate-content-parity.js +356 -0
  203. package/dist/vbrief-activate-parity.d.ts +39 -0
  204. package/dist/vbrief-activate-parity.js +216 -0
  205. package/dist/vbrief-activate.d.ts +3 -0
  206. package/dist/vbrief-activate.js +7 -0
  207. package/dist/vbrief-build-parity.d.ts +28 -0
  208. package/dist/vbrief-build-parity.js +399 -0
  209. package/dist/vbrief-build.d.ts +3 -0
  210. package/dist/vbrief-build.js +8 -0
  211. package/dist/vbrief-preflight-parity.d.ts +34 -0
  212. package/dist/vbrief-preflight-parity.js +163 -0
  213. package/dist/vbrief-preflight.d.ts +12 -0
  214. package/dist/vbrief-preflight.js +58 -0
  215. package/dist/vbrief-reconcile-parity.d.ts +22 -0
  216. package/dist/vbrief-reconcile-parity.js +600 -0
  217. package/dist/vbrief-reconcile.d.ts +3 -0
  218. package/dist/vbrief-reconcile.js +7 -0
  219. package/dist/vbrief-validate-parity.d.ts +27 -0
  220. package/dist/vbrief-validate-parity.js +122 -0
  221. package/dist/vbrief-validate.d.ts +3 -0
  222. package/dist/vbrief-validate.js +8 -0
  223. package/dist/vbrief-validation-parity.d.ts +28 -0
  224. package/dist/vbrief-validation-parity.js +645 -0
  225. package/dist/vbrief-validation.d.ts +3 -0
  226. package/dist/vbrief-validation.js +8 -0
  227. package/dist/verify-branch.d.ts +14 -0
  228. package/dist/verify-branch.js +81 -0
  229. package/dist/verify-encoding.d.ts +15 -0
  230. package/dist/verify-encoding.js +86 -0
  231. package/dist/verify-env-parity.d.ts +28 -0
  232. package/dist/verify-env-parity.js +272 -0
  233. package/dist/verify-hooks-installed.d.ts +10 -0
  234. package/dist/verify-hooks-installed.js +48 -0
  235. package/dist/verify-investigation.d.ts +3 -0
  236. package/dist/verify-investigation.js +10 -0
  237. package/dist/verify-judgment-gates.d.ts +3 -0
  238. package/dist/verify-judgment-gates.js +10 -0
  239. package/dist/verify-no-task-runtime.d.ts +3 -0
  240. package/dist/verify-no-task-runtime.js +16 -0
  241. package/dist/verify-session-ritual.d.ts +13 -0
  242. package/dist/verify-session-ritual.js +78 -0
  243. package/dist/verify-source-cli/rule-ownership-lint.d.ts +12 -0
  244. package/dist/verify-source-cli/rule-ownership-lint.js +61 -0
  245. package/dist/verify-source-cli/verify-content-manifest.d.ts +12 -0
  246. package/dist/verify-source-cli/verify-content-manifest.js +61 -0
  247. package/dist/verify-source-cli/verify-scm-boundary.d.ts +13 -0
  248. package/dist/verify-source-cli/verify-scm-boundary.js +66 -0
  249. package/dist/verify-source-cli/verify-stubs.d.ts +11 -0
  250. package/dist/verify-source-cli/verify-stubs.js +27 -0
  251. package/dist/verify-source-parity.d.ts +26 -0
  252. package/dist/verify-source-parity.js +178 -0
  253. package/dist/verify-story-ready.d.ts +16 -0
  254. package/dist/verify-story-ready.js +138 -0
  255. package/dist/verify-tools.d.ts +13 -0
  256. package/dist/verify-tools.js +67 -0
  257. package/dist/verify-wip-cap.d.ts +13 -0
  258. package/dist/verify-wip-cap.js +62 -0
  259. package/dist/wip-cap-parity.d.ts +39 -0
  260. package/dist/wip-cap-parity.js +209 -0
  261. package/package.json +50 -0
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Golden-output parity harness (#1787 s1): runs BOTH the Python oracle
4
+ * (`scripts/verify_session_ritual.py`) and the ported TS session core over
5
+ * shared fixtures (cache-off) and diffs JSON stdout + exit codes.
6
+ */
7
+ import { execFileSync, spawnSync } from "node:child_process";
8
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { dirname, join, resolve } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { emitVerifyJson, newRitualStatePayload, ritualStep, verifySessionRitual, writeRitualState, } from "@deftai/directive-core/session";
13
+ const FIXED_NOW = "2026-06-09T01:00:00Z";
14
+ function initGitRepo(policy = { sessionRitualStalenessHours: 4 }) {
15
+ const root = mkdtempSync(join(tmpdir(), "deft-session-parity-"));
16
+ writeFileSync(join(root, "README.md"), "fixture\n", "utf8");
17
+ mkdirSync(join(root, "vbrief"), { recursive: true });
18
+ writeFileSync(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), JSON.stringify({
19
+ vBRIEFInfo: { version: "0.6" },
20
+ plan: { title: "T", status: "running", items: [], policy },
21
+ }), "utf8");
22
+ execFileSync("git", ["init", "-q"], { cwd: root, encoding: "utf8" });
23
+ execFileSync("git", ["config", "user.email", "parity@test.local"], {
24
+ cwd: root,
25
+ encoding: "utf8",
26
+ });
27
+ execFileSync("git", ["config", "user.name", "deft-parity"], { cwd: root, encoding: "utf8" });
28
+ execFileSync("git", ["add", "-A"], { cwd: root, encoding: "utf8" });
29
+ execFileSync("git", ["commit", "-q", "-m", "init"], {
30
+ cwd: root,
31
+ encoding: "utf8",
32
+ env: {
33
+ ...process.env,
34
+ GIT_AUTHOR_NAME: "deft-parity",
35
+ GIT_AUTHOR_EMAIL: "parity@test.local",
36
+ GIT_COMMITTER_NAME: "deft-parity",
37
+ GIT_COMMITTER_EMAIL: "parity@test.local",
38
+ },
39
+ });
40
+ const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
41
+ return { root, head };
42
+ }
43
+ function fakeGitRunner(head, worktree) {
44
+ return (_root, args) => {
45
+ if (args[0] === "rev-parse" && args[1] === "--verify" && args[2] === "HEAD") {
46
+ return { code: 0, stdout: head, stderr: "" };
47
+ }
48
+ if (args[0] === "rev-parse" && args[1] === "--show-toplevel") {
49
+ return { code: 0, stdout: worktree, stderr: "" };
50
+ }
51
+ return { code: 0, stdout: "", stderr: "" };
52
+ };
53
+ }
54
+ function writeFreshState(fixture, startedAt, gated = {}) {
55
+ const now = new Date(startedAt);
56
+ writeRitualState(fixture.root, newRitualStatePayload({
57
+ sessionId: "parity-session",
58
+ gitHead: fixture.head,
59
+ worktreePath: resolve(fixture.root),
60
+ startedAt: now,
61
+ quickSteps: {
62
+ alignment: ritualStep({ ok: true, ts: now }),
63
+ branch_policy: ritualStep({ ok: true, ts: now }),
64
+ triage_welcome: ritualStep({ ok: true, ts: now }),
65
+ },
66
+ gatedSteps: gated,
67
+ }));
68
+ }
69
+ function loadPyModule(_deftRoot, name, rel) {
70
+ return `spec = importlib.util.spec_from_file_location(${JSON.stringify(name)}, root / ${JSON.stringify(rel)})
71
+ mod = importlib.util.module_from_spec(spec)
72
+ sys.modules[${JSON.stringify(name)}] = mod
73
+ spec.loader.exec_module(mod)`;
74
+ }
75
+ function runPythonVerify(deftRoot, fixture, scenario) {
76
+ const runnerSnippet = scenario.runner
77
+ ? `def runner(args, cwd):
78
+ return (0, "OK", "")`
79
+ : "";
80
+ const script = `import importlib.util, json, sys
81
+ from datetime import datetime, UTC
82
+ from pathlib import Path
83
+ root = Path(${JSON.stringify(deftRoot)})
84
+ fixture = Path(${JSON.stringify(fixture.root)})
85
+ ${loadPyModule(deftRoot, "verify_session_ritual", "scripts/verify_session_ritual.py")}
86
+ ${runnerSnippet}
87
+ now = datetime.fromisoformat(${JSON.stringify(scenario.nowIso.replace("Z", "+00:00"))})
88
+ kwargs = dict(tier=${JSON.stringify(scenario.tier)}, now=now, bypass=${scenario.bypass ? "True" : "False"})
89
+ ${scenario.runner ? "kwargs['runner'] = runner" : ""}
90
+ result = mod.verify(fixture, **kwargs)
91
+ print(json.dumps({"ready": result.code == 0, "exit_code": result.code, "tier": result.tier, "message": result.message, "state_path": str(result.state_path), "bypassed": result.bypassed, "would_fail_code": result.would_fail_code}, sort_keys=True))
92
+ sys.exit(result.code)`;
93
+ const result = spawnSync("uv", ["run", "python", "-c", script], {
94
+ cwd: deftRoot,
95
+ encoding: "utf8",
96
+ env: { ...process.env, DEFT_CACHE_DISABLE: "1", PYTHONUTF8: "1" },
97
+ stdio: ["ignore", "pipe", "pipe"],
98
+ });
99
+ return {
100
+ exitCode: result.status ?? 2,
101
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
102
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
103
+ };
104
+ }
105
+ function runTsVerify(fixture, scenario) {
106
+ const result = verifySessionRitual(fixture.root, {
107
+ tier: scenario.tier,
108
+ now: new Date(scenario.nowIso),
109
+ bypass: scenario.bypass,
110
+ runGit: fakeGitRunner(fixture.head, resolve(fixture.root)),
111
+ runner: scenario.runner,
112
+ });
113
+ return {
114
+ exitCode: result.code,
115
+ stdout: `${emitVerifyJson(result)}\n`,
116
+ stderr: "",
117
+ };
118
+ }
119
+ const mockGatedRunner = () => () => ({
120
+ code: 0,
121
+ stdout: "OK",
122
+ stderr: "",
123
+ });
124
+ export const PARITY_CASES = [
125
+ {
126
+ name: "quick-tier",
127
+ setup() {
128
+ const fixture = initGitRepo();
129
+ writeFreshState(fixture, FIXED_NOW);
130
+ return fixture;
131
+ },
132
+ tier: "quick",
133
+ bypass: false,
134
+ nowIso: FIXED_NOW,
135
+ },
136
+ {
137
+ name: "gated-tier",
138
+ setup() {
139
+ const fixture = initGitRepo();
140
+ writeFreshState(fixture, FIXED_NOW);
141
+ return fixture;
142
+ },
143
+ tier: "gated",
144
+ bypass: false,
145
+ nowIso: FIXED_NOW,
146
+ runner: mockGatedRunner(),
147
+ },
148
+ {
149
+ name: "stale-expiry",
150
+ setup() {
151
+ const fixture = initGitRepo({ sessionRitualStalenessHours: 1 });
152
+ writeFreshState(fixture, "2026-06-08T00:00:00Z");
153
+ return fixture;
154
+ },
155
+ tier: "quick",
156
+ bypass: false,
157
+ nowIso: FIXED_NOW,
158
+ },
159
+ {
160
+ name: "defer",
161
+ setup() {
162
+ const fixture = initGitRepo();
163
+ const now = new Date(FIXED_NOW);
164
+ writeRitualState(fixture.root, newRitualStatePayload({
165
+ sessionId: "parity-session",
166
+ gitHead: fixture.head,
167
+ worktreePath: resolve(fixture.root),
168
+ startedAt: now,
169
+ quickSteps: {
170
+ alignment: ritualStep({ ok: true, ts: now }),
171
+ branch_policy: ritualStep({ ok: true, ts: now }),
172
+ triage_welcome: ritualStep({ ok: true, ts: now }),
173
+ },
174
+ gatedSteps: {
175
+ doctor: ritualStep({ ok: true, ts: now, deferredReason: "operator deferred" }),
176
+ cache_fresh: ritualStep({ ok: true, ts: now, deferredReason: "operator deferred" }),
177
+ },
178
+ }));
179
+ return fixture;
180
+ },
181
+ tier: "gated",
182
+ bypass: false,
183
+ nowIso: FIXED_NOW,
184
+ },
185
+ {
186
+ name: "skip",
187
+ setup() {
188
+ return initGitRepo();
189
+ },
190
+ tier: "quick",
191
+ bypass: true,
192
+ nowIso: FIXED_NOW,
193
+ },
194
+ ];
195
+ function resolveDeftRoot() {
196
+ if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
197
+ return resolve(process.env.DEFT_ROOT);
198
+ }
199
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
200
+ }
201
+ export function runParity() {
202
+ const deftRoot = resolveDeftRoot();
203
+ const diffs = [];
204
+ for (const scenario of PARITY_CASES) {
205
+ const fixture = scenario.setup();
206
+ try {
207
+ const py = runPythonVerify(deftRoot, fixture, scenario);
208
+ const ts = runTsVerify(fixture, scenario);
209
+ diffs.push({
210
+ name: scenario.name,
211
+ exitMismatch: py.exitCode !== ts.exitCode,
212
+ stdoutMismatch: py.stdout !== ts.stdout,
213
+ pythonExit: py.exitCode,
214
+ tsExit: ts.exitCode,
215
+ pythonStdout: py.stdout,
216
+ tsStdout: ts.stdout,
217
+ });
218
+ }
219
+ finally {
220
+ rmSync(fixture.root, { recursive: true, force: true });
221
+ }
222
+ }
223
+ const ok = diffs.every((d) => !d.exitMismatch && !d.stdoutMismatch);
224
+ return { ok, diffs };
225
+ }
226
+ export function renderReport(result) {
227
+ if (result.ok) {
228
+ return `session parity: CLEAN -- Python and TS agree on ${result.diffs.length} case(s).`;
229
+ }
230
+ const lines = ["session parity: DIVERGENCE"];
231
+ for (const d of result.diffs) {
232
+ if (d.exitMismatch || d.stdoutMismatch) {
233
+ lines.push(` case: ${d.name}`);
234
+ if (d.exitMismatch) {
235
+ lines.push(` exit mismatch: python=${d.pythonExit} ts=${d.tsExit}`);
236
+ }
237
+ if (d.stdoutMismatch) {
238
+ lines.push(` python stdout: ${JSON.stringify(d.pythonStdout)}`);
239
+ lines.push(` ts stdout: ${JSON.stringify(d.tsStdout)}`);
240
+ }
241
+ }
242
+ }
243
+ return lines.join("\n");
244
+ }
245
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
246
+ try {
247
+ const result = runParity();
248
+ if (result.ok) {
249
+ process.stdout.write(`${renderReport(result)}\n`);
250
+ process.exit(0);
251
+ }
252
+ process.stderr.write(`${renderReport(result)}\n`);
253
+ process.exit(1);
254
+ }
255
+ catch (err) {
256
+ process.stderr.write(`session parity: harness error -- ${String(err)}\n`);
257
+ process.exit(2);
258
+ }
259
+ }
260
+ //# sourceMappingURL=session-parity.js.map
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ export interface CommandCapture {
3
+ readonly exitCode: number;
4
+ readonly stdout: string;
5
+ readonly stderr: string;
6
+ }
7
+ export interface SliceFixtureOptions {
8
+ readonly seedRecords?: ReadonlyArray<Record<string, unknown>>;
9
+ }
10
+ export interface ParityCase {
11
+ readonly name: string;
12
+ readonly argv: string[];
13
+ readonly fixture?: SliceFixtureOptions;
14
+ }
15
+ export interface ParityDiff {
16
+ readonly caseName: string;
17
+ readonly exitMismatch: boolean;
18
+ readonly stdoutMismatch: boolean;
19
+ readonly stderrMismatch: boolean;
20
+ readonly pythonExit: number;
21
+ readonly tsExit: number;
22
+ }
23
+ export interface ParityResult {
24
+ readonly ok: boolean;
25
+ readonly diffs: ParityDiff[];
26
+ }
27
+ /** Strip volatile absolute paths, UUIDs, and timestamps before compare. */
28
+ export declare function normalizeOutput(text: string): string;
29
+ export declare function buildFixtureRepo(options?: SliceFixtureOptions): string;
30
+ /** Diff one parity case between Python oracle and TS CLI. */
31
+ export declare function diffCase(python: CommandCapture, ts: CommandCapture, caseName: string): ParityDiff;
32
+ export declare const PARITY_CASES: readonly ParityCase[];
33
+ /** Run all parity cases; returns aggregate result. */
34
+ export declare function runParity(): ParityResult;
35
+ export declare function renderReport(result: ParityResult): string;
36
+ //# sourceMappingURL=slice-parity.d.ts.map
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Golden-output parity harness (#1727): runs BOTH the Python oracle
4
+ * (`scripts/slice_record_existing.py`) and the ported TS slice CLI with
5
+ * identical argv on throwaway fixtures, then diffs exit codes and
6
+ * normalised stdout/stderr (cache-off).
7
+ *
8
+ * Exit codes: 0 parity / 1 divergence / 2 harness setup error.
9
+ */
10
+ import { spawnSync } from "node:child_process";
11
+ import { appendFileSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ function sortKeysDeep(value) {
16
+ if (Array.isArray(value)) {
17
+ return value.map((item) => sortKeysDeep(item));
18
+ }
19
+ if (value !== null && typeof value === "object") {
20
+ const obj = value;
21
+ const sorted = {};
22
+ for (const key of Object.keys(obj).sort()) {
23
+ sorted[key] = sortKeysDeep(obj[key]);
24
+ }
25
+ return sorted;
26
+ }
27
+ return value;
28
+ }
29
+ function seedJsonLine(record) {
30
+ return JSON.stringify(sortKeysDeep(record)).replace(/("(?:[^"\\]|\\.)*")|([:,])/g, (_match, str, sep) => str !== undefined ? str : sep === ":" ? ": " : ", ");
31
+ }
32
+ /** Strip volatile absolute paths, UUIDs, and timestamps before compare. */
33
+ export function normalizeOutput(text) {
34
+ return text
35
+ .replace(/--project-root [^\s]+/g, "--project-root <ROOT>")
36
+ .replace(/slice_id=[0-9a-fA-F-]{36}/g, "slice_id=<UUID>")
37
+ .replace(/slice_id=[0-9a-fA-F-]{36}/g, "slice_id=<UUID>")
38
+ .replace(/"slice_id": "[0-9a-fA-F-]{36}"/g, '"slice_id": "<UUID>"')
39
+ .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/g, "<ISO>");
40
+ }
41
+ function runCapture(cmd, args, cwd) {
42
+ const result = spawnSync(cmd, args, {
43
+ cwd,
44
+ encoding: "utf8",
45
+ env: process.env,
46
+ stdio: ["ignore", "pipe", "pipe"],
47
+ });
48
+ return {
49
+ exitCode: result.status ?? 2,
50
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
51
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
52
+ };
53
+ }
54
+ export function buildFixtureRepo(options = {}) {
55
+ const root = mkdtempSync(join(tmpdir(), "deft-slice-parity-"));
56
+ mkdirSync(join(root, "vbrief", ".eval"), { recursive: true });
57
+ mkdirSync(join(root, ".git"));
58
+ const logPath = join(root, "vbrief", ".eval", "slices.jsonl");
59
+ for (const record of options.seedRecords ?? []) {
60
+ appendFileSync(logPath, `${seedJsonLine(record)}\n`, "utf8");
61
+ }
62
+ return root;
63
+ }
64
+ function resolveDeftRoot() {
65
+ if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
66
+ return resolve(process.env.DEFT_ROOT);
67
+ }
68
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
69
+ }
70
+ function runPythonSlice(deftRoot, repo, argv) {
71
+ return runCapture("uv", [
72
+ "run",
73
+ "python",
74
+ join(deftRoot, "scripts", "slice_record_existing.py"),
75
+ ...argv,
76
+ "--project-root",
77
+ repo,
78
+ ], deftRoot);
79
+ }
80
+ function runTsSlice(deftRoot, repo, argv) {
81
+ return runCapture("node", [
82
+ join(deftRoot, "packages", "core", "dist", "slice", "cli.js"),
83
+ ...argv,
84
+ "--project-root",
85
+ repo,
86
+ ], deftRoot);
87
+ }
88
+ /** Diff one parity case between Python oracle and TS CLI. */
89
+ export function diffCase(python, ts, caseName) {
90
+ const pyOut = normalizeOutput(python.stdout);
91
+ const tsOut = normalizeOutput(ts.stdout);
92
+ const pyErr = normalizeOutput(python.stderr);
93
+ const tsErr = normalizeOutput(ts.stderr);
94
+ return {
95
+ caseName,
96
+ exitMismatch: python.exitCode !== ts.exitCode,
97
+ stdoutMismatch: pyOut !== tsOut,
98
+ stderrMismatch: pyErr !== tsErr,
99
+ pythonExit: python.exitCode,
100
+ tsExit: ts.exitCode,
101
+ };
102
+ }
103
+ export const PARITY_CASES = [
104
+ { name: "list-empty", argv: ["list"] },
105
+ {
106
+ name: "list-seeded",
107
+ argv: ["list"],
108
+ fixture: {
109
+ seedRecords: [
110
+ {
111
+ slice_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
112
+ umbrella: 10,
113
+ umbrella_url: "https://github.com/owner/repo/issues/10",
114
+ sliced_at: "2026-05-14T17:00:00Z",
115
+ actor: "manual:operator",
116
+ children: [
117
+ { n: 11, url: "https://github.com/owner/repo/issues/11", wave: 1, role: "manual" },
118
+ ],
119
+ expected_close_signal: "all-children-merged",
120
+ },
121
+ ],
122
+ },
123
+ },
124
+ {
125
+ name: "list-json-seeded",
126
+ argv: ["list", "--json"],
127
+ fixture: {
128
+ seedRecords: [
129
+ {
130
+ slice_id: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
131
+ umbrella: 10,
132
+ umbrella_url: "https://github.com/owner/repo/issues/10",
133
+ sliced_at: "2026-05-14T17:00:00Z",
134
+ actor: "manual:operator",
135
+ children: [
136
+ { n: 11, url: "https://github.com/owner/repo/issues/11", wave: 1, role: "manual" },
137
+ ],
138
+ expected_close_signal: "all-children-merged",
139
+ },
140
+ ],
141
+ },
142
+ },
143
+ {
144
+ name: "record-existing-skip-validation",
145
+ argv: [
146
+ "record-existing",
147
+ "--umbrella=1",
148
+ "--children=2,3",
149
+ "--repo=owner/repo",
150
+ "--skip-validation",
151
+ "--sliced-at=2026-05-14T17:00:00Z",
152
+ ],
153
+ },
154
+ {
155
+ name: "record-existing-idempotent",
156
+ argv: [
157
+ "record-existing",
158
+ "--umbrella=1",
159
+ "--children=2,3",
160
+ "--repo=owner/repo",
161
+ "--skip-validation",
162
+ "--sliced-at=2026-05-14T17:00:00Z",
163
+ ],
164
+ fixture: {
165
+ seedRecords: [
166
+ {
167
+ slice_id: "bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee",
168
+ umbrella: 1,
169
+ umbrella_url: "https://github.com/owner/repo/issues/1",
170
+ sliced_at: "2026-05-14T17:00:00Z",
171
+ actor: "manual:operator",
172
+ children: [
173
+ { n: 2, url: "https://github.com/owner/repo/issues/2", wave: 1, role: "manual" },
174
+ { n: 3, url: "https://github.com/owner/repo/issues/3", wave: 1, role: "manual" },
175
+ ],
176
+ expected_close_signal: "all-children-merged",
177
+ },
178
+ ],
179
+ },
180
+ },
181
+ {
182
+ name: "record-existing-dry-run",
183
+ argv: [
184
+ "record-existing",
185
+ "--umbrella=42",
186
+ "--children=100,101",
187
+ "--repo=owner/repo",
188
+ "--dry-run",
189
+ "--skip-validation",
190
+ "--sliced-at=2026-05-14T17:00:00Z",
191
+ ],
192
+ },
193
+ {
194
+ name: "duplicate-child",
195
+ argv: [
196
+ "record-existing",
197
+ "--umbrella=1",
198
+ "--children=2,2",
199
+ "--repo=owner/repo",
200
+ "--skip-validation",
201
+ ],
202
+ },
203
+ {
204
+ name: "umbrella-in-children",
205
+ argv: [
206
+ "record-existing",
207
+ "--umbrella=1",
208
+ "--children=1,2",
209
+ "--repo=owner/repo",
210
+ "--skip-validation",
211
+ ],
212
+ },
213
+ {
214
+ name: "wave-member-not-in-children",
215
+ argv: [
216
+ "record-existing",
217
+ "--umbrella=1",
218
+ "--children=2,3",
219
+ "--wave-2=999",
220
+ "--repo=owner/repo",
221
+ "--skip-validation",
222
+ ],
223
+ },
224
+ {
225
+ name: "cross-wave-collision",
226
+ argv: [
227
+ "record-existing",
228
+ "--umbrella=1",
229
+ "--children=2,3",
230
+ "--wave-1=2",
231
+ "--wave-2=2",
232
+ "--repo=owner/repo",
233
+ "--skip-validation",
234
+ ],
235
+ },
236
+ {
237
+ name: "default-subcommand",
238
+ argv: [
239
+ "--umbrella=1",
240
+ "--children=2",
241
+ "--repo=owner/repo",
242
+ "--skip-validation",
243
+ "--sliced-at=2026-05-14T17:00:00Z",
244
+ ],
245
+ },
246
+ {
247
+ name: "missing-project-root",
248
+ argv: ["list", "--project-root", "/no/such/deft-project-root-xyz"],
249
+ },
250
+ ];
251
+ /** Run all parity cases; returns aggregate result. */
252
+ export function runParity() {
253
+ const deftRoot = resolveDeftRoot();
254
+ const diffs = [];
255
+ for (const testCase of PARITY_CASES) {
256
+ const pyRepo = buildFixtureRepo(testCase.fixture);
257
+ const tsRepo = buildFixtureRepo(testCase.fixture);
258
+ try {
259
+ const python = runPythonSlice(deftRoot, pyRepo, testCase.argv);
260
+ const ts = runTsSlice(deftRoot, tsRepo, testCase.argv);
261
+ diffs.push(diffCase(python, ts, testCase.name));
262
+ }
263
+ finally {
264
+ rmSync(pyRepo, { recursive: true, force: true });
265
+ rmSync(tsRepo, { recursive: true, force: true });
266
+ }
267
+ }
268
+ const ok = diffs.every((d) => !d.exitMismatch && !d.stdoutMismatch && !d.stderrMismatch);
269
+ return { ok, diffs };
270
+ }
271
+ export function renderReport(result) {
272
+ if (result.ok) {
273
+ return `slice parity: CLEAN -- Python and TS agree on ${PARITY_CASES.length} case(s).`;
274
+ }
275
+ const lines = ["slice parity: DIVERGENCE"];
276
+ for (const d of result.diffs) {
277
+ if (d.exitMismatch || d.stdoutMismatch || d.stderrMismatch) {
278
+ lines.push(` case: ${d.caseName}`);
279
+ if (d.exitMismatch)
280
+ lines.push(` exit: python=${d.pythonExit} ts=${d.tsExit}`);
281
+ if (d.stdoutMismatch)
282
+ lines.push(" stdout mismatch");
283
+ if (d.stderrMismatch)
284
+ lines.push(" stderr mismatch");
285
+ }
286
+ }
287
+ return lines.join("\n");
288
+ }
289
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
290
+ try {
291
+ const result = runParity();
292
+ if (result.ok) {
293
+ process.stdout.write(`${renderReport(result)}\n`);
294
+ process.exit(0);
295
+ }
296
+ process.stderr.write(`${renderReport(result)}\n`);
297
+ process.exit(1);
298
+ }
299
+ catch (err) {
300
+ process.stderr.write(`slice parity: harness error -- ${String(err)}\n`);
301
+ process.exit(2);
302
+ }
303
+ }
304
+ //# sourceMappingURL=slice-parity.js.map
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ /** Run slice verbs with argv; returns process exit code. */
3
+ export declare function run(argv: string[]): number;
4
+ //# sourceMappingURL=slice.d.ts.map
package/dist/slice.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Thin CLI entry for slice:record-existing / slice:list (#1727).
4
+ * Delegates to the core implementation at packages/core/dist/slice/cli.js.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ function coreCliPath() {
10
+ return join(dirname(fileURLToPath(import.meta.url)), "../../core/dist/slice/cli.js");
11
+ }
12
+ /** Run slice verbs with argv; returns process exit code. */
13
+ export function run(argv) {
14
+ const result = spawnSync(process.execPath, [coreCliPath(), ...argv], {
15
+ encoding: "utf8",
16
+ stdio: ["ignore", "pipe", "pipe"],
17
+ });
18
+ if (typeof result.stdout === "string" && result.stdout.length > 0) {
19
+ process.stdout.write(result.stdout);
20
+ }
21
+ if (typeof result.stderr === "string" && result.stderr.length > 0) {
22
+ process.stderr.write(result.stderr);
23
+ }
24
+ return result.status ?? 2;
25
+ }
26
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
27
+ process.exit(run(process.argv.slice(2)));
28
+ }
29
+ //# sourceMappingURL=slice.js.map
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ export interface ScenarioResult {
3
+ readonly name: string;
4
+ readonly exitCode: number;
5
+ readonly stdout: string;
6
+ readonly stderr: string;
7
+ }
8
+ export interface ParityScenario {
9
+ readonly name: string;
10
+ readonly vbriefRel: string;
11
+ readonly vbriefStatus?: string;
12
+ readonly envelopeRel: string | null;
13
+ readonly allowDirty?: boolean;
14
+ readonly dirty?: boolean;
15
+ }
16
+ export interface ParityResult {
17
+ readonly ok: boolean;
18
+ readonly scenarios: Array<{
19
+ readonly name: string;
20
+ readonly exitMismatch: boolean;
21
+ readonly pythonExit: number;
22
+ readonly tsExit: number;
23
+ readonly messageMismatch: boolean;
24
+ readonly pythonMessage: string;
25
+ readonly tsMessage: string;
26
+ }>;
27
+ }
28
+ /** Scenarios exercised by the parity harness (mirrors Python contract cases). */
29
+ export declare const PARITY_SCENARIOS: readonly ParityScenario[];
30
+ /** Normalise gate message for comparison (trim, collapse whitespace). */
31
+ export declare function normaliseMessage(stdout: string, stderr: string, exitCode: number): string;
32
+ /** Build a fixture repo for one scenario; return repo root + vbrief absolute path. */
33
+ export declare function buildScenarioRepo(scenario: ParityScenario): {
34
+ root: string;
35
+ vbriefPath: string;
36
+ envelopePath: string | null;
37
+ };
38
+ /** Diff python vs TS gate outputs across all parity scenarios. */
39
+ export declare function diffParity(python: ScenarioResult, ts: ScenarioResult): {
40
+ exitMismatch: boolean;
41
+ messageMismatch: boolean;
42
+ pythonMessage: string;
43
+ tsMessage: string;
44
+ };
45
+ /** Run all parity scenarios and return a structured result. */
46
+ export declare function runParity(): ParityResult;
47
+ /** Render a human-readable parity report (exported for unit tests). */
48
+ export declare function renderReport(result: ParityResult): string;
49
+ //# sourceMappingURL=story-ready-parity.d.ts.map