@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
@@ -64,6 +64,7 @@ export const FRAMEWORK_CHECK_GATES = [
64
64
  "verify:encoding",
65
65
  "verify:cache-fresh",
66
66
  "verify:orphan-active",
67
+ "verify:completed-write-guard",
67
68
  "verify:license-sync",
68
69
  "verify:contract-drift",
69
70
  "toolchain:check",
@@ -111,6 +112,7 @@ export const CONSUMER_CHECK_GATES = [
111
112
  "verify:cache-fresh",
112
113
  "verify:wip-cap",
113
114
  "verify:orphan-active",
115
+ "verify:completed-write-guard",
114
116
  "doctor",
115
117
  "toolchain:check-consumer",
116
118
  // #3145 enforcement trio (test placement, scope provenance, gate composition)
@@ -20,7 +20,7 @@ const GATE_REMEDIES = {
20
20
  "verify:test-boundary": "Move tests to the allowed placement or update plan.policy test-boundary allowlist",
21
21
  "verify:scope-provenance": "Record approved scope provenance for the failing paths",
22
22
  "verify:consumer-check-contract": "Align consumer Taskfile includes with the required gate graph",
23
- "verify:forward-coverage": "Add tests covering new source files (task verify:forward-coverage)",
23
+ "verify:forward-coverage": "Add tests for new source files and uncovered changed branches (task verify:forward-coverage)",
24
24
  "verify:scm-boundary": "Move SCM mutations off GraphQL-heavy paths or wait for rate-limit reset",
25
25
  "verify:license-sync": "Sync LICENSE / package license fields",
26
26
  "verify:agents-md-budget": "Trim AGENTS.md managed section or raise plan.policy.agentsMdBudget deliberately",
@@ -0,0 +1,33 @@
1
+ import type { LabelClient } from "../vbrief-reconcile/types.js";
2
+ /** Closed catalog. Last chip wins. Not an open `design-critique:*` glob. */
3
+ export declare const DESIGN_CRITIQUE_CATALOG_CHIPS: readonly ["design-critique:mechanism-shaped", "design-critique:triage-ready"];
4
+ export type DesignCritiqueCatalogChip = (typeof DESIGN_CRITIQUE_CATALOG_CHIPS)[number];
5
+ export declare function isDesignCritiqueCatalogChip(name: string): name is DesignCritiqueCatalogChip;
6
+ /**
7
+ * Remaining-set replace: GET current, drop the other catalog names, keep other
8
+ * facets, then PUT/PATCH this list. One write. No DELETE-then-POST window.
9
+ */
10
+ export declare function remainingSetAfterDesignCritiqueChip(current: readonly string[], nextChip: string): string[];
11
+ /**
12
+ * Fold exclusive catalog-chip replace into a LabelClient.apply add/remove
13
+ * mutation. Production writer: ScmLabelClient.apply.
14
+ */
15
+ export declare function mergeDesignCritiqueExclusiveIntoApply(current: readonly string[], add: readonly string[], remove: readonly string[]): {
16
+ add: string[];
17
+ remove: string[];
18
+ };
19
+ /** Delta for LabelClient.apply (one add+remove mutation). */
20
+ export declare function designCritiqueChipApplyDelta(current: readonly string[], nextChip: string): {
21
+ add: string[];
22
+ remove: string[];
23
+ };
24
+ /**
25
+ * Exclusive catalog-chip write via LabelClient.apply. GET current, then one
26
+ * apply(add, remove). Does not PUT a naive full wipe.
27
+ */
28
+ export declare function applyDesignCritiqueCatalogChip(client: LabelClient, repo: string, issueNumber: number, nextChip: string): {
29
+ remaining: string[];
30
+ add: readonly string[];
31
+ remove: readonly string[];
32
+ };
33
+ //# sourceMappingURL=exclusive-chip.d.ts.map
@@ -0,0 +1,68 @@
1
+ /** Closed catalog. Last chip wins. Not an open `design-critique:*` glob. */
2
+ export const DESIGN_CRITIQUE_CATALOG_CHIPS = [
3
+ "design-critique:mechanism-shaped",
4
+ "design-critique:triage-ready",
5
+ ];
6
+ const CATALOG = new Set(DESIGN_CRITIQUE_CATALOG_CHIPS);
7
+ export function isDesignCritiqueCatalogChip(name) {
8
+ return CATALOG.has(name);
9
+ }
10
+ /**
11
+ * Remaining-set replace: GET current, drop the other catalog names, keep other
12
+ * facets, then PUT/PATCH this list. One write. No DELETE-then-POST window.
13
+ */
14
+ export function remainingSetAfterDesignCritiqueChip(current, nextChip) {
15
+ if (!isDesignCritiqueCatalogChip(nextChip)) {
16
+ throw new Error(`not a design-critique catalog chip: ${nextChip} (closed set: ${DESIGN_CRITIQUE_CATALOG_CHIPS.join(", ")})`);
17
+ }
18
+ const remaining = current.filter((name) => !CATALOG.has(name));
19
+ remaining.push(nextChip);
20
+ return remaining;
21
+ }
22
+ /**
23
+ * Fold exclusive catalog-chip replace into a LabelClient.apply add/remove
24
+ * mutation. Production writer: ScmLabelClient.apply.
25
+ */
26
+ export function mergeDesignCritiqueExclusiveIntoApply(current, add, remove) {
27
+ const catalogAdds = add.filter(isDesignCritiqueCatalogChip);
28
+ if (catalogAdds.length === 0) {
29
+ return { add: [...add], remove: [...remove] };
30
+ }
31
+ const nextChip = catalogAdds[catalogAdds.length - 1];
32
+ const delta = designCritiqueChipApplyDelta(current, nextChip);
33
+ const addSet = new Set(add.filter((name) => !isDesignCritiqueCatalogChip(name) || name === nextChip));
34
+ for (const name of delta.add)
35
+ addSet.add(name);
36
+ const removeSet = new Set(remove.filter((name) => name !== nextChip));
37
+ for (const name of delta.remove)
38
+ removeSet.add(name);
39
+ removeSet.delete(nextChip);
40
+ return { add: [...addSet], remove: [...removeSet] };
41
+ }
42
+ /** Delta for LabelClient.apply (one add+remove mutation). */
43
+ export function designCritiqueChipApplyDelta(current, nextChip) {
44
+ if (!isDesignCritiqueCatalogChip(nextChip)) {
45
+ throw new Error(`not a design-critique catalog chip: ${nextChip} (closed set: ${DESIGN_CRITIQUE_CATALOG_CHIPS.join(", ")})`);
46
+ }
47
+ const currentSet = new Set(current);
48
+ const remove = DESIGN_CRITIQUE_CATALOG_CHIPS.filter((name) => name !== nextChip && currentSet.has(name));
49
+ const add = currentSet.has(nextChip) ? [] : [nextChip];
50
+ return { add, remove };
51
+ }
52
+ /**
53
+ * Exclusive catalog-chip write via LabelClient.apply. GET current, then one
54
+ * apply(add, remove). Does not PUT a naive full wipe.
55
+ */
56
+ export function applyDesignCritiqueCatalogChip(client, repo, issueNumber, nextChip) {
57
+ const current = client.fetchLabels(repo, issueNumber);
58
+ const { add, remove } = designCritiqueChipApplyDelta(current, nextChip);
59
+ if (add.length > 0 || remove.length > 0) {
60
+ client.apply(repo, issueNumber, add, remove);
61
+ }
62
+ return {
63
+ remaining: remainingSetAfterDesignCritiqueChip(current, nextChip),
64
+ add,
65
+ remove,
66
+ };
67
+ }
68
+ //# sourceMappingURL=exclusive-chip.js.map
@@ -0,0 +1,59 @@
1
+ /** Parent-side substantiation deposit + fail-closed evaluator (#3651 / ADR-006). */
2
+ export type AuditReading = "measured" | "asserted";
3
+ export type AuditRole = "parent" | "critic" | "triage";
4
+ export type AuditPremise = {
5
+ markerId: string;
6
+ sha?: string;
7
+ pointer?: string;
8
+ reading?: AuditReading;
9
+ introducedByRole: AuditRole;
10
+ /** Changes classification, residual, or next-build contract. */
11
+ loadBearing: boolean;
12
+ };
13
+ export type AuditClearance = {
14
+ markerId: string;
15
+ clearedByRole: AuditRole;
16
+ /** Critic artifact names this marker as its audit target. */
17
+ targetsMarker: boolean;
18
+ };
19
+ export type AuditEnvelope = {
20
+ /** Marker ids listed on `audit-targets:`. Empty means the field was omitted. */
21
+ auditTargets: string[];
22
+ /** Envelope explicitly set `audit-targets: none`. */
23
+ declaredNone: boolean;
24
+ };
25
+ export type AuditBindAttempt = {
26
+ allAcceptMap: boolean;
27
+ /** Marker ids the bind record still lists as unresolved. */
28
+ unresolvedMarkerIds: string[];
29
+ };
30
+ export type ParentAuditDeposit = {
31
+ premises: readonly AuditPremise[];
32
+ clearances: readonly AuditClearance[];
33
+ envelopes: readonly AuditEnvelope[];
34
+ /** Ids the next critic envelope must name. */
35
+ namedAuditTargets?: readonly string[];
36
+ bindAttempt?: AuditBindAttempt;
37
+ };
38
+ export type AuditFailureCode = "missing-token" | "parent-self-clear" | "silent-clear" | "bind-unresolved" | "envelope-omits-target" | "marker-collision";
39
+ export type AuditFailure = {
40
+ code: AuditFailureCode;
41
+ detail: string;
42
+ };
43
+ export declare function parseAuditToken(token: string): {
44
+ markerId: string;
45
+ sha: string;
46
+ pointer: string;
47
+ reading: AuditReading;
48
+ } | null;
49
+ export declare function formatAuditToken(parts: {
50
+ markerId: string;
51
+ sha: string;
52
+ pointer: string;
53
+ reading: AuditReading;
54
+ }): string;
55
+ export declare function evaluateParentAudit(deposit: ParentAuditDeposit): {
56
+ ok: boolean;
57
+ failures: AuditFailure[];
58
+ };
59
+ //# sourceMappingURL=parent-audit.d.ts.map
@@ -0,0 +1,121 @@
1
+ /** Parent-side substantiation deposit + fail-closed evaluator (#3651 / ADR-006). */
2
+ const TOKEN_RE = /^audit:([A-Za-z0-9._-]+) sha=([0-9a-fA-F]{7,40}) pointer=(\S+) reading=(measured|asserted)$/;
3
+ export function parseAuditToken(token) {
4
+ const match = TOKEN_RE.exec(token.trim());
5
+ if (!match)
6
+ return null;
7
+ return {
8
+ markerId: match[1] ?? "",
9
+ sha: match[2] ?? "",
10
+ pointer: match[3] ?? "",
11
+ reading: (match[4] ?? "asserted"),
12
+ };
13
+ }
14
+ export function formatAuditToken(parts) {
15
+ return `audit:${parts.markerId} sha=${parts.sha} pointer=${parts.pointer} reading=${parts.reading}`;
16
+ }
17
+ function independentlyCleared(markerId, clearances, colliding) {
18
+ if (colliding.has(markerId))
19
+ return false;
20
+ return clearances.some((c) => c.markerId === markerId && c.clearedByRole === "critic" && c.targetsMarker);
21
+ }
22
+ function collidingMarkerIds(premises) {
23
+ const counts = new Map();
24
+ for (const premise of premises) {
25
+ if (!premise.loadBearing)
26
+ continue;
27
+ counts.set(premise.markerId, (counts.get(premise.markerId) ?? 0) + 1);
28
+ }
29
+ return new Set([...counts.entries()].filter(([, n]) => n > 1).map(([id]) => id));
30
+ }
31
+ export function evaluateParentAudit(deposit) {
32
+ const failures = [];
33
+ const colliding = collidingMarkerIds(deposit.premises);
34
+ for (const id of colliding) {
35
+ failures.push({
36
+ code: "marker-collision",
37
+ detail: `load-bearing premises share marker ${id}`,
38
+ });
39
+ }
40
+ for (const premise of deposit.premises) {
41
+ if (!premise.loadBearing)
42
+ continue;
43
+ if (!premise.sha || !premise.pointer || !premise.reading) {
44
+ failures.push({
45
+ code: "missing-token",
46
+ detail: `premise ${premise.markerId} is missing sha, pointer, or reading`,
47
+ });
48
+ continue;
49
+ }
50
+ const token = formatAuditToken({
51
+ markerId: premise.markerId,
52
+ sha: premise.sha,
53
+ pointer: premise.pointer,
54
+ reading: premise.reading,
55
+ });
56
+ if (!parseAuditToken(token)) {
57
+ failures.push({
58
+ code: "missing-token",
59
+ detail: `premise ${premise.markerId} token failed grammar`,
60
+ });
61
+ }
62
+ }
63
+ for (const clearance of deposit.clearances) {
64
+ if (clearance.clearedByRole === "parent") {
65
+ failures.push({
66
+ code: "parent-self-clear",
67
+ detail: `parent cleared marker ${clearance.markerId}`,
68
+ });
69
+ }
70
+ }
71
+ const computedUnresolved = deposit.premises
72
+ .filter((p) => p.loadBearing && !independentlyCleared(p.markerId, deposit.clearances, colliding))
73
+ .map((p) => p.markerId);
74
+ const named = deposit.namedAuditTargets ?? [];
75
+ if (named.length > 0) {
76
+ if (deposit.envelopes.length === 0) {
77
+ failures.push({
78
+ code: "envelope-omits-target",
79
+ detail: `no envelope named audit targets ${named.join(",")}`,
80
+ });
81
+ }
82
+ for (const envelope of deposit.envelopes) {
83
+ if (envelope.declaredNone) {
84
+ failures.push({
85
+ code: "envelope-omits-target",
86
+ detail: "envelope declared none while named audit targets exist",
87
+ });
88
+ continue;
89
+ }
90
+ for (const id of named) {
91
+ if (!envelope.auditTargets.includes(id)) {
92
+ failures.push({
93
+ code: "envelope-omits-target",
94
+ detail: `envelope omitted audit target ${id}`,
95
+ });
96
+ }
97
+ }
98
+ }
99
+ }
100
+ const bind = deposit.bindAttempt;
101
+ if (bind) {
102
+ const declared = new Set(bind.unresolvedMarkerIds);
103
+ for (const id of computedUnresolved) {
104
+ if (!declared.has(id)) {
105
+ failures.push({
106
+ code: "silent-clear",
107
+ detail: `marker ${id} dropped from unresolved without critic clearance`,
108
+ });
109
+ }
110
+ }
111
+ const unresolvedForBind = [...new Set([...computedUnresolved, ...bind.unresolvedMarkerIds])];
112
+ if (bind.allAcceptMap && unresolvedForBind.length > 0) {
113
+ failures.push({
114
+ code: "bind-unresolved",
115
+ detail: `all-accept bind with unresolved markers ${unresolvedForBind.join(",")}`,
116
+ });
117
+ }
118
+ }
119
+ return { ok: failures.length === 0, failures };
120
+ }
121
+ //# sourceMappingURL=parent-audit.js.map
@@ -106,6 +106,12 @@ export declare function checkCompletedLifecycleConsistency(projectRoot: string):
106
106
  * historical pre-#2862 corpora do not red-light doctor; scope:complete fails closed.
107
107
  */
108
108
  export declare function checkCompletedOpenItems(projectRoot: string): CheckResult;
109
+ /**
110
+ * Surface completed/ artifacts that never passed runTransition (#3679).
111
+ * Exit-exempt so historical corpora do not red-light doctor; the change-set
112
+ * verify (`verify:completed-write-guard`) fails closed on new adds.
113
+ */
114
+ export declare function checkCompletedUnguardedWrite(projectRoot: string): CheckResult;
109
115
  /**
110
116
  * Surface invalid coverageDebt + checkResume typed blocks (#3314).
111
117
  * Absent / default / valid typed values pass. Invalid blocks skip (advisory,
@@ -6,6 +6,7 @@ import { detectDualLayout, detectLegacyLayout, dualLayoutSignpostLine, legacyLay
6
6
  import { detectCanonicalVendoredManifest, isNpmManaged, NPM_MANAGED_SENTINEL_KEY, NPM_MANAGED_SENTINEL_VALUE, } from "../init-deposit/migrate.js";
7
7
  import { resolveLifecycleRoot } from "../layout/resolve.js";
8
8
  import { scanCompletedLifecycleConsistency } from "../lifecycle/completed-consistency.js";
9
+ import { scanCompletedWriteCorpus } from "../lifecycle/completed-write-guard.js";
9
10
  import { resolveCheckResume } from "../policy/check-resume.js";
10
11
  import { resolveCoverageDebt } from "../policy/coverage-debt.js";
11
12
  import { classifyXbriefSchemaDistance } from "../staleness-tickler/probe-xbrief.js";
@@ -34,6 +35,7 @@ export const DOCTOR_ADVISORY_FAIL_CHECKS = new Set([
34
35
  "stale-xbrief-schema-deposit",
35
36
  "typescript-7-side-by-side",
36
37
  "completed-open-items",
38
+ "completed-unguarded-write",
37
39
  "coverage-check-resume-policy",
38
40
  ]);
39
41
  /** True when a check fail must stay a warning (not lastErrorCount / exit 1). */
@@ -1167,6 +1169,37 @@ export function checkCompletedOpenItems(projectRoot) {
1167
1169
  },
1168
1170
  };
1169
1171
  }
1172
+ /**
1173
+ * Surface completed/ artifacts that never passed runTransition (#3679).
1174
+ * Exit-exempt so historical corpora do not red-light doctor; the change-set
1175
+ * verify (`verify:completed-write-guard`) fails closed on new adds.
1176
+ */
1177
+ export function checkCompletedUnguardedWrite(projectRoot) {
1178
+ const checkName = "completed-unguarded-write";
1179
+ const result = scanCompletedWriteCorpus(projectRoot);
1180
+ if (result.findings.length === 0) {
1181
+ return {
1182
+ name: checkName,
1183
+ status: "pass",
1184
+ detail: "Completed write-guard OK (#3679): no completed/ artifacts lack a runTransition write",
1185
+ data: { finding_count: 0, scanned: result.scanned },
1186
+ };
1187
+ }
1188
+ const message = `Completed unguarded writes (#3679, advisory). ` +
1189
+ `${result.findings.length} artifact(s) under completed/ have no runTransition write:\n` +
1190
+ result.findings.map((f) => ` - ${f.detail}`).join("\n") +
1191
+ `\nNew adds fail closed via verify:completed-write-guard; historical corpus is advisory.`;
1192
+ return {
1193
+ name: checkName,
1194
+ status: "fail",
1195
+ detail: message,
1196
+ data: {
1197
+ finding_count: result.findings.length,
1198
+ advisory: true,
1199
+ findings: result.findings.map((f) => ({ rel_path: f.relPath, detail: f.detail })),
1200
+ },
1201
+ };
1202
+ }
1170
1203
  /**
1171
1204
  * Surface invalid coverageDebt + checkResume typed blocks (#3314).
1172
1205
  * Absent / default / valid typed values pass. Invalid blocks skip (advisory,
@@ -1248,6 +1281,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
1248
1281
  checks.push(checkCoverageCheckResumePolicy(projectRoot));
1249
1282
  checks.push(checkCompletedLifecycleConsistency(projectRoot));
1250
1283
  checks.push(checkCompletedOpenItems(projectRoot));
1284
+ checks.push(checkCompletedUnguardedWrite(projectRoot));
1251
1285
  return {
1252
1286
  projectRoot,
1253
1287
  installRoot: null,
@@ -1270,6 +1304,7 @@ export function runChecksImpl(projectRoot, seams = {}) {
1270
1304
  checks.push(checkCoverageCheckResumePolicy(projectRoot));
1271
1305
  checks.push(checkCompletedLifecycleConsistency(projectRoot));
1272
1306
  checks.push(checkCompletedOpenItems(projectRoot));
1307
+ checks.push(checkCompletedUnguardedWrite(projectRoot));
1273
1308
  return {
1274
1309
  projectRoot,
1275
1310
  installRoot,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Intersect an Istanbul coverage-final.json report with changed lines (#3514).
3
+ *
4
+ * The 90% default is per-change coverage of added/modified branches.
5
+ * The project vitest floor (75) is a collapse detector for the aggregate.
6
+ * They are not interchangeable.
7
+ */
8
+ /** Per-commit coverage of added/modified branches. Not the 75 global floor. */
9
+ export declare const DEFAULT_DIFF_COVERAGE_THRESHOLD = 90;
10
+ export interface UncoveredChangedBranch {
11
+ readonly path: string;
12
+ readonly line: number;
13
+ readonly branchId: string;
14
+ readonly pathIndex: number;
15
+ readonly type: string;
16
+ }
17
+ export interface DiffCoverageReport {
18
+ readonly reportPresent: boolean;
19
+ readonly skippedReason: string | null;
20
+ readonly changedBranchTotal: number;
21
+ readonly changedBranchCovered: number;
22
+ readonly percent: number;
23
+ readonly threshold: number;
24
+ readonly uncovered: readonly UncoveredChangedBranch[];
25
+ readonly belowThreshold: boolean;
26
+ }
27
+ export interface DiffCoverageOptions {
28
+ readonly projectRoot: string;
29
+ readonly coverageReportPath: string;
30
+ readonly changedLinesByFile: ReadonlyMap<string, ReadonlySet<number>>;
31
+ readonly threshold?: number;
32
+ }
33
+ /**
34
+ * Measure coverage of Istanbul branch paths whose line is in the diff.
35
+ * Missing or unreadable reports skip (warn-first); they are not a config error.
36
+ */
37
+ export declare function evaluateDiffCoverage(options: DiffCoverageOptions): DiffCoverageReport;
38
+ export declare function hasDiffCoverageFindings(report: DiffCoverageReport): boolean;
39
+ //# sourceMappingURL=diff-coverage.d.ts.map
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Intersect an Istanbul coverage-final.json report with changed lines (#3514).
3
+ *
4
+ * The 90% default is per-change coverage of added/modified branches.
5
+ * The project vitest floor (75) is a collapse detector for the aggregate.
6
+ * They are not interchangeable.
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { isAbsolute, relative } from "node:path";
10
+ /** Per-commit coverage of added/modified branches. Not the 75 global floor. */
11
+ export const DEFAULT_DIFF_COVERAGE_THRESHOLD = 90;
12
+ function toPosix(p) {
13
+ return p.replace(/\\/g, "/");
14
+ }
15
+ function normalizeReportPath(projectRoot, rawKey, file) {
16
+ const candidate = file.path && file.path.length > 0 ? file.path : rawKey;
17
+ const rel = isAbsolute(candidate) ? relative(projectRoot, candidate) : candidate;
18
+ return toPosix(rel);
19
+ }
20
+ function emptyReport(threshold, skippedReason, reportPresent) {
21
+ return {
22
+ reportPresent,
23
+ skippedReason,
24
+ changedBranchTotal: 0,
25
+ changedBranchCovered: 0,
26
+ percent: 100,
27
+ threshold,
28
+ uncovered: [],
29
+ belowThreshold: false,
30
+ };
31
+ }
32
+ function branchLine(meta, pathIndex) {
33
+ const loc = meta?.locations?.[pathIndex];
34
+ return loc?.start?.line ?? meta?.loc?.start?.line ?? meta?.line ?? 0;
35
+ }
36
+ /**
37
+ * Measure coverage of Istanbul branch paths whose line is in the diff.
38
+ * Missing or unreadable reports skip (warn-first); they are not a config error.
39
+ */
40
+ export function evaluateDiffCoverage(options) {
41
+ const threshold = options.threshold ?? DEFAULT_DIFF_COVERAGE_THRESHOLD;
42
+ const reportPath = options.coverageReportPath;
43
+ if (!existsSync(reportPath)) {
44
+ return emptyReport(threshold, `coverage report missing at ${reportPath}`, false);
45
+ }
46
+ let rawReport;
47
+ try {
48
+ rawReport = JSON.parse(readFileSync(reportPath, "utf8"));
49
+ }
50
+ catch (err) {
51
+ return emptyReport(threshold, `unreadable coverage report at ${reportPath}: ${String(err.message)}`, false);
52
+ }
53
+ const changedByPosix = new Map();
54
+ for (const [path, lines] of options.changedLinesByFile) {
55
+ changedByPosix.set(toPosix(path), lines);
56
+ }
57
+ const uncovered = [];
58
+ let total = 0;
59
+ let covered = 0;
60
+ for (const [rawKey, file] of Object.entries(rawReport)) {
61
+ const relPath = normalizeReportPath(options.projectRoot, rawKey, file);
62
+ const changedLines = changedByPosix.get(relPath);
63
+ if (changedLines === undefined || changedLines.size === 0) {
64
+ continue;
65
+ }
66
+ if (!file.b) {
67
+ continue;
68
+ }
69
+ for (const [branchId, hits] of Object.entries(file.b)) {
70
+ const meta = file.branchMap?.[branchId];
71
+ for (let pathIndex = 0; pathIndex < hits.length; pathIndex += 1) {
72
+ const count = hits[pathIndex] ?? 0;
73
+ const line = branchLine(meta, pathIndex);
74
+ if (!changedLines.has(line)) {
75
+ continue;
76
+ }
77
+ total += 1;
78
+ if (count > 0) {
79
+ covered += 1;
80
+ continue;
81
+ }
82
+ uncovered.push({
83
+ path: relPath,
84
+ line,
85
+ branchId,
86
+ pathIndex,
87
+ type: meta?.type ?? "branch",
88
+ });
89
+ }
90
+ }
91
+ }
92
+ uncovered.sort((a, b) => a.path.localeCompare(b.path) ||
93
+ a.line - b.line ||
94
+ a.branchId.localeCompare(b.branchId) ||
95
+ a.pathIndex - b.pathIndex);
96
+ const percent = total === 0 ? 100 : (covered / total) * 100;
97
+ const belowThreshold = total > 0 && percent + 1e-9 < threshold;
98
+ return {
99
+ reportPresent: true,
100
+ skippedReason: null,
101
+ changedBranchTotal: total,
102
+ changedBranchCovered: covered,
103
+ percent,
104
+ threshold,
105
+ uncovered,
106
+ belowThreshold,
107
+ };
108
+ }
109
+ export function hasDiffCoverageFindings(report) {
110
+ return report.uncovered.length > 0 || report.belowThreshold;
111
+ }
112
+ //# sourceMappingURL=diff-coverage.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Parse unified diffs into added/modified new-file line numbers (#3514).
3
+ */
4
+ /** Repo-relative path -> 1-indexed new-file line numbers introduced by the diff. */
5
+ export type ChangedLineMap = Map<string, Set<number>>;
6
+ /** Decode a Git C-quoted path (`"foo\\tbar"` → `foo\tbar`). */
7
+ export declare function unescapeGitQuotedPath(raw: string): string;
8
+ /** Strip git `a/` `b/` prefixes and quotes from a `+++` path. */
9
+ export declare function stripGitDiffPath(raw: string): string;
10
+ /**
11
+ * Collect 1-indexed lines added on the new side of a unified diff (`-U0` ok).
12
+ * Deletions-only hunks contribute nothing. `/dev/null` destinations are skipped.
13
+ */
14
+ export declare function parseUnifiedDiffAddedLines(diffText: string): ChangedLineMap;
15
+ /** Count physical lines in file content (no trailing empty split artifact). */
16
+ export declare function countFileLines(content: string): number;
17
+ /** Treat every line of an untracked file as added. */
18
+ export declare function allLinesChanged(lineCount: number): Set<number>;
19
+ //# sourceMappingURL=diff-lines.d.ts.map