@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
package/dist/policy.js ADDED
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Policy CLI (#1722): `policy:show` and `policy:allow-direct-commits` surfaces,
4
+ * mirroring scripts/_policy_show_cli.py and scripts/policy_set.py.
5
+ */
6
+ import { existsSync } from "node:fs";
7
+ import { join, resolve as pathResolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { disclosureLine, inspectAllPolicies, inspectOnePolicy, PROJECT_DEFINITION_REL_PATH, pythonListRepr, pythonStringRepr, registeredPolicyNames, renderJson, renderText, resolvePolicy, setPolicy, } from "@deftai/directive-core/policy";
10
+ const CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
11
+ "branch turns OFF the deft branch-protection policy.\n" +
12
+ " \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
13
+ "commits.\n" +
14
+ " \u2022 verify:branch will pass on the default branch.\n" +
15
+ " \u2022 The CI sanity check (head_ref != base_ref) is still independent and " +
16
+ "will continue to flag master->master PRs.\n" +
17
+ " \u2022 This change is reversible: run `task policy:enforce-branches` to " +
18
+ "re-enable the gate.\n" +
19
+ " \u2022 The change is recorded to meta/policy-changes.log for auditability.";
20
+ function makeSetError(message) {
21
+ return {
22
+ cmd: "show",
23
+ confirm: false,
24
+ actor: "",
25
+ note: "",
26
+ projectRoot: ".",
27
+ format: "text",
28
+ changedOnly: false,
29
+ field: null,
30
+ error: message,
31
+ };
32
+ }
33
+ function parseProjectRoot(argv) {
34
+ let projectRoot = ".";
35
+ for (let i = 0; i < argv.length; i += 1) {
36
+ const arg = argv[i];
37
+ if (arg === "--project-root") {
38
+ const v = argv[i + 1];
39
+ if (v === undefined) {
40
+ return { projectRoot: ".", error: "argument --project-root: expected one argument" };
41
+ }
42
+ projectRoot = v;
43
+ i += 1;
44
+ }
45
+ else if (arg?.startsWith("--project-root=")) {
46
+ projectRoot = arg.slice("--project-root=".length);
47
+ }
48
+ else {
49
+ return { projectRoot: ".", error: `unrecognized argument: ${arg}` };
50
+ }
51
+ }
52
+ return { projectRoot };
53
+ }
54
+ /** Parse policy:show flags (mirrors _policy_show_cli.py). */
55
+ export function parseShowArgs(argv) {
56
+ const parsed = {
57
+ format: "text",
58
+ changedOnly: false,
59
+ field: null,
60
+ projectRoot: ".",
61
+ };
62
+ for (let i = 0; i < argv.length; i += 1) {
63
+ const arg = argv[i];
64
+ if (arg === "--format") {
65
+ const v = argv[i + 1];
66
+ if (v !== "text" && v !== "json") {
67
+ return {
68
+ ...parsed,
69
+ error: v === undefined ? "argument --format: expected one argument" : `invalid choice: '${v}'`,
70
+ };
71
+ }
72
+ parsed.format = v;
73
+ i += 1;
74
+ }
75
+ else if (arg?.startsWith("--format=")) {
76
+ const v = arg.slice("--format=".length);
77
+ if (v !== "text" && v !== "json") {
78
+ return { ...parsed, error: `invalid choice: '${v}'` };
79
+ }
80
+ parsed.format = v;
81
+ }
82
+ else if (arg === "--changed-only") {
83
+ parsed.changedOnly = true;
84
+ }
85
+ else if (arg === "--field") {
86
+ const v = argv[i + 1];
87
+ if (v === undefined)
88
+ return { ...parsed, error: "argument --field: expected one argument" };
89
+ parsed.field = v;
90
+ i += 1;
91
+ }
92
+ else if (arg?.startsWith("--field=")) {
93
+ parsed.field = arg.slice("--field=".length);
94
+ }
95
+ else if (arg === "--project-root") {
96
+ const v = argv[i + 1];
97
+ if (v === undefined) {
98
+ return { ...parsed, error: "argument --project-root: expected one argument" };
99
+ }
100
+ parsed.projectRoot = v;
101
+ i += 1;
102
+ }
103
+ else if (arg?.startsWith("--project-root=")) {
104
+ parsed.projectRoot = arg.slice("--project-root=".length);
105
+ }
106
+ else {
107
+ return { ...parsed, error: `unrecognized argument: ${arg}` };
108
+ }
109
+ }
110
+ return parsed;
111
+ }
112
+ /** Parse argv for the policy CLI (show + set subcommands). */
113
+ export function parseArgs(argv) {
114
+ if (argv.length === 0) {
115
+ return makeSetError("usage: policy [show|enforce-branches|allow-direct-commits|resolve] ...");
116
+ }
117
+ const cmd = argv[0];
118
+ if (cmd === "show") {
119
+ const show = parseShowArgs(argv.slice(1));
120
+ return {
121
+ cmd: "show",
122
+ confirm: false,
123
+ actor: "task policy:show",
124
+ note: "",
125
+ projectRoot: show.projectRoot,
126
+ format: show.format,
127
+ changedOnly: show.changedOnly,
128
+ field: show.field,
129
+ error: show.error,
130
+ };
131
+ }
132
+ if (cmd === "resolve") {
133
+ const root = parseProjectRoot(argv.slice(1));
134
+ return {
135
+ cmd: "resolve",
136
+ confirm: false,
137
+ actor: "agent",
138
+ note: "",
139
+ projectRoot: root.projectRoot,
140
+ format: "text",
141
+ changedOnly: false,
142
+ field: null,
143
+ error: root.error,
144
+ };
145
+ }
146
+ if (cmd === "enforce-branches" || cmd === "allow-direct-commits") {
147
+ let confirm = false;
148
+ let actor = cmd === "enforce-branches"
149
+ ? "task policy:enforce-branches"
150
+ : "task policy:allow-direct-commits";
151
+ let note = "";
152
+ let projectRoot = ".";
153
+ for (let i = 1; i < argv.length; i += 1) {
154
+ const arg = argv[i];
155
+ if (arg === "--confirm") {
156
+ confirm = true;
157
+ }
158
+ else if (arg === "--actor") {
159
+ const v = argv[i + 1];
160
+ if (v === undefined)
161
+ return makeSetError("argument --actor: expected one argument");
162
+ actor = v;
163
+ i += 1;
164
+ }
165
+ else if (arg?.startsWith("--actor=")) {
166
+ actor = arg.slice("--actor=".length);
167
+ }
168
+ else if (arg === "--note") {
169
+ const v = argv[i + 1];
170
+ if (v === undefined)
171
+ return makeSetError("argument --note: expected one argument");
172
+ note = v;
173
+ i += 1;
174
+ }
175
+ else if (arg?.startsWith("--note=")) {
176
+ note = arg.slice("--note=".length);
177
+ }
178
+ else if (arg === "--project-root") {
179
+ const v = argv[i + 1];
180
+ if (v === undefined)
181
+ return makeSetError("argument --project-root: expected one argument");
182
+ projectRoot = v;
183
+ i += 1;
184
+ }
185
+ else if (arg?.startsWith("--project-root=")) {
186
+ projectRoot = arg.slice("--project-root=".length);
187
+ }
188
+ else {
189
+ return makeSetError(`unrecognized argument: ${arg}`);
190
+ }
191
+ }
192
+ return {
193
+ cmd,
194
+ confirm,
195
+ actor,
196
+ note,
197
+ projectRoot,
198
+ format: "text",
199
+ changedOnly: false,
200
+ field: null,
201
+ };
202
+ }
203
+ return makeSetError(`unknown subcommand: ${cmd}`);
204
+ }
205
+ function runShow(args) {
206
+ const projectRoot = pathResolve(args.projectRoot);
207
+ const pdPath = join(projectRoot, PROJECT_DEFINITION_REL_PATH);
208
+ if (!existsSync(pdPath)) {
209
+ process.stderr.write(`[policy:show] PROJECT-DEFINITION not found at ${pdPath}; ` +
210
+ "rendering framework defaults.\n");
211
+ }
212
+ if (args.field !== null) {
213
+ const field = inspectOnePolicy(args.field, projectRoot);
214
+ if (field === null) {
215
+ const known = registeredPolicyNames();
216
+ process.stderr.write(`[policy:show] unknown --field=${pythonStringRepr(args.field)}; ` +
217
+ `registered fields: ${pythonListRepr(known)}\n`);
218
+ return 2;
219
+ }
220
+ if (args.format === "json") {
221
+ process.stdout.write(`${renderJson([field])}\n`);
222
+ }
223
+ else {
224
+ process.stdout.write(`${renderText([field])}\n`);
225
+ }
226
+ return 0;
227
+ }
228
+ let fields = inspectAllPolicies(projectRoot);
229
+ if (args.changedOnly) {
230
+ fields = fields.filter((f) => f.source !== "default");
231
+ }
232
+ if (args.format === "json") {
233
+ process.stdout.write(`${renderJson(fields)}\n`);
234
+ }
235
+ else {
236
+ process.stdout.write(`${renderText(fields)}\n`);
237
+ }
238
+ return 0;
239
+ }
240
+ function runResolve(projectRoot) {
241
+ const result = resolvePolicy(projectRoot);
242
+ process.stdout.write(`allowDirectCommitsToMaster=${String(result.allowDirectCommits).toLowerCase()}\n`);
243
+ process.stdout.write(`source=${result.source}\n`);
244
+ if (result.deprecationWarning !== null) {
245
+ process.stdout.write(`warning=${result.deprecationWarning}\n`);
246
+ }
247
+ if (result.error !== null) {
248
+ process.stdout.write(`error=${result.error}\n`);
249
+ }
250
+ process.stdout.write(`${disclosureLine(result)}\n`);
251
+ return 0;
252
+ }
253
+ function runSet(args) {
254
+ const projectRoot = pathResolve(args.projectRoot);
255
+ if (args.cmd === "allow-direct-commits" && !args.confirm) {
256
+ process.stdout.write(`${CAPABILITY_COST_DISCLOSURE}\n\n`);
257
+ process.stdout.write("Re-run with --confirm to apply: task policy:allow-direct-commits -- --confirm\n");
258
+ return 1;
259
+ }
260
+ const target = args.cmd === "allow-direct-commits";
261
+ try {
262
+ const { changed, auditEntry } = setPolicy(projectRoot, {
263
+ allowDirectCommits: target,
264
+ actor: args.actor,
265
+ note: args.note,
266
+ });
267
+ const state = target ? "OFF" : "ON";
268
+ process.stdout.write(`\u2713 plan.policy.allowDirectCommitsToMaster=${target ? "true" : "false"} ` +
269
+ `(branch-protection ${state}).\n`);
270
+ if (changed) {
271
+ process.stdout.write(` audit: meta/policy-changes.log :: ${auditEntry}\n`);
272
+ }
273
+ else {
274
+ process.stdout.write(" no-op: value already matched (audit entry still appended for trail).\n");
275
+ }
276
+ process.stdout.write(`${disclosureLine(resolvePolicy(projectRoot))}\n`);
277
+ return 0;
278
+ }
279
+ catch (err) {
280
+ const message = err instanceof Error ? err.message : String(err);
281
+ if (message.includes("PROJECT-DEFINITION not found")) {
282
+ process.stderr.write(`\u274c ${message}\n`);
283
+ process.stderr.write(" Recovery: run `task setup` to generate vbrief/PROJECT-DEFINITION.vbrief.json.\n");
284
+ return 2;
285
+ }
286
+ process.stderr.write(`\u274c Config error: ${message}\n`);
287
+ return 2;
288
+ }
289
+ }
290
+ /** Run the policy CLI; returns process exit code. */
291
+ export function run(argv) {
292
+ const args = parseArgs(argv);
293
+ if (args.error !== undefined) {
294
+ process.stderr.write(`policy: ${args.error}\n`);
295
+ return 2;
296
+ }
297
+ if (args.cmd === "show") {
298
+ return runShow({
299
+ format: args.format,
300
+ changedOnly: args.changedOnly,
301
+ field: args.field,
302
+ projectRoot: args.projectRoot,
303
+ });
304
+ }
305
+ if (args.cmd === "resolve") {
306
+ return runResolve(pathResolve(args.projectRoot));
307
+ }
308
+ if (args.cmd === "enforce-branches" || args.cmd === "allow-direct-commits") {
309
+ return runSet(args);
310
+ }
311
+ return 2;
312
+ }
313
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
314
+ process.exit(run(process.argv.slice(2)));
315
+ }
316
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ export interface FakeGhResponses {
3
+ readonly [label: string]: {
4
+ readonly returncode: number;
5
+ readonly stdout?: string;
6
+ readonly stderr?: string;
7
+ };
8
+ }
9
+ export interface ParityScenario {
10
+ readonly name: string;
11
+ readonly argv: readonly string[];
12
+ readonly responses?: FakeGhResponses;
13
+ readonly setupFiles?: (dir: string) => Record<string, string>;
14
+ readonly compareStream?: "stdout" | "stderr";
15
+ }
16
+ export interface ScenarioResult {
17
+ readonly name: string;
18
+ readonly exitCode: number;
19
+ readonly stdout: string;
20
+ readonly stderr: string;
21
+ }
22
+ export interface ParityResult {
23
+ readonly ok: boolean;
24
+ readonly scenarios: Array<{
25
+ readonly name: string;
26
+ readonly exitMismatch: boolean;
27
+ readonly pythonExit: number;
28
+ readonly tsExit: number;
29
+ readonly outputMismatch: boolean;
30
+ readonly pythonOutput: string;
31
+ readonly tsOutput: string;
32
+ readonly stream: "stdout" | "stderr";
33
+ }>;
34
+ }
35
+ export declare const PARITY_SCENARIOS: readonly ParityScenario[];
36
+ export declare function pickOutput(result: ScenarioResult, stream: "stdout" | "stderr"): string;
37
+ export declare function diffParity(python: ScenarioResult, ts: ScenarioResult, stream: "stdout" | "stderr"): {
38
+ exitMismatch: boolean;
39
+ outputMismatch: boolean;
40
+ pythonOutput: string;
41
+ tsOutput: string;
42
+ };
43
+ export declare function runParity(): ParityResult;
44
+ export declare function renderReport(result: ParityResult): string;
45
+ //# sourceMappingURL=pr-closing-keywords-parity.d.ts.map
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Golden-output parity harness (#1730): runs BOTH the Python oracle
4
+ * (`scripts/pr_check_closing_keywords.py`) and the ported TS CLI with a fake `gh`
5
+ * on PATH (cache-off), then diffs exit codes and byte-identical stderr.
6
+ *
7
+ * Exit codes: 0 parity / 1 divergence / 2 harness setup error.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { dirname, join, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ const FAKE_GH_PY = `import json
15
+ import os
16
+ import sys
17
+
18
+ def classify(cmd):
19
+ joined = " ".join(cmd)
20
+ if "--json" in cmd and "body" in cmd:
21
+ return "pr-body"
22
+ if "--json" in cmd and "commits" in cmd:
23
+ return "pr-commits"
24
+ return "unknown"
25
+
26
+ responses = json.loads(os.environ.get("DEFT_FAKE_GH_RESPONSES", "{}"))
27
+ label = classify(sys.argv[1:])
28
+ resp = responses.get(label, {"returncode": 1, "stderr": f"unexpected gh call: {label}", "stdout": ""})
29
+ stdout = resp.get("stdout", "")
30
+ stderr = resp.get("stderr", "")
31
+ if stdout:
32
+ sys.stdout.write(stdout)
33
+ if stderr:
34
+ sys.stderr.write(stderr)
35
+ sys.exit(int(resp.get("returncode", 0)))
36
+ `;
37
+ export const PARITY_SCENARIOS = [
38
+ {
39
+ name: "offline-no-keywords-clean",
40
+ argv: ["--body-file", "{body}"],
41
+ setupFiles: () => ({ body: "Refs #642 (umbrella; remains open)." }),
42
+ compareStream: "stderr",
43
+ },
44
+ {
45
+ name: "offline-negation-hit",
46
+ argv: ["--body-file", "{body}"],
47
+ setupFiles: () => ({
48
+ body: "This PR DOES NOT CLOSE #734 -- the issue stays open.",
49
+ }),
50
+ compareStream: "stderr",
51
+ },
52
+ {
53
+ name: "offline-true-positive-closes-clean",
54
+ argv: ["--body-file", "{body}"],
55
+ setupFiles: () => ({
56
+ body: "feat(core): land the gate.\n\nCloses #734\n\nDescription continues...",
57
+ }),
58
+ compareStream: "stderr",
59
+ },
60
+ {
61
+ name: "pr-mode-no-keywords-clean",
62
+ argv: ["--pr", "735", "--repo", "deftai/directive"],
63
+ responses: {
64
+ "pr-body": {
65
+ returncode: 0,
66
+ stdout: JSON.stringify({ body: "Refs #642 only." }),
67
+ },
68
+ "pr-commits": {
69
+ returncode: 0,
70
+ stdout: JSON.stringify({
71
+ commits: [{ messageHeadline: "feat: implement", messageBody: "Refs #1\n" }],
72
+ }),
73
+ },
74
+ },
75
+ compareStream: "stderr",
76
+ },
77
+ {
78
+ name: "pr-mode-negation-hit",
79
+ argv: ["--pr", "735", "--repo", "deftai/directive"],
80
+ responses: {
81
+ "pr-body": {
82
+ returncode: 0,
83
+ stdout: JSON.stringify({
84
+ body: "Body header. Intentionally NOT using `Closes #642` because umbrella.",
85
+ }),
86
+ },
87
+ "pr-commits": { returncode: 0, stdout: JSON.stringify({ commits: [] }) },
88
+ },
89
+ compareStream: "stderr",
90
+ },
91
+ {
92
+ name: "allow-list-suppressed",
93
+ argv: ["--body-file", "{body}", "--allow-known-false-positives", "999"],
94
+ setupFiles: () => ({
95
+ body: "Body. Intentionally not `Closes #999` (test fixture).\n",
96
+ }),
97
+ compareStream: "stderr",
98
+ },
99
+ ];
100
+ function installFakeGh() {
101
+ const binDir = mkdtempSync(join(tmpdir(), "deft-pr-closing-keywords-fake-gh-"));
102
+ const ghBin = join(binDir, "gh");
103
+ const ghxBin = join(binDir, "ghx");
104
+ writeFileSync(ghBin, `#!/usr/bin/env python3\n${FAKE_GH_PY}`, "utf8");
105
+ writeFileSync(ghxBin, `#!/usr/bin/env python3\n${FAKE_GH_PY}`, "utf8");
106
+ chmodSync(ghBin, 0o755);
107
+ chmodSync(ghxBin, 0o755);
108
+ return {
109
+ binDir,
110
+ cleanup: () => {
111
+ rmSync(binDir, { recursive: true, force: true });
112
+ },
113
+ };
114
+ }
115
+ function runCapture(cmd, args, cwd, env) {
116
+ const result = spawnSync(cmd, args, {
117
+ cwd,
118
+ encoding: "utf8",
119
+ env: { ...process.env, ...env },
120
+ stdio: ["ignore", "pipe", "pipe"],
121
+ });
122
+ return {
123
+ status: result.status ?? 2,
124
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
125
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
126
+ };
127
+ }
128
+ function resolveDeftRoot() {
129
+ if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
130
+ return resolve(process.env.DEFT_ROOT);
131
+ }
132
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
133
+ }
134
+ function normaliseHarnessNoise(text) {
135
+ return text
136
+ .split("\n")
137
+ .filter((line) => !line.startsWith("Using CPython") &&
138
+ !line.startsWith("Creating virtual environment") &&
139
+ !line.startsWith("Installed "))
140
+ .join("\n");
141
+ }
142
+ export function pickOutput(result, stream) {
143
+ return stream === "stdout" ? result.stdout : result.stderr;
144
+ }
145
+ export function diffParity(python, ts, stream) {
146
+ const pythonOutput = normaliseHarnessNoise(pickOutput(python, stream));
147
+ const tsOutput = normaliseHarnessNoise(pickOutput(ts, stream));
148
+ return {
149
+ exitMismatch: python.exitCode !== ts.exitCode,
150
+ outputMismatch: pythonOutput !== tsOutput,
151
+ pythonOutput,
152
+ tsOutput,
153
+ };
154
+ }
155
+ function materializeArgv(scenario, fileDir) {
156
+ const files = scenario.setupFiles?.(fileDir) ?? {};
157
+ for (const [name, content] of Object.entries(files)) {
158
+ writeFileSync(join(fileDir, name), content, "utf8");
159
+ }
160
+ return scenario.argv.map((arg) => {
161
+ if (arg.startsWith("{") && arg.endsWith("}")) {
162
+ const key = arg.slice(1, -1);
163
+ return join(fileDir, key);
164
+ }
165
+ return arg;
166
+ });
167
+ }
168
+ function runScenario(deftRoot, scenario) {
169
+ const fake = installFakeGh();
170
+ const fileDir = mkdtempSync(join(tmpdir(), "deft-pr-closing-keywords-files-"));
171
+ try {
172
+ const argv = materializeArgv(scenario, fileDir);
173
+ const pathPrefix = `${fake.binDir}:${process.env.PATH ?? ""}`;
174
+ const env = {
175
+ DEFT_CACHE_DISABLE: "1",
176
+ PYTHONUTF8: "1",
177
+ PATH: pathPrefix,
178
+ };
179
+ if (scenario.responses !== undefined) {
180
+ env.DEFT_FAKE_GH_RESPONSES = JSON.stringify(scenario.responses);
181
+ }
182
+ const py = runCapture("uv", ["run", "python", join(deftRoot, "scripts", "pr_check_closing_keywords.py"), ...argv], deftRoot, env);
183
+ const ts = runCapture("node", [join(deftRoot, "packages", "cli", "dist", "pr-closing-keywords.js"), ...argv], deftRoot, env);
184
+ return {
185
+ python: {
186
+ name: scenario.name,
187
+ exitCode: py.status,
188
+ stdout: py.stdout,
189
+ stderr: py.stderr,
190
+ },
191
+ ts: {
192
+ name: scenario.name,
193
+ exitCode: ts.status,
194
+ stdout: ts.stdout,
195
+ stderr: ts.stderr,
196
+ },
197
+ };
198
+ }
199
+ finally {
200
+ fake.cleanup();
201
+ rmSync(fileDir, { recursive: true, force: true });
202
+ }
203
+ }
204
+ export function runParity() {
205
+ const deftRoot = resolveDeftRoot();
206
+ const scenarios = [];
207
+ for (const scenario of PARITY_SCENARIOS) {
208
+ const ran = runScenario(deftRoot, scenario);
209
+ const stream = scenario.compareStream ?? "stderr";
210
+ scenarios.push({
211
+ name: scenario.name,
212
+ pythonExit: ran.python.exitCode,
213
+ tsExit: ran.ts.exitCode,
214
+ stream,
215
+ ...diffParity(ran.python, ran.ts, stream),
216
+ });
217
+ }
218
+ const ok = scenarios.every((s) => !s.exitMismatch && !s.outputMismatch);
219
+ return { ok, scenarios };
220
+ }
221
+ export function renderReport(result) {
222
+ if (result.ok) {
223
+ return `pr_check_closing_keywords parity: CLEAN -- Python and TS agree on ${result.scenarios.length} scenario(s).`;
224
+ }
225
+ const lines = ["pr_check_closing_keywords parity: DIVERGENCE"];
226
+ for (const s of result.scenarios) {
227
+ if (s.exitMismatch || s.outputMismatch) {
228
+ lines.push(` scenario: ${s.name}`);
229
+ if (s.exitMismatch) {
230
+ lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
231
+ }
232
+ if (s.outputMismatch) {
233
+ lines.push(` stream: ${s.stream}`);
234
+ lines.push(` python (${s.pythonOutput.length} bytes):`);
235
+ lines.push(s.pythonOutput.slice(0, 500));
236
+ lines.push(` ts (${s.tsOutput.length} bytes):`);
237
+ lines.push(s.tsOutput.slice(0, 500));
238
+ }
239
+ }
240
+ }
241
+ return lines.join("\n");
242
+ }
243
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
244
+ try {
245
+ const result = runParity();
246
+ if (result.ok) {
247
+ process.stdout.write(`${renderReport(result)}\n`);
248
+ process.exit(0);
249
+ }
250
+ process.stderr.write(`${renderReport(result)}\n`);
251
+ process.exit(1);
252
+ }
253
+ catch (err) {
254
+ const msg = String(err).replace(/\r?\n/g, " ");
255
+ process.stderr.write(`pr_check_closing_keywords parity: harness error -- ${msg}\n`);
256
+ process.exit(2);
257
+ }
258
+ }
259
+ //# sourceMappingURL=pr-closing-keywords-parity.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): number;
3
+ //# sourceMappingURL=pr-closing-keywords.d.ts.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { cmdPrCheckClosingKeywords } from "../../core/dist/pr-closing-keywords/main.js";
4
+ export function run(argv) {
5
+ return cmdPrCheckClosingKeywords(argv);
6
+ }
7
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
8
+ process.exit(run(process.argv.slice(2)));
9
+ }
10
+ //# sourceMappingURL=pr-closing-keywords.js.map
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ export interface FakeGhResponses {
3
+ readonly [label: string]: {
4
+ readonly returncode: number;
5
+ readonly stdout?: string;
6
+ readonly stderr?: string;
7
+ };
8
+ }
9
+ export interface ParityScenario {
10
+ readonly name: string;
11
+ readonly argv: readonly string[];
12
+ readonly responses: FakeGhResponses;
13
+ readonly compareStream?: "stdout" | "stderr";
14
+ }
15
+ export interface ScenarioResult {
16
+ readonly name: string;
17
+ readonly exitCode: number;
18
+ readonly stdout: string;
19
+ readonly stderr: string;
20
+ }
21
+ export interface ParityResult {
22
+ readonly ok: boolean;
23
+ readonly scenarios: Array<{
24
+ readonly name: string;
25
+ readonly exitMismatch: boolean;
26
+ readonly pythonExit: number;
27
+ readonly tsExit: number;
28
+ readonly outputMismatch: boolean;
29
+ readonly pythonOutput: string;
30
+ readonly tsOutput: string;
31
+ readonly stream: "stdout" | "stderr";
32
+ }>;
33
+ }
34
+ export declare const PARITY_SCENARIOS: readonly ParityScenario[];
35
+ export declare function pickOutput(result: ScenarioResult, stream: "stdout" | "stderr"): string;
36
+ export declare function diffParity(python: ScenarioResult, ts: ScenarioResult, stream: "stdout" | "stderr"): {
37
+ exitMismatch: boolean;
38
+ outputMismatch: boolean;
39
+ pythonOutput: string;
40
+ tsOutput: string;
41
+ };
42
+ export declare function runParity(): ParityResult;
43
+ export declare function renderReport(result: ParityResult): string;
44
+ //# sourceMappingURL=pr-merge-readiness-parity.d.ts.map