@deftai/directive-core 0.108.0 → 0.109.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 (138) hide show
  1. package/dist/cache/operations.js +1 -1
  2. package/dist/check/gate-lists.d.ts +14 -0
  3. package/dist/check/gate-lists.js +26 -3
  4. package/dist/check/session-completed-ac.d.ts +1 -1
  5. package/dist/check/session-completed-ac.js +1 -1
  6. package/dist/consumer-check-contract/evaluate.d.ts +47 -0
  7. package/dist/consumer-check-contract/evaluate.js +181 -15
  8. package/dist/delivery-attempt/evaluate.d.ts +9 -1
  9. package/dist/delivery-attempt/evaluate.js +69 -0
  10. package/dist/delivery-attempt/index.d.ts +1 -1
  11. package/dist/delivery-attempt/index.js +1 -1
  12. package/dist/deposit/live-procedure-exclusions.d.ts +18 -0
  13. package/dist/deposit/live-procedure-exclusions.js +110 -0
  14. package/dist/deposit/live-procedure-targets.d.ts +45 -0
  15. package/dist/deposit/live-procedure-targets.js +274 -0
  16. package/dist/deposit/python-free.d.ts +6 -0
  17. package/dist/deposit/python-free.js +15 -0
  18. package/dist/deposit/rewrite-deposit-links.d.ts +42 -0
  19. package/dist/deposit/rewrite-deposit-links.js +148 -0
  20. package/dist/deposit/run-stage-content-pack.d.ts +2 -0
  21. package/dist/deposit/run-stage-content-pack.js +3 -0
  22. package/dist/deposit/stage-content-pack.d.ts +17 -0
  23. package/dist/deposit/stage-content-pack.js +91 -0
  24. package/dist/design-critique/citation-grammar.d.ts +7 -0
  25. package/dist/design-critique/citation-grammar.js +1 -1
  26. package/dist/design-critique/completed-arc-record.d.ts +1 -1
  27. package/dist/design-critique/completed-arc-record.js +59 -14
  28. package/dist/hooks/classify/host-session-identity.d.ts +24 -11
  29. package/dist/hooks/classify/host-session-identity.js +46 -49
  30. package/dist/hooks/classify/index.d.ts +2 -2
  31. package/dist/hooks/classify/index.js +2 -2
  32. package/dist/hooks/classify/paths.d.ts +2 -0
  33. package/dist/hooks/classify/paths.js +8 -4
  34. package/dist/hooks/classify/stdin.js +69 -3
  35. package/dist/hooks/dest-form.d.ts +20 -1
  36. package/dist/hooks/dest-form.js +158 -21
  37. package/dist/hooks/dispatcher.d.ts +6 -2
  38. package/dist/hooks/dispatcher.js +258 -62
  39. package/dist/hooks/fixtures/cases.d.ts +6 -0
  40. package/dist/hooks/fixtures/cases.js +53 -0
  41. package/dist/hooks/git-destructive-log.d.ts +32 -0
  42. package/dist/hooks/git-destructive-log.js +46 -0
  43. package/dist/hooks/index.d.ts +2 -0
  44. package/dist/hooks/index.js +2 -0
  45. package/dist/hooks/shell-write-targets.d.ts +10 -0
  46. package/dist/hooks/shell-write-targets.js +274 -0
  47. package/dist/hooks/tools.d.ts +20 -2
  48. package/dist/hooks/tools.js +30 -1
  49. package/dist/init-deposit/gitignore.d.ts +7 -0
  50. package/dist/init-deposit/gitignore.js +24 -0
  51. package/dist/init-deposit/init-deposit.js +3 -0
  52. package/dist/init-deposit/refresh.js +7 -0
  53. package/dist/init-deposit/runtime-writers.d.ts +14 -0
  54. package/dist/init-deposit/runtime-writers.js +33 -0
  55. package/dist/intake/issue-ingest.d.ts +6 -1
  56. package/dist/intake/issue-ingest.js +21 -2
  57. package/dist/intake/reconcile-issues.js +13 -13
  58. package/dist/lifecycle/brief-envelope.d.ts +25 -0
  59. package/dist/lifecycle/brief-envelope.js +42 -0
  60. package/dist/lifecycle/index.d.ts +1 -0
  61. package/dist/lifecycle/index.js +1 -0
  62. package/dist/literal-acceptance/evaluate.js +14 -5
  63. package/dist/literal-acceptance/index.d.ts +1 -1
  64. package/dist/literal-acceptance/index.js +1 -1
  65. package/dist/literal-acceptance/run.d.ts +2 -0
  66. package/dist/literal-acceptance/run.js +19 -1
  67. package/dist/orphan-active/candidate-scope.d.ts +53 -0
  68. package/dist/orphan-active/candidate-scope.js +157 -0
  69. package/dist/orphan-active/evaluate.d.ts +27 -0
  70. package/dist/orphan-active/evaluate.js +63 -6
  71. package/dist/orphan-active/index.d.ts +1 -0
  72. package/dist/orphan-active/index.js +1 -0
  73. package/dist/policy/merge-approval-head.js +7 -6
  74. package/dist/pr-closing-keywords/gh.js +32 -9
  75. package/dist/pr-closing-keywords/main.d.ts +1 -0
  76. package/dist/pr-closing-keywords/main.js +68 -3
  77. package/dist/pr-closing-keywords/types.d.ts +2 -0
  78. package/dist/pr-merge-readiness/gh.js +32 -9
  79. package/dist/pr-protected-issues/gh.js +36 -9
  80. package/dist/pr-wait-mergeable/wrappers.js +3 -3
  81. package/dist/product-first-done-gate/evaluate.js +6 -2
  82. package/dist/product-first-done-gate/types.js +2 -0
  83. package/dist/release/consumer-hard-stops.d.ts +48 -0
  84. package/dist/release/consumer-hard-stops.js +140 -0
  85. package/dist/release/consumer-readiness-disclosure.d.ts +18 -0
  86. package/dist/release/consumer-readiness-disclosure.js +51 -0
  87. package/dist/release/index.d.ts +2 -0
  88. package/dist/release/index.js +2 -0
  89. package/dist/release/pipeline.js +55 -0
  90. package/dist/release/run-consumer-readiness.d.ts +15 -0
  91. package/dist/release/run-consumer-readiness.js +24 -0
  92. package/dist/release/types.d.ts +6 -0
  93. package/dist/review-monitor/github-lease.js +4 -3
  94. package/dist/run-summary/types.d.ts +2 -2
  95. package/dist/scm/build-command.d.ts +2 -2
  96. package/dist/scm/build-command.js +2 -2
  97. package/dist/scm/call-shape.d.ts +25 -0
  98. package/dist/scm/call-shape.js +59 -0
  99. package/dist/scm/call.d.ts +7 -4
  100. package/dist/scm/call.js +41 -11
  101. package/dist/scm/gh-rest.d.ts +25 -12
  102. package/dist/scm/gh-rest.js +65 -15
  103. package/dist/scm/index.d.ts +2 -0
  104. package/dist/scm/index.js +2 -0
  105. package/dist/scm/spawn-status.d.ts +33 -0
  106. package/dist/scm/spawn-status.js +53 -0
  107. package/dist/scope/acceptance-evidence.d.ts +1 -1
  108. package/dist/scope/transition.js +4 -14
  109. package/dist/session/ac-pass-banking.d.ts +2 -2
  110. package/dist/session/ac-pass-banking.js +2 -2
  111. package/dist/session/host-session-owner.d.ts +53 -0
  112. package/dist/session/host-session-owner.js +89 -0
  113. package/dist/session/index.d.ts +1 -0
  114. package/dist/session/index.js +1 -0
  115. package/dist/session/occupancy.d.ts +21 -1
  116. package/dist/session/occupancy.js +70 -12
  117. package/dist/session/verify-ac-session-cache.d.ts +2 -2
  118. package/dist/session/verify-ac-session-cache.js +2 -2
  119. package/dist/triage/evaluate/worktrees.js +153 -6
  120. package/dist/umbrella-current-shape/index.d.ts +51 -3
  121. package/dist/umbrella-current-shape/index.js +106 -18
  122. package/dist/validate-content/deposit-required.d.ts +39 -0
  123. package/dist/validate-content/deposit-required.js +147 -0
  124. package/dist/validate-content/index.d.ts +1 -0
  125. package/dist/validate-content/index.js +1 -0
  126. package/dist/validate-content/validate-links.d.ts +2 -3
  127. package/dist/validate-content/validate-links.js +29 -2
  128. package/dist/vbrief-activate/activate.d.ts +7 -2
  129. package/dist/vbrief-activate/activate.js +26 -13
  130. package/dist/verify-ac/evaluate.d.ts +9 -0
  131. package/dist/verify-ac/evaluate.js +32 -9
  132. package/dist/verify-source/deposit-closure.d.ts +23 -0
  133. package/dist/verify-source/deposit-closure.js +162 -0
  134. package/dist/verify-source/index.d.ts +2 -0
  135. package/dist/verify-source/index.js +2 -0
  136. package/dist/verify-source/semantic-single-source.d.ts +36 -0
  137. package/dist/verify-source/semantic-single-source.js +349 -0
  138. package/package.json +3 -3
@@ -107,7 +107,7 @@ export function cachePut(source, key, raw, options = {}) {
107
107
  const rawSize = Buffer.byteLength(rawText, "utf8");
108
108
  // #1870 Greptile P2: account for current-shape sidecar bytes in cap + meta
109
109
  // so usage does not underreport by the cumulative sidecar size.
110
- const plannedSidecar = buildCurrentShapeSidecar(raw);
110
+ const plannedSidecar = buildCurrentShapeSidecar(raw).sidecar;
111
111
  const plannedSidecarText = plannedSidecar !== null
112
112
  ? `${pythonJsonDump({
113
113
  ...plannedSidecar,
@@ -15,6 +15,20 @@ export type CheckGateSpec = string | {
15
15
  * xbrief/completed instead of skipping (#3357).
16
16
  */
17
17
  export declare const PRODUCT_FIRST_AC_GATE: CheckGateSpec;
18
+ /**
19
+ * Candidate-scoped orphan-active for check aggregates (#3893).
20
+ *
21
+ * The unscoped sweep on the merge chokepoint gave a brief stranded by one
22
+ * merge authority over every other open PR, and N stranded briefs made N
23
+ * single-brief lifecycle PRs mutually unmergeable. It was never preventive for
24
+ * the candidate either: that candidate's linked PR still reads
25
+ * `merged_at: null` while its own gate runs.
26
+ *
27
+ * Bare `verify:orphan-active` stays repo-wide for `deft doctor`, manual runs,
28
+ * swarm finalize, and the after-merge `--issue N` DONE gate (#3429); the
29
+ * scoped form still falls back to the full sweep at the delivery tip.
30
+ */
31
+ export declare const ORPHAN_ACTIVE_MERGE_GATE: CheckGateSpec;
18
32
  export declare function checkGateId(spec: CheckGateSpec): string;
19
33
  /** Args for `task … --taskfile <path>` (optional `--` + public CLI flags). */
20
34
  export declare function checkGateSpawnArgs(spec: CheckGateSpec, taskfilePath: string): string[];
@@ -9,6 +9,23 @@ export const PRODUCT_FIRST_AC_GATE = {
9
9
  task: PRODUCT_AC_GATE_ID,
10
10
  args: ["--soft-missing-xbrief"],
11
11
  };
12
+ /**
13
+ * Candidate-scoped orphan-active for check aggregates (#3893).
14
+ *
15
+ * The unscoped sweep on the merge chokepoint gave a brief stranded by one
16
+ * merge authority over every other open PR, and N stranded briefs made N
17
+ * single-brief lifecycle PRs mutually unmergeable. It was never preventive for
18
+ * the candidate either: that candidate's linked PR still reads
19
+ * `merged_at: null` while its own gate runs.
20
+ *
21
+ * Bare `verify:orphan-active` stays repo-wide for `deft doctor`, manual runs,
22
+ * swarm finalize, and the after-merge `--issue N` DONE gate (#3429); the
23
+ * scoped form still falls back to the full sweep at the delivery tip.
24
+ */
25
+ export const ORPHAN_ACTIVE_MERGE_GATE = {
26
+ task: "verify:orphan-active",
27
+ args: ["--changed-only"],
28
+ };
12
29
  export function checkGateId(spec) {
13
30
  return typeof spec === "string" ? spec : spec.task;
14
31
  }
@@ -62,8 +79,9 @@ export const FRAMEWORK_CHECK_GATES = [
62
79
  // --- Fast preflight (seconds–few min) — #3188 ---
63
80
  "verify:branch",
64
81
  "verify:encoding",
82
+ "verify:closing-keywords",
65
83
  "verify:cache-fresh",
66
- "verify:orphan-active",
84
+ ORPHAN_ACTIVE_MERGE_GATE,
67
85
  "verify:completed-write-guard",
68
86
  "verify:license-sync",
69
87
  "verify:contract-drift",
@@ -73,7 +91,10 @@ export const FRAMEWORK_CHECK_GATES = [
73
91
  "verify:rule-ownership",
74
92
  "verify:biome-config",
75
93
  "verify:content-manifest",
94
+ // #3900 check 1: C1 declaration vs staged pack. Checks 2/3 compose C3/C2.
95
+ "verify:deposit-closure",
76
96
  "verify:skill-external-fetch-gate",
97
+ "verify:semantic-single-source",
77
98
  "verify:cursor-tier1",
78
99
  "verify:openclaw-tier1",
79
100
  "verify:go-freeze",
@@ -82,7 +103,9 @@ export const FRAMEWORK_CHECK_GATES = [
82
103
  // #3145: test/source boundary + approved-scope provenance + consumer gate composition
83
104
  "verify:test-boundary",
84
105
  "verify:scope-provenance",
85
- "verify:consumer-check-contract",
106
+ // #3893: fail-closed on merge-chokepoint gate scoping for the composition
107
+ // this repo owns; consumer deposits stay warn-only until `deft update`.
108
+ { task: "verify:consumer-check-contract", args: ["--framework-source"] },
86
109
  // #3362: dead-surface detector (warn-only this release; no --enforce)
87
110
  "verify:telemetry-coverage",
88
111
  "verify:vbrief-conformance",
@@ -111,7 +134,7 @@ export const CONSUMER_CHECK_GATES = [
111
134
  "verify:branch",
112
135
  "verify:cache-fresh",
113
136
  "verify:wip-cap",
114
- "verify:orphan-active",
137
+ ORPHAN_ACTIVE_MERGE_GATE,
115
138
  "verify:completed-write-guard",
116
139
  "doctor",
117
140
  "toolchain:check-consumer",
@@ -8,7 +8,7 @@
8
8
  /** One remediation when check cannot target the just-completed brief (#3357). */
9
9
  export declare const SESSION_COMPLETED_AC_REMEDIATION = "Run task verify:ac -- xbrief/completed/<just-completed>.xbrief.json (a story completed this session; check must not soft-skip) (#3357)";
10
10
  /** Project-relative marker written by scope:complete for this session (#3357). */
11
- export declare const SESSION_COMPLETED_MARKER_REL: readonly [".deft", "last-completed.json"];
11
+ export declare const SESSION_COMPLETED_MARKER_REL: readonly [".deft", "cache", "last-completed.json"];
12
12
  export interface SessionCompletedMarker {
13
13
  readonly path: string;
14
14
  readonly sessionId: string;
@@ -13,7 +13,7 @@ import { parseTimestamp } from "../session/time.js";
13
13
  /** One remediation when check cannot target the just-completed brief (#3357). */
14
14
  export const SESSION_COMPLETED_AC_REMEDIATION = "Run task verify:ac -- xbrief/completed/<just-completed>.xbrief.json (a story completed this session; check must not soft-skip) (#3357)";
15
15
  /** Project-relative marker written by scope:complete for this session (#3357). */
16
- export const SESSION_COMPLETED_MARKER_REL = [".deft", "last-completed.json"];
16
+ export const SESSION_COMPLETED_MARKER_REL = [".deft", "cache", "last-completed.json"];
17
17
  function asRecord(value) {
18
18
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
19
19
  return value;
@@ -10,6 +10,20 @@
10
10
  import { type CheckGateSpec } from "../check/gate-lists.js";
11
11
  /** Gates that #3145 requires on consumer check composition (beyond baseline). */
12
12
  export declare const REQUIRED_CONSUMER_ENFORCEMENT_GATES: readonly string[];
13
+ /**
14
+ * Gates whose composition on a check aggregate must carry a scoping argument
15
+ * (#3893).
16
+ *
17
+ * `verify:orphan-active` is repo-wide by default. Composed unscoped on a merge
18
+ * chokepoint it fails a candidate for residue another merge stranded, and N
19
+ * stranded briefs make N single-brief lifecycle PRs mutually unmergeable. It is
20
+ * not preventive for the candidate either: that candidate's linked PR still
21
+ * reads `merged_at: null` while its own gate runs.
22
+ *
23
+ * Fail-closed under `--framework-source`, where this repo owns the composition;
24
+ * a warning elsewhere while `deft update` re-deposits the Taskfile.
25
+ */
26
+ export declare const MERGE_CHOKEPOINT_SCOPED_GATE_ARGS: ReadonlyMap<string, string>;
13
27
  export interface ConsumerCheckContractFinding {
14
28
  readonly gateId: string;
15
29
  readonly surface: "check-task" | "ci-workflow" | "verify-taskfile";
@@ -150,11 +164,44 @@ export declare function stripTaskBodyComments(body: string): string;
150
164
  export declare function taskBodyInvokesCheckOrchestrator(body: string): boolean;
151
165
  /** True when the check / check:consumer / check:framework-source task invokes orchestrator. */
152
166
  export declare function taskfileInvokesCheckOrchestrator(text: string): boolean;
167
+ /** One `deps:` entry plus the YAML nested under it (`vars:` and friends). */
168
+ export interface CheckDepEntry {
169
+ readonly name: string;
170
+ readonly body: string;
171
+ }
172
+ /**
173
+ * Extract `deps:` entries from a go-task task definition, keeping each entry's
174
+ * nested body so an argument-carrying dep (#3893) is distinguishable from a
175
+ * bare one. Best-effort line parser.
176
+ */
177
+ export declare function extractCheckDepEntries(taskfileText: string, taskName: string): CheckDepEntry[];
153
178
  /**
154
179
  * Extract dependency task names from a go-task task definition body snippet.
155
180
  * Best-effort line parser for `deps:` list entries.
156
181
  */
157
182
  export declare function extractCheckDeps(taskfileText: string, taskName: string): string[];
183
+ /**
184
+ * Drop a YAML trailing comment and surrounding quotes from a scalar value.
185
+ * Honours double-quoted `\\"` escapes and single-quoted `''` doubling, so a
186
+ * value carrying a quote is not truncated before the argument that follows.
187
+ */
188
+ export declare function parseYamlScalar(raw: string): string;
189
+ /**
190
+ * Effective `CLI_ARGS` value for a `deps:` entry, or null when it sets none.
191
+ *
192
+ * Reads the key itself rather than the entry text, so the required argument
193
+ * appearing in a comment, a sibling variable, or a descriptive value cannot
194
+ * stand in for the argument the gate actually receives (#3893).
195
+ */
196
+ export declare function depEntryCliArgs(body: string): string | null;
197
+ /**
198
+ * True when the effective `CLI_ARGS` token list carries `requiredArg`.
199
+ *
200
+ * Exact token only: the guarded verbs accept the bare flag, so an equals form
201
+ * like `--changed-only=0` would exit 2 on an unrecognized argument while the
202
+ * contract reported the composition healthy.
203
+ */
204
+ export declare function cliArgsCarry(cliArgs: string | null, requiredArg: string): boolean;
158
205
  /**
159
206
  * Evaluate whether required enforcement gates are composed into consumer check/CI.
160
207
  */
@@ -17,6 +17,22 @@ export const REQUIRED_CONSUMER_ENFORCEMENT_GATES = [
17
17
  "verify:scope-provenance",
18
18
  "verify:consumer-check-contract",
19
19
  ];
20
+ /**
21
+ * Gates whose composition on a check aggregate must carry a scoping argument
22
+ * (#3893).
23
+ *
24
+ * `verify:orphan-active` is repo-wide by default. Composed unscoped on a merge
25
+ * chokepoint it fails a candidate for residue another merge stranded, and N
26
+ * stranded briefs make N single-brief lifecycle PRs mutually unmergeable. It is
27
+ * not preventive for the candidate either: that candidate's linked PR still
28
+ * reads `merged_at: null` while its own gate runs.
29
+ *
30
+ * Fail-closed under `--framework-source`, where this repo owns the composition;
31
+ * a warning elsewhere while `deft update` re-deposits the Taskfile.
32
+ */
33
+ export const MERGE_CHOKEPOINT_SCOPED_GATE_ARGS = new Map([
34
+ ["verify:orphan-active", "--changed-only"],
35
+ ]);
20
36
  /**
21
37
  * Relative path forms accepted for the deft-install canonical Taskfile include
22
38
  * (init-deposit `MINIMAL_TASKFILE` / `CANONICAL_TASKFILE_INCLUDE`).
@@ -575,16 +591,18 @@ export function taskfileInvokesCheckOrchestrator(text) {
575
591
  return false;
576
592
  }
577
593
  /**
578
- * Extract dependency task names from a go-task task definition body snippet.
579
- * Best-effort line parser for `deps:` list entries.
594
+ * Extract `deps:` entries from a go-task task definition, keeping each entry's
595
+ * nested body so an argument-carrying dep (#3893) is distinguishable from a
596
+ * bare one. Best-effort line parser.
580
597
  */
581
- export function extractCheckDeps(taskfileText, taskName) {
598
+ export function extractCheckDepEntries(taskfileText, taskName) {
582
599
  const lines = taskfileText.replace(/\r\n/g, "\n").split("\n");
583
- const deps = [];
600
+ const entries = [];
584
601
  let inTask = false;
585
602
  let taskIndent = 0;
586
603
  let inDeps = false;
587
604
  let depsIndent = 0;
605
+ let current = null;
588
606
  const taskRe = new RegExp(`^${escapeRegExp(taskName)}\\s*:`);
589
607
  for (const raw of lines) {
590
608
  const stripped = raw.trim();
@@ -604,23 +622,142 @@ export function extractCheckDeps(taskfileText, taskName) {
604
622
  if (/^deps\s*:/.test(stripped)) {
605
623
  inDeps = true;
606
624
  depsIndent = indent;
625
+ current = null;
607
626
  continue;
608
627
  }
609
- if (inDeps) {
610
- if (indent <= depsIndent && !stripped.startsWith("-")) {
611
- inDeps = false;
628
+ if (!inDeps)
629
+ continue;
630
+ if (indent <= depsIndent && !stripped.startsWith("-")) {
631
+ inDeps = false;
632
+ current = null;
633
+ }
634
+ else if (stripped.startsWith("-")) {
635
+ // - verify:branch OR - task: verify:branch
636
+ const m = stripped.match(/^-\s+task:\s*["']?([^"'#]+?)["']?\s*(?:#.*)?$/) ??
637
+ stripped.match(/^-\s+["']?([^"'#]+?)["']?\s*(?:#.*)?$/);
638
+ current = m?.[1] === undefined ? null : { name: m[1].trim(), lines: [] };
639
+ if (current !== null) {
640
+ entries.push(current);
612
641
  }
613
- else if (stripped.startsWith("-")) {
614
- // - verify:branch OR - task: verify:branch
615
- const m = stripped.match(/^-\s+task:\s*["']?([^"'#]+?)["']?\s*(?:#.*)?$/) ??
616
- stripped.match(/^-\s+["']?([^"'#]+?)["']?\s*(?:#.*)?$/);
617
- if (m?.[1]) {
618
- deps.push(m[1].trim());
642
+ }
643
+ else if (current !== null) {
644
+ // Keep indentation: the scoping check below needs to tell an immediate
645
+ // `vars:` property from deeper YAML (#3893).
646
+ current.lines.push(raw);
647
+ }
648
+ }
649
+ return entries.map((entry) => ({ name: entry.name, body: entry.lines.join("\n") }));
650
+ }
651
+ /**
652
+ * Extract dependency task names from a go-task task definition body snippet.
653
+ * Best-effort line parser for `deps:` list entries.
654
+ */
655
+ export function extractCheckDeps(taskfileText, taskName) {
656
+ return extractCheckDepEntries(taskfileText, taskName).map((entry) => entry.name);
657
+ }
658
+ /**
659
+ * Drop a YAML trailing comment and surrounding quotes from a scalar value.
660
+ * Honours double-quoted `\\"` escapes and single-quoted `''` doubling, so a
661
+ * value carrying a quote is not truncated before the argument that follows.
662
+ */
663
+ export function parseYamlScalar(raw) {
664
+ const text = raw.trim();
665
+ if (text.startsWith('"')) {
666
+ let out = "";
667
+ for (let i = 1; i < text.length; i += 1) {
668
+ const ch = text[i];
669
+ if (ch === "\\" && i + 1 < text.length) {
670
+ out += text[i + 1];
671
+ i += 1;
672
+ continue;
673
+ }
674
+ if (ch === '"')
675
+ return out;
676
+ out += ch;
677
+ }
678
+ return out;
679
+ }
680
+ if (text.startsWith("'")) {
681
+ let out = "";
682
+ for (let i = 1; i < text.length; i += 1) {
683
+ const ch = text[i];
684
+ if (ch === "'") {
685
+ if (text[i + 1] === "'") {
686
+ out += "'";
687
+ i += 1;
688
+ continue;
619
689
  }
690
+ return out;
620
691
  }
692
+ out += ch;
621
693
  }
694
+ return out;
622
695
  }
623
- return deps;
696
+ const comment = text.indexOf(" #");
697
+ return (comment === -1 ? text : text.slice(0, comment)).trim();
698
+ }
699
+ /**
700
+ * Inline-flow `vars: { CLI_ARGS: ... }` scalar. Quote escapes are honoured so a
701
+ * value carrying a quote is not truncated before the argument that follows.
702
+ */
703
+ const INLINE_CLI_ARGS_RE = /CLI_ARGS\s*:\s*("(?:\\.|[^"\\])*"|'(?:''|[^'])*'|[^,}]*)/;
704
+ /**
705
+ * Effective `CLI_ARGS` value for a `deps:` entry, or null when it sets none.
706
+ *
707
+ * Reads the key itself rather than the entry text, so the required argument
708
+ * appearing in a comment, a sibling variable, or a descriptive value cannot
709
+ * stand in for the argument the gate actually receives (#3893).
710
+ */
711
+ export function depEntryCliArgs(body) {
712
+ let varsIndent = null;
713
+ let propIndent = null;
714
+ let value = null;
715
+ for (const rawLine of body.split("\n")) {
716
+ const line = rawLine.trim();
717
+ if (line.length === 0 || line.startsWith("#"))
718
+ continue;
719
+ const indent = rawLine.length - rawLine.trimStart().length;
720
+ if (varsIndent !== null && indent <= varsIndent) {
721
+ varsIndent = null;
722
+ propIndent = null;
723
+ }
724
+ const varsBlock = /^vars\s*:\s*(.*)$/.exec(line);
725
+ if (varsBlock !== null) {
726
+ varsIndent = indent;
727
+ propIndent = null;
728
+ // Inline flow form: vars: { CLI_ARGS: "--changed-only" }
729
+ const inline = INLINE_CLI_ARGS_RE.exec(varsBlock[1] ?? "");
730
+ if (inline !== null) {
731
+ value = parseYamlScalar(inline[1] ?? "");
732
+ }
733
+ continue;
734
+ }
735
+ if (varsIndent === null)
736
+ continue;
737
+ propIndent ??= indent;
738
+ if (indent !== propIndent)
739
+ continue;
740
+ const cliArgs = /^CLI_ARGS\s*:\s*(.*)$/.exec(line);
741
+ if (cliArgs !== null) {
742
+ value = parseYamlScalar(cliArgs[1] ?? "");
743
+ }
744
+ }
745
+ return value;
746
+ }
747
+ /**
748
+ * True when the effective `CLI_ARGS` token list carries `requiredArg`.
749
+ *
750
+ * Exact token only: the guarded verbs accept the bare flag, so an equals form
751
+ * like `--changed-only=0` would exit 2 on an unrecognized argument while the
752
+ * contract reported the composition healthy.
753
+ */
754
+ export function cliArgsCarry(cliArgs, requiredArg) {
755
+ if (cliArgs === null)
756
+ return false;
757
+ return cliArgs
758
+ .split(/\s+/)
759
+ .filter((token) => token.length > 0)
760
+ .some((token) => token === requiredArg);
624
761
  }
625
762
  function remediationForMissing(gateId, surface) {
626
763
  return (`Add \`${gateId}\` to the consumer ${surface} composition (Taskfile check deps ` +
@@ -712,12 +849,41 @@ export function evaluateConsumerCheckContract(projectRoot, options = {}) {
712
849
  }
713
850
  let anyAggregateDefined = false;
714
851
  for (const target of checkTargets) {
715
- const deps = extractCheckDeps(rootTaskfile, target);
852
+ const entries = extractCheckDepEntries(rootTaskfile, target);
853
+ const deps = entries.map((entry) => entry.name);
716
854
  const body = extractTaskBody(rootTaskfile, target);
717
855
  const defined = deps.length > 0 || body.trim().length > 0;
718
856
  if (!defined)
719
857
  continue;
720
858
  anyAggregateDefined = true;
859
+ // Checked even on a trusted orchestrator body: a listed dep still runs, and
860
+ // unscoped on a merge chokepoint is the #3893 defect.
861
+ for (const [gateId, requiredArg] of MERGE_CHOKEPOINT_SCOPED_GATE_ARGS) {
862
+ // Every occurrence must carry the argument: a scoped entry followed by a
863
+ // bare duplicate still runs the repo-wide scan (#3893).
864
+ const listed = entries.filter((row) => row.name === gateId);
865
+ if (listed.length === 0)
866
+ continue;
867
+ if (listed.every((row) => cliArgsCarry(depEntryCliArgs(row.body), requiredArg)))
868
+ continue;
869
+ const finding = {
870
+ gateId,
871
+ surface: "check-task",
872
+ detail: `aggregate '${target}' composes ${gateId} without ${requiredArg}: an unscoped ` +
873
+ "repo-wide lifecycle scan on a merge chokepoint fails a candidate for residue " +
874
+ "another merge stranded (#3893)",
875
+ remediation: `Pass ${requiredArg} to ${gateId} in the '${target}' deps (go-task object form, ` +
876
+ `vars: CLI_ARGS: "${requiredArg}"). Bare ${gateId} stays repo-wide for doctor, ` +
877
+ "manual runs, and the after-merge --issue N run. " +
878
+ "See content/docs/consumer-check-contract.md (#3893).",
879
+ };
880
+ if (options.frameworkSource === true) {
881
+ findings.push(finding);
882
+ }
883
+ else {
884
+ soft.push(finding);
885
+ }
886
+ }
721
887
  const trustThis = taskInvokesOrchestrator(target) && verifyDefinesRequired;
722
888
  if (trustThis)
723
889
  continue;
@@ -4,7 +4,7 @@
4
4
  * Loads durable unit state and returns a single deterministic decision
5
5
  * before every delivery dispatch or retry.
6
6
  */
7
- import type { DeliveryUnitLedger, PreDispatchInput, PreDispatchResult } from "./types.js";
7
+ import type { DeliveryBudgetPolicy, DeliveryUnitLedger, PreDispatchInput, PreDispatchResult } from "./types.js";
8
8
  /**
9
9
  * Evaluate whether a delivery dispatch may proceed.
10
10
  *
@@ -15,6 +15,14 @@ import type { DeliveryUnitLedger, PreDispatchInput, PreDispatchResult } from "./
15
15
  * That keeps bounded override quota for dispatches that truly need it.
16
16
  */
17
17
  export declare function evaluatePreDispatch(ledger: DeliveryUnitLedger, input: PreDispatchInput): PreDispatchResult;
18
+ /**
19
+ * Elapsed budget for a run already queued or running (#3983).
20
+ * Pre-dispatch cannot see this case: an active attempt is DENY_DUPLICATE_ACTIVE.
21
+ */
22
+ export declare function evaluateInFlight(ledger: DeliveryUnitLedger, input?: {
23
+ readonly now?: string;
24
+ readonly policy?: Partial<DeliveryBudgetPolicy>;
25
+ }): PreDispatchResult;
18
26
  /**
19
27
  * Convenience: evaluate against ledger, and if blocked, return handoff suitable
20
28
  * for persistence via markBlocked + saveUnitLedger.
@@ -421,6 +421,75 @@ function evaluatePreDispatchNatural(ledger, input) {
421
421
  resume: null,
422
422
  });
423
423
  }
424
+ /**
425
+ * Elapsed budget for a run already queued or running (#3983).
426
+ * Pre-dispatch cannot see this case: an active attempt is DENY_DUPLICATE_ACTIVE.
427
+ */
428
+ export function evaluateInFlight(ledger, input = {}) {
429
+ const nowIso = utcIso(input.now);
430
+ const synthetic = {
431
+ scopeId: ledger.scopeId,
432
+ targetId: ledger.targetId,
433
+ workflowId: ledger.workflowId,
434
+ sourceRevision: ledger.lastSourceRevision ?? "in-flight",
435
+ trigger: "resume",
436
+ now: nowIso,
437
+ policy: input.policy,
438
+ };
439
+ const policy = mergePolicy(input.policy);
440
+ const active = activeAttempts(ledger);
441
+ if (active.length === 0) {
442
+ return result(synthetic, ledger, "ALLOW_FIRST_ATTEMPT", "no in-flight attempt", {
443
+ retryability: null,
444
+ fingerprint: null,
445
+ sameFailureCount: 0,
446
+ materialClass: "none",
447
+ resume: null,
448
+ });
449
+ }
450
+ const current = active[0];
451
+ if (current === undefined) {
452
+ return result(synthetic, ledger, "ALLOW_FIRST_ATTEMPT", "no in-flight attempt", {
453
+ retryability: null,
454
+ fingerprint: null,
455
+ sameFailureCount: 0,
456
+ materialClass: "none",
457
+ resume: null,
458
+ });
459
+ }
460
+ const startedMs = Date.parse(current.startedAt);
461
+ const nowMs = Date.parse(nowIso);
462
+ const liveSeconds = Number.isFinite(startedMs) && Number.isFinite(nowMs)
463
+ ? Math.max(0, (nowMs - startedMs) / 1000)
464
+ : 0;
465
+ const elapsed = ledger.totalElapsedSeconds + liveSeconds;
466
+ if (elapsed >= policy.maxElapsedSeconds) {
467
+ const resume = {
468
+ kind: "operator-override",
469
+ description: "elapsed budget exhausted on in-flight attempt; override or phase reset required",
470
+ satisfied: false,
471
+ };
472
+ return result(synthetic, ledger, "BLOCK_ELAPSED_BUDGET", "in-flight elapsed-time budget exhausted", {
473
+ retryability: ledger.lastFailure?.retryability ?? null,
474
+ fingerprint: ledger.lastFailure?.fingerprint ?? null,
475
+ sameFailureCount: 0,
476
+ materialClass: "none",
477
+ resume,
478
+ handoffLedger: {
479
+ ...ledger,
480
+ resumeCondition: resume,
481
+ blockedDecision: "BLOCK_ELAPSED_BUDGET",
482
+ },
483
+ });
484
+ }
485
+ return result(synthetic, ledger, "ALLOW_RESUME", "in-flight elapsed under budget", {
486
+ retryability: null,
487
+ fingerprint: null,
488
+ sameFailureCount: 0,
489
+ materialClass: "none",
490
+ resume: ledger.resumeCondition,
491
+ });
492
+ }
424
493
  /**
425
494
  * Convenience: evaluate against ledger, and if blocked, return handoff suitable
426
495
  * for persistence via markBlocked + saveUnitLedger.
@@ -8,7 +8,7 @@
8
8
  * @see content/docs/delivery-attempt.md
9
9
  */
10
10
  export { type BeginAttemptOnDiskInput, beginAttemptOnDisk, type CompleteAttemptOnDiskInput, completeAttemptOnDisk, } from "./disk-begin.js";
11
- export { evaluateAndPrepareBlock, evaluatePreDispatch, } from "./evaluate.js";
11
+ export { evaluateAndPrepareBlock, evaluateInFlight, evaluatePreDispatch, } from "./evaluate.js";
12
12
  export { buildFailureInfo, computeFailureFingerprint, type FingerprintInput, inferRetryability, normalizeFailureMessage, } from "./fingerprint.js";
13
13
  export { buildTerminalHandoff, defaultResumeFor, formatHandoffReport, nextSafeActionFor, redactHandoffForPersist, } from "./handoff.js";
14
14
  export { activeAttempts, beginAttempt, clearBlockIfResumed, completeAttempt, deliveryAttemptsDir, emptyUnitLedger, hasActiveAttempt, isUnitLockReclaimable, type LoadUnitLedgerResult, listUnitLedgers, loadOrCreateUnitLedger, loadUnitLedger, loadUnitLedgerResult, MemoryLedgerStore, markBlocked, newAttemptId, parseUnitLedger, recordOperatorOverride, saveUnitLedger, UNIT_LOCK_STALE_MS, unitLedgerFilename, unitLedgerPath, withUnitLock, } from "./ledger.js";
@@ -8,7 +8,7 @@
8
8
  * @see content/docs/delivery-attempt.md
9
9
  */
10
10
  export { beginAttemptOnDisk, completeAttemptOnDisk, } from "./disk-begin.js";
11
- export { evaluateAndPrepareBlock, evaluatePreDispatch, } from "./evaluate.js";
11
+ export { evaluateAndPrepareBlock, evaluateInFlight, evaluatePreDispatch, } from "./evaluate.js";
12
12
  export { buildFailureInfo, computeFailureFingerprint, inferRetryability, normalizeFailureMessage, } from "./fingerprint.js";
13
13
  export { buildTerminalHandoff, defaultResumeFor, formatHandoffReport, nextSafeActionFor, redactHandoffForPersist, } from "./handoff.js";
14
14
  export { activeAttempts, beginAttempt, clearBlockIfResumed, completeAttempt, deliveryAttemptsDir, emptyUnitLedger, hasActiveAttempt, isUnitLockReclaimable, listUnitLedgers, loadOrCreateUnitLedger, loadUnitLedger, loadUnitLedgerResult, MemoryLedgerStore, markBlocked, newAttemptId, parseUnitLedger, recordOperatorOverride, saveUnitLedger, UNIT_LOCK_STALE_MS, unitLedgerFilename, unitLedgerPath, withUnitLock, } from "./ledger.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * C3 live-procedure exclusion declaration (#3602 / #3899).
3
+ *
4
+ * History, examples, and prohibitions are skipped by this list, not by
5
+ * matching prose patterns. A file that names a deleted Python script in
6
+ * order to forbid treating it as a live path must appear here as
7
+ * `prohibition` or C3 will mis-fire.
8
+ */
9
+ export type LiveProcedureExclusionKind = "history" | "example" | "prohibition";
10
+ export interface LiveProcedureExclusion {
11
+ /** POSIX path relative to a flattened consumer deposit root. */
12
+ readonly path: string;
13
+ readonly kind: LiveProcedureExclusionKind;
14
+ readonly reason: string;
15
+ }
16
+ export declare const LIVE_PROCEDURE_EXCLUSIONS: readonly LiveProcedureExclusion[];
17
+ export declare function isDeclaredLiveProcedureExclusion(relativePath: string): boolean;
18
+ //# sourceMappingURL=live-procedure-exclusions.d.ts.map