@deftai/directive-core 0.105.0 → 0.107.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 (173) hide show
  1. package/dist/authz/classify.js +591 -34
  2. package/dist/check/gate-lists.js +2 -0
  3. package/dist/check/named-cause.js +1 -1
  4. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  5. package/dist/design-critique/exclusive-chip.js +68 -0
  6. package/dist/design-critique/parent-audit.d.ts +59 -0
  7. package/dist/design-critique/parent-audit.js +121 -0
  8. package/dist/doctor/checks.d.ts +6 -0
  9. package/dist/doctor/checks.js +35 -0
  10. package/dist/forward-coverage/diff-coverage.d.ts +39 -0
  11. package/dist/forward-coverage/diff-coverage.js +112 -0
  12. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  13. package/dist/forward-coverage/diff-lines.js +173 -0
  14. package/dist/forward-coverage/evaluate.d.ts +24 -9
  15. package/dist/forward-coverage/evaluate.js +125 -19
  16. package/dist/hooks/dest-form.d.ts +46 -0
  17. package/dist/hooks/dest-form.js +575 -0
  18. package/dist/hooks/dispatcher.d.ts +17 -1
  19. package/dist/hooks/dispatcher.js +82 -11
  20. package/dist/hooks/index.d.ts +1 -0
  21. package/dist/hooks/index.js +1 -0
  22. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  23. package/dist/init-deposit/agent-hooks.js +1 -1
  24. package/dist/intake/clause-derivation.d.ts +9 -0
  25. package/dist/intake/clause-derivation.js +30 -3
  26. package/dist/intake/github-auth-modes-cli.js +14 -2
  27. package/dist/intake/github-auth-modes.d.ts +54 -9
  28. package/dist/intake/github-auth-modes.js +326 -123
  29. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  30. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  31. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  32. package/dist/lifecycle/completed-write-guard.js +373 -0
  33. package/dist/lifecycle/index.d.ts +1 -0
  34. package/dist/lifecycle/index.js +1 -0
  35. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  36. package/dist/lifecycle-visible/evaluate.js +710 -0
  37. package/dist/lifecycle-visible/index.d.ts +2 -0
  38. package/dist/lifecycle-visible/index.js +2 -0
  39. package/dist/literal-acceptance/capture.d.ts +16 -0
  40. package/dist/literal-acceptance/capture.js +78 -10
  41. package/dist/literal-acceptance/evaluate.js +25 -3
  42. package/dist/literal-acceptance/index.d.ts +2 -2
  43. package/dist/literal-acceptance/index.js +2 -2
  44. package/dist/literal-acceptance/run.js +9 -3
  45. package/dist/literal-acceptance/types.d.ts +6 -0
  46. package/dist/literal-acceptance/types.js +6 -0
  47. package/dist/orchestration/judgment-policy.d.ts +13 -0
  48. package/dist/orchestration/judgment-policy.js +15 -0
  49. package/dist/orchestration/probe-session.js +13 -8
  50. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  51. package/dist/orchestration/verify-judgment-gates.js +43 -11
  52. package/dist/policy/ceremony-dial.js +7 -7
  53. package/dist/policy/host-hooks.d.ts +20 -0
  54. package/dist/policy/host-hooks.js +121 -2
  55. package/dist/policy/org-force-on-migration.js +2 -1
  56. package/dist/policy/plan-extensions.d.ts +1 -1
  57. package/dist/policy/plan-extensions.js +2 -0
  58. package/dist/policy/product-signal.js +6 -7
  59. package/dist/policy/require-human-merge.js +8 -6
  60. package/dist/policy/resolve.d.ts +7 -2
  61. package/dist/policy/resolve.js +25 -8
  62. package/dist/policy/runtime-authority.d.ts +18 -0
  63. package/dist/policy/runtime-authority.js +11 -0
  64. package/dist/policy/value-feedback.js +7 -8
  65. package/dist/policy/write-fence.js +3 -0
  66. package/dist/preflight-cache/evaluate.d.ts +12 -0
  67. package/dist/preflight-cache/evaluate.js +24 -4
  68. package/dist/preflight-cache/index.d.ts +1 -1
  69. package/dist/preflight-cache/index.js +1 -1
  70. package/dist/product-first-done-gate/evaluate.d.ts +4 -0
  71. package/dist/product-first-done-gate/evaluate.js +82 -10
  72. package/dist/release/closed-verb-gate.d.ts +12 -0
  73. package/dist/release/closed-verb-gate.js +51 -0
  74. package/dist/release/index.d.ts +1 -0
  75. package/dist/release/index.js +1 -0
  76. package/dist/release/pipeline.js +7 -0
  77. package/dist/release/types.d.ts +12 -0
  78. package/dist/render/constants.d.ts +2 -1
  79. package/dist/render/constants.js +2 -1
  80. package/dist/render/framework-commands.js +4 -0
  81. package/dist/render/spec-validate.js +23 -12
  82. package/dist/run-summary/types.d.ts +4 -0
  83. package/dist/scm/design-critique-chip.d.ts +37 -0
  84. package/dist/scm/design-critique-chip.js +171 -0
  85. package/dist/scm/index.d.ts +1 -0
  86. package/dist/scm/index.js +1 -0
  87. package/dist/scm/main.d.ts +3 -0
  88. package/dist/scm/main.js +15 -1
  89. package/dist/scm/readiness-cli.d.ts +2 -0
  90. package/dist/scm/readiness-cli.js +42 -1
  91. package/dist/scm/readiness.d.ts +2 -1
  92. package/dist/scm/readiness.js +7 -1
  93. package/dist/scope/decompose.js +2 -1
  94. package/dist/scope/delivery-evidence.d.ts +27 -1
  95. package/dist/scope/index.d.ts +1 -0
  96. package/dist/scope/index.js +1 -0
  97. package/dist/scope/lifecycle-write.d.ts +24 -0
  98. package/dist/scope/lifecycle-write.js +64 -0
  99. package/dist/scope/main.js +18 -6
  100. package/dist/scope/promote-from-issue.js +5 -5
  101. package/dist/scope/transition.js +90 -11
  102. package/dist/scope-provenance/evaluate.js +8 -4
  103. package/dist/session/ac-pass-banking.d.ts +5 -0
  104. package/dist/session/ac-pass-banking.js +3 -2
  105. package/dist/session/git.d.ts +18 -0
  106. package/dist/session/git.js +81 -1
  107. package/dist/session/occupancy.d.ts +2 -1
  108. package/dist/session/occupancy.js +11 -4
  109. package/dist/session/process-cost.d.ts +32 -10
  110. package/dist/session/process-cost.js +189 -1
  111. package/dist/session/product-state-hash.js +41 -24
  112. package/dist/session/ritual-entrypoint.js +16 -1
  113. package/dist/session/session-start.d.ts +18 -0
  114. package/dist/session/session-start.js +49 -2
  115. package/dist/session/verify-session-ritual.d.ts +46 -4
  116. package/dist/session/verify-session-ritual.js +118 -10
  117. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  118. package/dist/swarm/finalize-cohort-cli.js +165 -35
  119. package/dist/swarm/finalize-cohort.d.ts +1 -0
  120. package/dist/swarm/finalize-cohort.js +14 -2
  121. package/dist/swarm/launch.d.ts +69 -0
  122. package/dist/swarm/launch.js +191 -1
  123. package/dist/swarm/routing-set-cli.js +2 -0
  124. package/dist/swarm/routing-verify.d.ts +1 -1
  125. package/dist/swarm/routing-verify.js +2 -2
  126. package/dist/triage/actions/index.d.ts +4 -0
  127. package/dist/triage/actions/index.js +6 -0
  128. package/dist/triage/bootstrap/index.js +6 -6
  129. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  130. package/dist/triage/evaluate/evaluate.js +169 -0
  131. package/dist/triage/evaluate/github.d.ts +16 -0
  132. package/dist/triage/evaluate/github.js +153 -0
  133. package/dist/triage/evaluate/index.d.ts +9 -0
  134. package/dist/triage/evaluate/index.js +8 -0
  135. package/dist/triage/evaluate/paths.d.ts +7 -0
  136. package/dist/triage/evaluate/paths.js +25 -0
  137. package/dist/triage/evaluate/sink.d.ts +5 -0
  138. package/dist/triage/evaluate/sink.js +52 -0
  139. package/dist/triage/evaluate/types.d.ts +93 -0
  140. package/dist/triage/evaluate/types.js +13 -0
  141. package/dist/triage/evaluate/validity.d.ts +8 -0
  142. package/dist/triage/evaluate/validity.js +87 -0
  143. package/dist/triage/evaluate/value.d.ts +12 -0
  144. package/dist/triage/evaluate/value.js +33 -0
  145. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  146. package/dist/triage/evaluate/wip-census.js +39 -0
  147. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  148. package/dist/triage/evaluate/worktrees.js +68 -0
  149. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  150. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  151. package/dist/triage/help/registry-data.d.ts +22 -8
  152. package/dist/triage/help/registry-data.js +54 -6
  153. package/dist/triage/index.d.ts +1 -0
  154. package/dist/triage/index.js +1 -0
  155. package/dist/triage/welcome/writers.d.ts +1 -1
  156. package/dist/triage/welcome/writers.js +10 -5
  157. package/dist/value/feedback-file.d.ts +17 -2
  158. package/dist/value/feedback-file.js +80 -6
  159. package/dist/value/readback.d.ts +4 -1
  160. package/dist/value/readback.js +51 -12
  161. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  162. package/dist/vbrief-reconcile/labels.js +30 -0
  163. package/dist/vbrief-validate/schema.d.ts +4 -0
  164. package/dist/vbrief-validate/schema.js +1 -1
  165. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  166. package/dist/vbrief-validation/story-quality.js +8 -1
  167. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  168. package/dist/verify-env/agent-hook-readiness.js +12 -8
  169. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  170. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  171. package/dist/verify-env/agent-hooks.js +3 -4
  172. package/dist/xbrief-migrate/agents-header.js +69 -7
  173. package/package.json +7 -3
@@ -9,11 +9,12 @@
9
9
  */
10
10
  import { existsSync, readFileSync } from "node:fs";
11
11
  import { basename, isAbsolute, relative, resolve } from "node:path";
12
- import { emitAcceptanceStampFromPlan } from "../intake/clause-derivation.js";
13
- import { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPlan, isNoopRefusalReason, runLiteralAcceptanceCommands, stripLiteralAcceptanceAdvisory, } from "../literal-acceptance/index.js";
12
+ import { emitAcceptanceStampFromPlan, MISSING_AMBIGUITY_ATTESTATION_CAUSE, stampedAmbiguityAttestationError, } from "../intake/clause-derivation.js";
13
+ import { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPlan, isExecutableLiteralSource, isInlineProseMention, isNoopRefusalReason, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, runLiteralAcceptanceCommands, stripLiteralAcceptanceAdvisory, } from "../literal-acceptance/index.js";
14
14
  import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
15
15
  import { maybeBankOnAcPass } from "../session/ac-pass-banking.js";
16
16
  import { resolveAcReuse, resolveScopeIdForAcReuse, snapshotFromReuseFields, } from "../session/ac-pass-reuse.js";
17
+ import { gitHead } from "../session/git.js";
17
18
  import { hashProductState } from "../session/product-state-hash.js";
18
19
  import { resolveVerifyAcSessionId, writeVerifyAcSessionCache, } from "../session/verify-ac-session-cache.js";
19
20
  import { formatClauseWalkMessage, walkAcceptanceClauses, } from "../verify-ac/clauses.js";
@@ -162,6 +163,24 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
162
163
  }, optionsWithScope, plan);
163
164
  }
164
165
  const projectRoot = resolve(optionsWithScope.projectRoot ?? process.cwd());
166
+ const attestationError = stampedAmbiguityAttestationError(optionsWithScope.observedAcceptance !== undefined
167
+ ? optionsWithScope.observedAcceptance
168
+ : plan.acceptance);
169
+ if (attestationError !== null) {
170
+ return applyOracle({
171
+ ok: false,
172
+ code: 2,
173
+ message: optionsWithScope.quiet === true ? "" : attestationError.message,
174
+ commands: [],
175
+ runs: [],
176
+ sourceRung: acceptance.source_rung,
177
+ noneStated: acceptance.none_stated,
178
+ acceptance,
179
+ resolution: "config",
180
+ resolvedCommandCount: 0,
181
+ cause: attestationError.cause ?? MISSING_AMBIGUITY_ATTESTATION_CAUSE,
182
+ }, optionsWithScope, plan);
183
+ }
165
184
  const reused = tryReuseVerifyAc(plan, acceptance, optionsWithScope, projectRoot);
166
185
  if (reused !== null) {
167
186
  return applyOracle(reused, optionsWithScope, plan);
@@ -186,9 +205,10 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
186
205
  // the #3267 ledger is a parallel store and can be empty while stated commands exist.
187
206
  // Stated was previously excluded, so rung=stated + empty ledger printed "nothing to run".
188
207
  // Do not override a blocking rejected ledger or a config error.
189
- if (shouldRunPlanAcceptanceDirectly(base, acceptance)) {
208
+ if (shouldRunPlanAcceptanceDirectly(base, acceptance, plan)) {
190
209
  const runner = optionsWithScope.runner;
191
- const direct = runLiteralAcceptanceCommands(acceptance.commands.map((c) => ({
210
+ const directCommands = statedAcceptanceCommands(acceptance, plan);
211
+ const direct = runLiteralAcceptanceCommands(directCommands.map((c) => ({
192
212
  command: c.command,
193
213
  cwd: c.cwd ?? null,
194
214
  expectedStdout: c.expectedStdout ?? null,
@@ -252,8 +272,38 @@ export function evaluateVerifyAcFromPlan(plan, options = {}) {
252
272
  }
253
273
  return applyOracle(annotate(base, acceptance, optionsWithScope.quiet), optionsWithScope, plan);
254
274
  }
255
- function shouldRunPlanAcceptanceDirectly(base, acceptance) {
256
- if (acceptance.commands.length === 0)
275
+ function hasNonDefaultAcceptanceContext(command) {
276
+ const cwd = command.cwd !== undefined && command.cwd !== null && String(command.cwd).trim();
277
+ const stdout = command.expectedStdout !== undefined &&
278
+ command.expectedStdout !== null &&
279
+ String(command.expectedStdout).length > 0;
280
+ const exit = typeof command.expectedExitCode === "number" && command.expectedExitCode !== 0;
281
+ return Boolean(cwd) || stdout || exit;
282
+ }
283
+ function hasStructuredAcceptancePeer(plan, command) {
284
+ return readStoredLiteralAcceptanceCommands(plan).some((c) => c.command === command && isExecutableLiteralSource(c.source));
285
+ }
286
+ /**
287
+ * True when a plan.acceptance.commands row is only an ingest copy of an inline
288
+ * prose mention (or an operator not-command disposition) and has no structured
289
+ * peer. Independently authored rows — promoted verify_commands, or a command
290
+ * with its own cwd/expectedStdout/exit — still run (#3721 Greptile).
291
+ */
292
+ function isInlineScrapedAcceptanceCommand(plan, command) {
293
+ if (hasNonDefaultAcceptanceContext(command))
294
+ return false;
295
+ if (hasStructuredAcceptancePeer(plan, command.command))
296
+ return false;
297
+ if (readNotAcceptanceCommands(plan).has(command.command))
298
+ return true;
299
+ return readStoredLiteralAcceptanceCommands(plan).some((c) => c.command === command.command && isInlineProseMention(c));
300
+ }
301
+ /** plan.acceptance.commands minus ingest-copied inline mentions (#3721). */
302
+ function statedAcceptanceCommands(acceptance, plan) {
303
+ return acceptance.commands.filter((c) => !isInlineScrapedAcceptanceCommand(plan, c));
304
+ }
305
+ function shouldRunPlanAcceptanceDirectly(base, acceptance, plan) {
306
+ if (statedAcceptanceCommands(acceptance, plan).length === 0)
257
307
  return false;
258
308
  if (base.runs.length > 0)
259
309
  return false;
@@ -372,6 +422,8 @@ function emitAcceptanceOutcome(result, options, projectRoot) {
372
422
  outcome: row.outcome,
373
423
  })),
374
424
  served_from: result.servedFrom ?? "executed",
425
+ ...(result.cause !== undefined ? { cause: result.cause } : {}),
426
+ miss_reason: (result.servedFrom ?? "executed") === "executed" ? result.missReason : undefined,
375
427
  });
376
428
  }
377
429
  catch {
@@ -524,10 +576,27 @@ function applyOracle(result, options, plan = {}) {
524
576
  next = { ...next, resolution: "fail" };
525
577
  }
526
578
  }
579
+ const servedFrom = next.servedFrom ?? "executed";
580
+ let missReason = next.missReason;
581
+ if (servedFrom === "executed" && (missReason === undefined || missReason.length === 0)) {
582
+ const reuse = resolveAcReuse({
583
+ projectRoot,
584
+ plan,
585
+ scopeId: options.bankScopeId,
586
+ sessionId: options.sessionId,
587
+ env: options.env,
588
+ productPaths: options.productPaths,
589
+ allowCache: (options.reuseMode ?? "auto") === "auto",
590
+ allowBank: true,
591
+ });
592
+ if (reuse.kind === "miss")
593
+ missReason = reuse.reason;
594
+ }
527
595
  const stamped = {
528
596
  ...next,
529
597
  message: options.quiet === true ? next.message : labelVerdict(next),
530
- servedFrom: next.servedFrom ?? "executed",
598
+ servedFrom,
599
+ missReason: servedFrom === "executed" ? missReason : undefined,
531
600
  };
532
601
  persistVerifyAcSessionCache(stamped, options, projectRoot, plan);
533
602
  if (options.skipAcceptanceEmit !== true) {
@@ -634,14 +703,14 @@ export function resolveOracleScopeKey(plan, xbriefPath, projectRoot) {
634
703
  return rel;
635
704
  }
636
705
  /**
637
- * After executable AC pass, FINALIZE the banking checkpoint (#3285).
638
- * Soft/advisory passes with zero runs do not bank. Fail-open on ledger errors.
706
+ * After verified-pass, FINALIZE the banking checkpoint (#3285 / #3558).
707
+ * Soft/advisory empty-pass still skips. Ledger I/O failures fail closed.
639
708
  */
640
709
  function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
641
710
  if (options.bankOnPass === false) {
642
711
  return result;
643
712
  }
644
- if (!result.ok || result.runs.length === 0) {
713
+ if (!result.ok || result.resolution !== "verified-pass") {
645
714
  return result;
646
715
  }
647
716
  const projectRoot = resolve(options.projectRoot ?? process.cwd());
@@ -661,8 +730,11 @@ function maybeAttachAcPassBank(result, plan, xbriefPath, options) {
661
730
  projectRoot,
662
731
  scopeId,
663
732
  executableRuns: result.runs.length,
733
+ verifiedPass: true,
664
734
  quiet: options.quiet,
665
735
  productStateHash: hashed.complete ? hashed.digest : null,
736
+ environ: options.env,
737
+ headSha: gitHead(projectRoot).head,
666
738
  });
667
739
  if (options.quiet || banked.notes.length === 0) {
668
740
  return result;
@@ -0,0 +1,12 @@
1
+ import type { ClosedVerbDecision } from "../authz/types.js";
2
+ import type { ReleaseConfig, ReleaseSeams } from "./types.js";
3
+ export declare const TAG_PUSH_CLOSED_VERB: "release-publish";
4
+ export declare function evaluateReleaseTagPushGate(version: string, projectRoot: string, seams: ReleaseSeams, repo: string): ClosedVerbDecision;
5
+ /**
6
+ * Fail closed before git tag + atomic tag push when skipTag is false.
7
+ * Dry-run and `--skip-tag` do not require a grant (no npm distribution).
8
+ * Returns EXIT_OK to continue, EXIT_VIOLATION to halt. Never spends the grant
9
+ * — draft-flip `release:publish` still owns markGrantUsed (#1095).
10
+ */
11
+ export declare function assertTagPushClosedVerb(config: ReleaseConfig, seams: ReleaseSeams): number;
12
+ //# sourceMappingURL=closed-verb-gate.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Closed-verb gate on the tag-push / npm-publish boundary (#3527).
3
+ *
4
+ * Reuses the draft-flip evaluator (`evaluateReleasePublishGate`) so the
5
+ * distributing step carries the same authz tier. Does not delete or weaken
6
+ * the later `release:publish` draft-flip check.
7
+ */
8
+ import { closedVerbEnvBypassKey } from "../authz/closed-verb.js";
9
+ import { evaluateReleasePublishGate } from "../release-publish/pipeline.js";
10
+ import { EXIT_OK, EXIT_VIOLATION, TOTAL_STEPS } from "./constants.js";
11
+ function emitGate(label, status) {
12
+ process.stderr.write(`[10/${TOTAL_STEPS}] ${label}... ${status}\n`);
13
+ }
14
+ export const TAG_PUSH_CLOSED_VERB = "release-publish";
15
+ export function evaluateReleaseTagPushGate(version, projectRoot, seams, repo) {
16
+ return evaluateReleasePublishGate(version, projectRoot, {
17
+ grants: seams.closedVerbGrants,
18
+ env: seams.closedVerbEnv,
19
+ repo,
20
+ });
21
+ }
22
+ /**
23
+ * Fail closed before git tag + atomic tag push when skipTag is false.
24
+ * Dry-run and `--skip-tag` do not require a grant (no npm distribution).
25
+ * Returns EXIT_OK to continue, EXIT_VIOLATION to halt. Never spends the grant
26
+ * — draft-flip `release:publish` still owns markGrantUsed (#1095).
27
+ */
28
+ export function assertTagPushClosedVerb(config, seams) {
29
+ if (config.skipTag) {
30
+ return EXIT_OK;
31
+ }
32
+ const tag = `v${config.version}`;
33
+ const label = `Closed-verb gate ${TAG_PUSH_CLOSED_VERB} ${tag} (tag-push / npm-publish)`;
34
+ if (config.dryRun) {
35
+ emitGate(label, "DRYRUN (would require human-origin grant or DEFT_ALLOW_RELEASE_PUBLISH=1 before tag push)");
36
+ return EXIT_OK;
37
+ }
38
+ const gate = evaluateReleaseTagPushGate(config.version, config.projectRoot, seams, config.repo);
39
+ if (!gate.allowed) {
40
+ const bypassKey = gate.envBypassKey ??
41
+ closedVerbEnvBypassKey(TAG_PUSH_CLOSED_VERB, config.projectRoot) ??
42
+ "DEFT_ALLOW_RELEASE_PUBLISH";
43
+ emitGate(label, `FAIL (${gate.code}: ${gate.reason})`);
44
+ process.stderr.write(`[release] denied code=${gate.code} — set ${bypassKey}=1 or ` +
45
+ `mint \`deft authz:grant -- --template ${TAG_PUSH_CLOSED_VERB} --target ${config.version}\`\n`);
46
+ return EXIT_VIOLATION;
47
+ }
48
+ emitGate(label, `OK (${gate.code}${gate.humanApprovalRef !== null ? ` grant=${gate.humanApprovalRef}` : ""})`);
49
+ return EXIT_OK;
50
+ }
51
+ //# sourceMappingURL=closed-verb-gate.js.map
@@ -1,5 +1,6 @@
1
1
  export * from "./auto-hatch.js";
2
2
  export * from "./build-dist.js";
3
+ export * from "./closed-verb-gate.js";
3
4
  export * from "./constants.js";
4
5
  export * from "./coverage-debt-ledger.js";
5
6
  export * from "./flags.js";
@@ -1,6 +1,7 @@
1
1
  /* v8 ignore file -- re-export barrel; covered via main.ts */
2
2
  export * from "./auto-hatch.js";
3
3
  export * from "./build-dist.js";
4
+ export * from "./closed-verb-gate.js";
4
5
  export * from "./constants.js";
5
6
  export * from "./coverage-debt-ledger.js";
6
7
  export * from "./flags.js";
@@ -4,6 +4,7 @@ import { assertProjectionContained } from "../fs/projection-containment.js";
4
4
  import { readCoverageTotalsFromReport } from "../vitest-runner/coverage-debt.js";
5
5
  import { buildCoverageDebtIssueDraft, classifyStep5FailureWithFreshness, evaluateAutoHatch, formatAutoHatchBanner, parseExitCodeFromReason, reasonLooksLikeTimeout, } from "./auto-hatch.js";
6
6
  import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
7
+ import { assertTagPushClosedVerb } from "./closed-verb-gate.js";
7
8
  import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, RELEASE_CHECK_TIMEOUT_MINUTES, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
8
9
  import { createCoverageDebtIssue, probeOpenCoverageDebtLedger } from "./coverage-debt-ledger.js";
9
10
  import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
@@ -373,6 +374,12 @@ export function runPipeline(config, seams = {}) {
373
374
  return EXIT_VIOLATION;
374
375
  }
375
376
  }
377
+ // Step 10–11 boundary: closed-verb gate before tag + atomic push (#3527).
378
+ // npm publish fires on tag push; draft-flip `release:publish` stays (#1095).
379
+ const tagPushGate = assertTagPushClosedVerb(config, seams);
380
+ if (tagPushGate !== EXIT_OK) {
381
+ return tagPushGate;
382
+ }
376
383
  // Step 10: git tag.
377
384
  label = `Tag v${version}`;
378
385
  if (config.skipTag) {
@@ -1,3 +1,5 @@
1
+ import type { EnvMap } from "../authz/closed-verb.js";
2
+ import type { HumanOriginGrant } from "../authz/types.js";
1
3
  export interface ReleaseConfig {
2
4
  readonly version: string;
3
5
  readonly repo: string;
@@ -77,5 +79,15 @@ export interface ReleaseSeams {
77
79
  readonly headSha?: (projectRoot: string) => string | null;
78
80
  /** #3187 — CI detector; when true suite stamp is never trusted. */
79
81
  readonly isCi?: () => boolean;
82
+ /**
83
+ * #3527 — closed-verb grants for the tag-push / npm-publish gate.
84
+ * When omitted, production loads active human-origin grants from disk.
85
+ */
86
+ readonly closedVerbGrants?: readonly HumanOriginGrant[];
87
+ /**
88
+ * #3527 — env map for DEFT_ALLOW_RELEASE_PUBLISH. When omitted, process.env.
89
+ * Tests inject `{}` so a host env bypass cannot leak into fail-closed cases.
90
+ */
91
+ readonly closedVerbEnv?: EnvMap;
80
92
  }
81
93
  //# sourceMappingURL=types.d.ts.map
@@ -1,6 +1,7 @@
1
1
  /** Canonical v0.6 status enum (matches `scripts/spec_validate.py`). */
2
2
  export declare const VALID_STATUSES: Set<string>;
3
- export declare const VALID_VBRIEF_VERSIONS: Set<string>;
3
+ export { VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
4
+ export { resolveInfoBlock } from "../vbrief-validate/schema.js";
4
5
  export declare const SPECIFICATION_NARRATIVE_KEY_ORDER: readonly ["Overview", "ProblemStatement", "Goals", "UserStories", "Requirements", "SuccessMetrics", "EdgeCases", "Architecture", "TechDecisions", "ImplementationPhases", "PreImplementationGates"];
5
6
  /** Migration capture narrative; omitted from default compact SPECIFICATION.md (#1566). */
6
7
  export declare const LEGACY_ARTIFACTS_NARRATIVE_KEY = "LegacyArtifacts";
@@ -10,7 +10,8 @@ export const VALID_STATUSES = new Set([
10
10
  "failed",
11
11
  "cancelled",
12
12
  ]);
13
- export const VALID_VBRIEF_VERSIONS = new Set(["0.6"]);
13
+ export { VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
14
+ export { resolveInfoBlock } from "../vbrief-validate/schema.js";
14
15
  export const SPECIFICATION_NARRATIVE_KEY_ORDER = [
15
16
  "Overview",
16
17
  "ProblemStatement",
@@ -85,6 +85,9 @@ export const COMMANDS = {
85
85
  "verify:orphan-active": spec("verify:orphan-active", "verify_orphan_active:main", {
86
86
  projectRootArg: "--project-root",
87
87
  }),
88
+ "verify:lifecycle-visible": spec("verify:lifecycle-visible", "verify_lifecycle_visible:main", {
89
+ projectRootArg: "--project-root",
90
+ }),
88
91
  "verify:completed-tracked": spec("verify:completed-tracked", "verify_completed_tracked:main", {
89
92
  projectRootArg: "--project-root",
90
93
  }),
@@ -302,6 +305,7 @@ const ENTRYPOINT_VERB = {
302
305
  "preflight_cache:main": "preflight-cache",
303
306
  "preflight_wip_cap:main": "verify-wip-cap",
304
307
  "verify_orphan_active:main": "verify-orphan-active",
308
+ "verify_lifecycle_visible:main": "verify-lifecycle-visible",
305
309
  "verify_completed_tracked:main": "verify-completed-tracked",
306
310
  "pack_render:main": "pack-render",
307
311
  "validate_strategy_output:main": "validate-strategy-output",
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { basename } from "node:path";
3
- import { VALID_STATUSES, VALID_VBRIEF_VERSIONS } from "./constants.js";
3
+ import { VALID_INFO_ROOT_KEYS, VALID_VBRIEF_VERSIONS } from "../vbrief-validate/constants.js";
4
+ import { resolveInfoBlock } from "../vbrief-validate/schema.js";
5
+ import { VALID_STATUSES } from "./constants.js";
4
6
  function validateNarratives(narratives, path, errors) {
5
7
  if (typeof narratives !== "object" || narratives === null || Array.isArray(narratives)) {
6
8
  errors.push(`${path} must be an object`);
@@ -46,19 +48,28 @@ function validatePlanItem(item, path, errors) {
46
48
  }
47
49
  function validateSchema(data) {
48
50
  const errors = [];
49
- if (!("vBRIEFInfo" in data)) {
50
- errors.push("missing required top-level key 'vBRIEFInfo'");
51
+ const resolved = resolveInfoBlock(data);
52
+ if (resolved === null) {
53
+ let infoShapeError = false;
54
+ for (const key of VALID_INFO_ROOT_KEYS) {
55
+ if (!(key in data))
56
+ continue;
57
+ const info = data[key];
58
+ if (info === null || Array.isArray(info) || typeof info !== "object") {
59
+ errors.push(`'${key}' must be an object`);
60
+ infoShapeError = true;
61
+ break;
62
+ }
63
+ }
64
+ if (!infoShapeError) {
65
+ errors.push("missing required top-level key 'vBRIEFInfo' or 'xBRIEFInfo'");
66
+ }
51
67
  }
52
68
  else {
53
- const info = data.vBRIEFInfo;
54
- if (typeof info !== "object" || info === null || Array.isArray(info)) {
55
- errors.push("'vBRIEFInfo' must be an object");
56
- }
57
- else {
58
- const version = info.version;
59
- if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
60
- errors.push(`'vBRIEFInfo.version' must be '0.6' (canonical v0.6 schema, #533), got ${JSON.stringify(version)}. Run \`task migrate:vbrief\` to upgrade pre-existing v0.5 vBRIEFs in-place.`);
61
- }
69
+ const version = resolved.info.version;
70
+ if (!VALID_VBRIEF_VERSIONS.has(String(version))) {
71
+ const allowed = [...VALID_VBRIEF_VERSIONS].map((v) => `'${v}'`).join(", ");
72
+ errors.push(`'${resolved.key}.version' must be one of ${allowed} (canonical v0.6/v0.8 schema, #2107), got ${JSON.stringify(version)}. Run \`task migrate:xbrief\` to upgrade pre-existing v0.5 vBRIEFs in-place.`);
62
73
  }
63
74
  }
64
75
  if (!("plan" in data)) {
@@ -128,6 +128,10 @@ export interface AcceptanceRunSummaryPayload {
128
128
  readonly clause_outcomes?: readonly AcceptanceClauseOutcomeRow[];
129
129
  /** bank | cache | executed — how verify:ac obtained this result (#3387). */
130
130
  readonly served_from?: "bank" | "cache" | "executed";
131
+ /** Config-error cause when outcome is config-error (#3559). */
132
+ readonly cause?: string;
133
+ /** Reuse-gate miss cause when served_from is executed (#3558). */
134
+ readonly miss_reason?: string;
131
135
  }
132
136
  /** Intake-time stamp: which rung locked, whether commands were stated, counts (#3323). */
133
137
  export interface AcceptanceStampRunSummaryPayload {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Chip-only remaining-set write for design-critique catalog labels (#3642).
3
+ *
4
+ * Parent attach of triage-ready / recut mechanism-shaped MUST use this verb,
5
+ * not `gh api POST .../labels` and not additive `scm:issue:edit --add-label`.
6
+ */
7
+ import { type DesignCritiqueCatalogChip } from "../design-critique/exclusive-chip.js";
8
+ import type { LabelClient } from "../vbrief-reconcile/types.js";
9
+ export declare const DESIGN_CRITIQUE_CHIP_VERB: "design-critique-chip";
10
+ export declare const DESIGN_CRITIQUE_CHIP_USAGE: string;
11
+ export interface DesignCritiqueChipArgs {
12
+ readonly issue: number;
13
+ readonly chip: DesignCritiqueCatalogChip;
14
+ readonly repo: string | null;
15
+ readonly json: boolean;
16
+ }
17
+ export interface DesignCritiqueChipSeams {
18
+ readonly client?: LabelClient;
19
+ /** Default OWNER/NAME when --repo is omitted (git origin). */
20
+ readonly resolveDefaultRepo?: () => string | null;
21
+ }
22
+ export interface DesignCritiqueChipResult {
23
+ readonly exitCode: number;
24
+ readonly stdout: string;
25
+ readonly stderr: string;
26
+ }
27
+ /** Fail-closed chip name. Closed catalog only. */
28
+ export declare function resolveDesignCritiqueChipArg(raw: string): DesignCritiqueCatalogChip;
29
+ export declare function parseDesignCritiqueChipArgs(extra: readonly string[]): DesignCritiqueChipArgs;
30
+ /** Resolve OWNER/NAME from `git remote get-url origin`. */
31
+ export declare function resolveRepoFromGitOrigin(): string | null;
32
+ /**
33
+ * GET current labels, remaining-set replace via applyDesignCritiqueCatalogChip.
34
+ * One LabelClient.apply write. Other facets stay.
35
+ */
36
+ export declare function runDesignCritiqueChip(extra: readonly string[], seams?: DesignCritiqueChipSeams): DesignCritiqueChipResult;
37
+ //# sourceMappingURL=design-critique-chip.d.ts.map
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Chip-only remaining-set write for design-critique catalog labels (#3642).
3
+ *
4
+ * Parent attach of triage-ready / recut mechanism-shaped MUST use this verb,
5
+ * not `gh api POST .../labels` and not additive `scm:issue:edit --add-label`.
6
+ */
7
+ import { spawnSync } from "node:child_process";
8
+ import { applyDesignCritiqueCatalogChip, } from "../design-critique/exclusive-chip.js";
9
+ import { parseGithubOwnerRepo } from "../policy/sync-default.js";
10
+ import { ScmLabelClient, ScmLabelError } from "../vbrief-reconcile/labels.js";
11
+ import { extractFlag, extractValueFlag } from "./argv.js";
12
+ import { InvalidRepoError, splitRepo } from "./gh-rest.js";
13
+ import { pyRepr } from "./py-format.js";
14
+ export const DESIGN_CRITIQUE_CHIP_VERB = "design-critique-chip";
15
+ export const DESIGN_CRITIQUE_CHIP_USAGE = "usage: scm issue design-critique-chip --issue N --chip triage-ready|mechanism-shaped [--repo OWNER/NAME] [--json]\n" +
16
+ " Parent attach of design-critique:triage-ready / recut mechanism-shaped.\n" +
17
+ " Closed catalog remaining-set replace. One write. Other facets stay.\n";
18
+ const CHIP_ALIASES = {
19
+ "triage-ready": "design-critique:triage-ready",
20
+ "mechanism-shaped": "design-critique:mechanism-shaped",
21
+ "design-critique:triage-ready": "design-critique:triage-ready",
22
+ "design-critique:mechanism-shaped": "design-critique:mechanism-shaped",
23
+ };
24
+ /** Fail-closed chip name. Closed catalog only. */
25
+ export function resolveDesignCritiqueChipArg(raw) {
26
+ const chip = CHIP_ALIASES[raw];
27
+ if (chip === undefined) {
28
+ throw new Error(`unknown design-critique chip ${pyRepr(raw)}; expected triage-ready|mechanism-shaped ` +
29
+ "(or design-critique:triage-ready|design-critique:mechanism-shaped)");
30
+ }
31
+ return chip;
32
+ }
33
+ function parseIssueNumber(raw, source) {
34
+ const issueN = Number.parseInt(raw, 10);
35
+ if (Number.isNaN(issueN) || issueN <= 0 || String(issueN) !== raw) {
36
+ throw new Error(`${source} must be a positive integer; got ${pyRepr(raw)}`);
37
+ }
38
+ return issueN;
39
+ }
40
+ export function parseDesignCritiqueChipArgs(extra) {
41
+ let remainder = [...extra];
42
+ const [help] = extractFlag(remainder, "--help");
43
+ const [helpShort] = extractFlag(remainder, "-h");
44
+ if (help || helpShort) {
45
+ throw new ChipUsageError(DESIGN_CRITIQUE_CHIP_USAGE.trimEnd());
46
+ }
47
+ const [json, afterJson] = extractFlag(remainder, "--json");
48
+ remainder = afterJson;
49
+ const [repoRaw, afterRepo] = extractValueFlag(remainder, "--repo");
50
+ remainder = afterRepo;
51
+ const [chipRaw, afterChip] = extractValueFlag(remainder, "--chip");
52
+ remainder = afterChip;
53
+ const [issueFlag, afterIssue] = extractValueFlag(remainder, "--issue");
54
+ remainder = afterIssue;
55
+ const leftoverFlags = remainder.filter((t) => t.startsWith("-"));
56
+ if (leftoverFlags.length > 0) {
57
+ throw new Error(`unrecognized flags: ${pyRepr(leftoverFlags)}. Supported: --issue, --chip, --repo, --json.`);
58
+ }
59
+ const positionals = remainder.filter((t) => !t.startsWith("-"));
60
+ if (chipRaw === null || chipRaw.length === 0) {
61
+ throw new Error("missing --chip triage-ready|mechanism-shaped");
62
+ }
63
+ const chip = resolveDesignCritiqueChipArg(chipRaw);
64
+ let issueRaw = issueFlag;
65
+ if (positionals.length > 1) {
66
+ throw new Error(`expected at most one positional issue number; got ${pyRepr(positionals)}`);
67
+ }
68
+ if (positionals.length === 1) {
69
+ const positional = positionals[0] ?? "";
70
+ if (issueRaw !== null && issueRaw !== positional) {
71
+ throw new Error(`--issue ${pyRepr(issueRaw)} conflicts with positional ${pyRepr(positional)}`);
72
+ }
73
+ issueRaw = positional;
74
+ }
75
+ if (issueRaw === null || issueRaw.length === 0) {
76
+ throw new Error("missing --issue N");
77
+ }
78
+ const issue = parseIssueNumber(issueRaw, "--issue");
79
+ if (repoRaw !== null && repoRaw.length > 0) {
80
+ splitRepo(repoRaw);
81
+ }
82
+ return { issue, chip, repo: repoRaw !== null && repoRaw.length > 0 ? repoRaw : null, json };
83
+ }
84
+ /** Resolve OWNER/NAME from `git remote get-url origin`. */
85
+ export function resolveRepoFromGitOrigin() {
86
+ const result = spawnSync("git", ["remote", "get-url", "origin"], {
87
+ encoding: "utf8",
88
+ env: process.env,
89
+ });
90
+ if (result.status !== 0)
91
+ return null;
92
+ const stdout = typeof result.stdout === "string" ? result.stdout : "";
93
+ return parseGithubOwnerRepo(stdout);
94
+ }
95
+ class ChipUsageError extends Error {
96
+ constructor(message) {
97
+ super(message);
98
+ this.name = "ChipUsageError";
99
+ }
100
+ }
101
+ /**
102
+ * GET current labels, remaining-set replace via applyDesignCritiqueCatalogChip.
103
+ * One LabelClient.apply write. Other facets stay.
104
+ */
105
+ export function runDesignCritiqueChip(extra, seams = {}) {
106
+ let args;
107
+ try {
108
+ args = parseDesignCritiqueChipArgs(extra);
109
+ }
110
+ catch (err) {
111
+ if (err instanceof ChipUsageError) {
112
+ return { exitCode: 0, stdout: `${err.message}\n`, stderr: "" };
113
+ }
114
+ if (err instanceof InvalidRepoError) {
115
+ return { exitCode: 2, stdout: "", stderr: `error: invalid --repo value: ${err.message}\n` };
116
+ }
117
+ const message = err instanceof Error ? err.message : String(err);
118
+ return { exitCode: 2, stdout: "", stderr: `error: ${message}\n` };
119
+ }
120
+ const repo = args.repo ?? (seams.resolveDefaultRepo ?? resolveRepoFromGitOrigin)();
121
+ if (repo === null || repo.length === 0) {
122
+ return {
123
+ exitCode: 2,
124
+ stdout: "",
125
+ stderr: "error: missing --repo OWNER/NAME (could not resolve from git origin)\n",
126
+ };
127
+ }
128
+ try {
129
+ splitRepo(repo);
130
+ }
131
+ catch (err) {
132
+ if (err instanceof InvalidRepoError) {
133
+ return { exitCode: 2, stdout: "", stderr: `error: invalid --repo value: ${err.message}\n` };
134
+ }
135
+ throw err;
136
+ }
137
+ const client = seams.client ?? new ScmLabelClient();
138
+ try {
139
+ const applied = applyDesignCritiqueCatalogChip(client, repo, args.issue, args.chip);
140
+ const payload = {
141
+ repo,
142
+ issue: args.issue,
143
+ chip: args.chip,
144
+ add: [...applied.add],
145
+ remove: [...applied.remove],
146
+ remaining: [...applied.remaining],
147
+ };
148
+ if (args.json) {
149
+ return { exitCode: 0, stdout: `${JSON.stringify(payload)}\n`, stderr: "" };
150
+ }
151
+ const wrote = applied.add.length > 0 || applied.remove.length > 0;
152
+ const detail = applied.remove.length > 0
153
+ ? `removed ${applied.remove.join(", ")}`
154
+ : wrote
155
+ ? "added"
156
+ : "already exclusive";
157
+ return {
158
+ exitCode: 0,
159
+ stdout: `${wrote ? "applied" : "unchanged"} ${args.chip} on ${repo}#${args.issue} (${detail})\n`,
160
+ stderr: "",
161
+ };
162
+ }
163
+ catch (err) {
164
+ if (err instanceof ScmLabelError) {
165
+ return { exitCode: 1, stdout: "", stderr: `error: ${err.message}\n` };
166
+ }
167
+ const message = err instanceof Error ? err.message : String(err);
168
+ return { exitCode: 1, stdout: "", stderr: `error: ${message}\n` };
169
+ }
170
+ }
171
+ //# sourceMappingURL=design-critique-chip.js.map
@@ -3,6 +3,7 @@ export * from "./binary.js";
3
3
  export * from "./build-command.js";
4
4
  export * from "./call.js";
5
5
  export * from "./constants.js";
6
+ export * from "./design-critique-chip.js";
6
7
  export * from "./errors.js";
7
8
  export * from "./gh-rest.js";
8
9
  export * from "./main.js";
package/dist/scm/index.js CHANGED
@@ -3,6 +3,7 @@ export * from "./binary.js";
3
3
  export * from "./build-command.js";
4
4
  export * from "./call.js";
5
5
  export * from "./constants.js";
6
+ export * from "./design-critique-chip.js";
6
7
  export * from "./errors.js";
7
8
  export * from "./gh-rest.js";
8
9
  export * from "./main.js";
@@ -1,3 +1,4 @@
1
+ import type { LabelClient } from "../vbrief-reconcile/types.js";
1
2
  import type { GhRestSeams } from "./gh-rest.js";
2
3
  export interface MainOptions {
3
4
  readonly whichFn?: Parameters<typeof import("./binary.js").resolveBinary>[0];
@@ -8,6 +9,8 @@ export interface MainOptions {
8
9
  * Production CLI always probes.
9
10
  */
10
11
  readonly skipReadiness?: boolean;
12
+ /** LabelClient seam for `issue design-critique-chip` (#3642). */
13
+ readonly labelClient?: LabelClient;
11
14
  }
12
15
  /**
13
16
  * CLI entry point. Returns the underlying binary's exit code (or 2 on arg error).