@deftai/directive-core 0.104.0 → 0.105.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 (225) hide show
  1. package/dist/authz/classify.js +526 -42
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +33 -3
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/fs/contained-write.d.ts +115 -1
  17. package/dist/fs/contained-write.js +241 -5
  18. package/dist/fs/mutation-ledger.d.ts +54 -0
  19. package/dist/fs/mutation-ledger.js +133 -0
  20. package/dist/fs/non-product-dirs.d.ts +79 -0
  21. package/dist/fs/non-product-dirs.js +96 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -1
  23. package/dist/hooks/dispatcher.js +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.js +17 -15
  27. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  28. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  29. package/dist/init-deposit/gitignore.js +9 -0
  30. package/dist/init-deposit/hygiene.d.ts +47 -11
  31. package/dist/init-deposit/hygiene.js +172 -37
  32. package/dist/init-deposit/legacy-detect.js +6 -2
  33. package/dist/init-deposit/refresh.d.ts +21 -1
  34. package/dist/init-deposit/refresh.js +106 -9
  35. package/dist/init-deposit/scaffold.js +28 -279
  36. package/dist/init-deposit/slash-deposit.js +13 -7
  37. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  38. package/dist/init-deposit/xbrief-projections.js +24 -11
  39. package/dist/intake/clause-derivation.d.ts +38 -1
  40. package/dist/intake/clause-derivation.js +211 -9
  41. package/dist/intake/issue-ingest.d.ts +3 -1
  42. package/dist/intake/issue-ingest.js +35 -6
  43. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  44. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  45. package/dist/literal-acceptance/capture.d.ts +10 -0
  46. package/dist/literal-acceptance/capture.js +224 -75
  47. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  48. package/dist/literal-acceptance/evaluate.js +70 -2
  49. package/dist/literal-acceptance/index.d.ts +3 -3
  50. package/dist/literal-acceptance/index.js +3 -3
  51. package/dist/literal-acceptance/safety.d.ts +41 -0
  52. package/dist/literal-acceptance/safety.js +126 -2
  53. package/dist/literal-acceptance/types.d.ts +8 -0
  54. package/dist/orphan-active/evaluate.d.ts +7 -1
  55. package/dist/orphan-active/evaluate.js +102 -23
  56. package/dist/platform/platform-capabilities.js +2 -3
  57. package/dist/policy/base-branch.d.ts +37 -0
  58. package/dist/policy/base-branch.js +114 -0
  59. package/dist/policy/branch-sync.d.ts +80 -0
  60. package/dist/policy/branch-sync.js +211 -0
  61. package/dist/policy/ceremony-dial-escalation.js +1 -0
  62. package/dist/policy/ceremony-dial.js +4 -14
  63. package/dist/policy/delivery-branch.d.ts +5 -3
  64. package/dist/policy/delivery-branch.js +7 -3
  65. package/dist/policy/file-size-thresholds.d.ts +24 -0
  66. package/dist/policy/file-size-thresholds.js +23 -0
  67. package/dist/policy/forge-outage-retry.d.ts +47 -0
  68. package/dist/policy/forge-outage-retry.js +157 -0
  69. package/dist/policy/index.d.ts +7 -0
  70. package/dist/policy/index.js +66 -7
  71. package/dist/policy/project-invariants.d.ts +50 -0
  72. package/dist/policy/project-invariants.js +205 -0
  73. package/dist/policy/sync-default.d.ts +129 -0
  74. package/dist/policy/sync-default.js +452 -0
  75. package/dist/policy/sync-max-files.d.ts +93 -0
  76. package/dist/policy/sync-max-files.js +192 -0
  77. package/dist/pr-monitor/absent-required.d.ts +11 -0
  78. package/dist/pr-monitor/absent-required.js +61 -0
  79. package/dist/pr-monitor/constants.d.ts +7 -0
  80. package/dist/pr-monitor/constants.js +7 -0
  81. package/dist/pr-monitor/index.d.ts +2 -1
  82. package/dist/pr-monitor/index.js +2 -1
  83. package/dist/pr-monitor/main.js +4 -0
  84. package/dist/pr-monitor/monitor.js +24 -1
  85. package/dist/pr-wait-mergeable/classify.js +6 -0
  86. package/dist/preflight/evaluate.d.ts +4 -0
  87. package/dist/preflight/evaluate.js +35 -1
  88. package/dist/preflight/index.d.ts +4 -0
  89. package/dist/preflight/index.js +2 -0
  90. package/dist/preflight/intended-placement.d.ts +62 -0
  91. package/dist/preflight/intended-placement.js +275 -0
  92. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  93. package/dist/preflight/project-invariants-gate.js +82 -0
  94. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  95. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  96. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  97. package/dist/product-first-done-gate/acceptance.js +79 -8
  98. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  99. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  100. package/dist/product-first-done-gate/evaluate.d.ts +12 -0
  101. package/dist/product-first-done-gate/evaluate.js +254 -31
  102. package/dist/product-first-done-gate/index.d.ts +2 -0
  103. package/dist/product-first-done-gate/index.js +1 -0
  104. package/dist/release/build-dist.d.ts +101 -0
  105. package/dist/release/build-dist.js +428 -46
  106. package/dist/run-summary/emit.d.ts +16 -10
  107. package/dist/run-summary/emit.js +26 -15
  108. package/dist/run-summary/index.d.ts +3 -2
  109. package/dist/run-summary/index.js +2 -1
  110. package/dist/run-summary/session-id.d.ts +21 -0
  111. package/dist/run-summary/session-id.js +92 -0
  112. package/dist/run-summary/types.d.ts +29 -4
  113. package/dist/run-summary/types.js +1 -0
  114. package/dist/scope/acceptance-evidence.d.ts +19 -7
  115. package/dist/scope/acceptance-evidence.js +38 -11
  116. package/dist/scope/delivery-evidence.d.ts +3 -3
  117. package/dist/scope/delivery-evidence.js +22 -27
  118. package/dist/scope/index.d.ts +1 -0
  119. package/dist/scope/index.js +1 -0
  120. package/dist/scope/parent-lineage.js +7 -6
  121. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  122. package/dist/scope/project-invariant-coverage.js +322 -0
  123. package/dist/scope/transition.js +1 -1
  124. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  125. package/dist/scope-provenance/compare-intent.js +233 -0
  126. package/dist/scope-provenance/digest.d.ts +18 -1
  127. package/dist/scope-provenance/digest.js +16 -11
  128. package/dist/scope-provenance/evaluate.d.ts +5 -1
  129. package/dist/scope-provenance/evaluate.js +64 -4
  130. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  131. package/dist/scope-provenance/extract-intent.js +262 -0
  132. package/dist/scope-provenance/index.d.ts +7 -1
  133. package/dist/scope-provenance/index.js +7 -1
  134. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  135. package/dist/scope-provenance/intent-digest.js +33 -0
  136. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  137. package/dist/scope-provenance/intent-evaluate.js +149 -0
  138. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  139. package/dist/scope-provenance/json-tokenizer.js +225 -0
  140. package/dist/scope-provenance/known-machine.d.ts +38 -0
  141. package/dist/scope-provenance/known-machine.js +157 -0
  142. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  143. package/dist/scope-provenance/mint-artifacts.js +425 -0
  144. package/dist/session/ac-pass-banking.d.ts +4 -36
  145. package/dist/session/ac-pass-banking.js +30 -77
  146. package/dist/session/ac-pass-reuse.d.ts +53 -0
  147. package/dist/session/ac-pass-reuse.js +95 -0
  148. package/dist/session/git.js +19 -4
  149. package/dist/session/index.d.ts +4 -0
  150. package/dist/session/index.js +4 -0
  151. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  152. package/dist/session/occupancy-stress-worker.js +36 -0
  153. package/dist/session/occupancy.d.ts +97 -0
  154. package/dist/session/occupancy.js +422 -0
  155. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  156. package/dist/session/product-state-hash.d.ts +26 -0
  157. package/dist/session/product-state-hash.js +683 -0
  158. package/dist/session/session-ready.d.ts +2 -0
  159. package/dist/session/session-ready.js +52 -30
  160. package/dist/session/session-start.d.ts +7 -0
  161. package/dist/session/session-start.js +71 -7
  162. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  163. package/dist/session/verify-ac-session-cache.js +126 -0
  164. package/dist/session/verify-session-ritual.d.ts +5 -0
  165. package/dist/session/verify-session-ritual.js +9 -0
  166. package/dist/slash/openclaw-deposit.js +13 -9
  167. package/dist/slice/constants.d.ts +2 -2
  168. package/dist/slice/constants.js +2 -2
  169. package/dist/slice/existing.js +9 -3
  170. package/dist/slice/lock.d.ts +16 -1
  171. package/dist/slice/lock.js +137 -11
  172. package/dist/story-ready/evaluate.d.ts +2 -0
  173. package/dist/story-ready/evaluate.js +13 -0
  174. package/dist/swarm/complete-cohort.d.ts +3 -0
  175. package/dist/swarm/complete-cohort.js +59 -11
  176. package/dist/swarm/finalize-cohort.js +33 -2
  177. package/dist/swarm/launch.d.ts +22 -0
  178. package/dist/swarm/launch.js +155 -0
  179. package/dist/swarm/routing-set-cli.d.ts +11 -1
  180. package/dist/swarm/routing-set-cli.js +13 -3
  181. package/dist/swarm/routing-verify.d.ts +12 -0
  182. package/dist/swarm/routing-verify.js +59 -20
  183. package/dist/swarm/routing.d.ts +14 -0
  184. package/dist/swarm/routing.js +53 -3
  185. package/dist/telemetry-coverage/fake-trial.js +10 -0
  186. package/dist/telemetry-coverage/kinds.js +2 -0
  187. package/dist/triage/actions/index.js +4 -1
  188. package/dist/ts-check-lane/index.d.ts +3 -0
  189. package/dist/ts-check-lane/index.js +2 -0
  190. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  191. package/dist/ts-check-lane/progress-reporter.js +38 -0
  192. package/dist/ts-check-lane/progress.d.ts +32 -0
  193. package/dist/ts-check-lane/progress.js +57 -0
  194. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  195. package/dist/ts-check-lane/run-lane.js +8 -6
  196. package/dist/validate-content/validate-links.d.ts +8 -0
  197. package/dist/validate-content/validate-links.js +9 -15
  198. package/dist/value/feedback-file.js +12 -2
  199. package/dist/vbrief-build/index.d.ts +1 -0
  200. package/dist/vbrief-build/index.js +1 -0
  201. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  202. package/dist/vbrief-build/project-invariants-io.js +41 -0
  203. package/dist/vbrief-reconcile/graph.js +18 -1
  204. package/dist/vbrief-reconcile/index.d.ts +1 -1
  205. package/dist/vbrief-reconcile/index.js +1 -1
  206. package/dist/vbrief-reconcile/types.d.ts +13 -0
  207. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  208. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  209. package/dist/vbrief-validate/index.d.ts +1 -1
  210. package/dist/vbrief-validate/index.js +1 -1
  211. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  212. package/dist/vbrief-validate/plan-hooks.js +68 -0
  213. package/dist/verify-ac/evaluate.d.ts +7 -1
  214. package/dist/verify-ac/evaluate.js +34 -26
  215. package/dist/verify-ac/flag.d.ts +4 -0
  216. package/dist/verify-ac/flag.js +16 -0
  217. package/dist/verify-ac/index.d.ts +2 -2
  218. package/dist/verify-ac/index.js +2 -2
  219. package/dist/verify-source/contained-writes.js +2 -0
  220. package/dist/verify-source/verify-stubs.d.ts +15 -1
  221. package/dist/verify-source/verify-stubs.js +20 -16
  222. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  223. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  224. package/dist/xbrief-migrate/migrate-project.js +2 -5
  225. package/package.json +3 -3
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Typed plan.policy.syncMaxFiles (#3390 / #3377 Wave 2).
3
+ *
4
+ * One integer. Unset is 400. `--max-files` overrides one run and does not
5
+ * write policy. Set 100 if SLizard is a required check. Do not probe
6
+ * reviewer APIs and do not encode vendor limits as code constants.
7
+ *
8
+ * File-count warn uses the shared #3388 detector. Count is
9
+ * `git diff --name-only origin/<dest>...origin/<source>`. No warn when
10
+ * the detector says this is not a sync. The #3391 verb consumes
11
+ * evaluateSyncMaxFilesWarn and the maxFiles one-run override.
12
+ */
13
+ import { defaultGitRunner } from "../session/git.js";
14
+ import { detectBranchSyncFromProject } from "./branch-sync.js";
15
+ import { readPlanPolicy } from "./plan-extensions.js";
16
+ import { loadProjectDefinition } from "./resolve.js";
17
+ export const FIELD_SYNC_MAX_FILES = "plan.policy.syncMaxFiles";
18
+ export const FIELD_SYNC_MAX_FILES_CLI_ALIAS = "syncMaxFiles";
19
+ /** Unset threshold. Not a vendor limit. */
20
+ export const DEFAULT_SYNC_MAX_FILES = 400;
21
+ /**
22
+ * Operator docs for the threshold. Recommendation only — not a coded
23
+ * Greptile or SLizard constant.
24
+ */
25
+ export const SYNC_MAX_FILES_DOCS = "Unset is 400. Set plan.policy.syncMaxFiles to 100 if SLizard is a required check.";
26
+ function isValidMaxFiles(raw) {
27
+ return typeof raw === "number" && Number.isInteger(raw) && raw >= 0;
28
+ }
29
+ /** Parse `--max-files`. Invalid or absent values are ignored. */
30
+ export function parseMaxFilesFlag(raw) {
31
+ if (!isValidMaxFiles(raw))
32
+ return null;
33
+ return raw;
34
+ }
35
+ /**
36
+ * Resolve the file-count threshold.
37
+ *
38
+ * `--max-files` (maxFiles) wins for this call only and is never written.
39
+ */
40
+ export function resolveSyncMaxFiles(projectRoot, maxFiles) {
41
+ const flag = parseMaxFilesFlag(maxFiles);
42
+ if (flag !== null) {
43
+ return {
44
+ maxFiles: flag,
45
+ source: "flag",
46
+ provenance: "flag",
47
+ error: null,
48
+ };
49
+ }
50
+ if (projectRoot !== undefined && projectRoot !== null && projectRoot.length > 0) {
51
+ const [data, err] = loadProjectDefinition(projectRoot);
52
+ if (data !== null) {
53
+ const policyBlock = readPlanPolicy(data.plan);
54
+ if (typeof policyBlock === "object" &&
55
+ policyBlock !== null &&
56
+ !Array.isArray(policyBlock) &&
57
+ "syncMaxFiles" in policyBlock) {
58
+ const raw = policyBlock.syncMaxFiles;
59
+ if (isValidMaxFiles(raw)) {
60
+ return {
61
+ maxFiles: raw,
62
+ source: "typed",
63
+ provenance: "policy",
64
+ error: null,
65
+ };
66
+ }
67
+ return {
68
+ maxFiles: DEFAULT_SYNC_MAX_FILES,
69
+ source: "default-on-error",
70
+ provenance: "default",
71
+ error: `${FIELD_SYNC_MAX_FILES} must be a non-negative integer; got ${String(raw)}`,
72
+ };
73
+ }
74
+ }
75
+ else if (err !== null && err.length > 0) {
76
+ return {
77
+ maxFiles: DEFAULT_SYNC_MAX_FILES,
78
+ source: "default",
79
+ provenance: "default",
80
+ error: err,
81
+ };
82
+ }
83
+ }
84
+ return {
85
+ maxFiles: DEFAULT_SYNC_MAX_FILES,
86
+ source: "default",
87
+ provenance: "default",
88
+ error: null,
89
+ };
90
+ }
91
+ /** Inspector row for `task policy:show --field=syncMaxFiles`. */
92
+ export function inspectSyncMaxFiles(_data, projectRoot) {
93
+ const resolved = resolveSyncMaxFiles(projectRoot);
94
+ return {
95
+ name: FIELD_SYNC_MAX_FILES,
96
+ current: resolved.maxFiles,
97
+ default: DEFAULT_SYNC_MAX_FILES,
98
+ source: resolved.source === "flag" ? "default" : resolved.source,
99
+ };
100
+ }
101
+ /** Count files in `origin/<dest>...origin/<source>`. */
102
+ export function countSyncChangedFiles(options) {
103
+ const dest = options.dest.trim();
104
+ const source = options.source.trim();
105
+ if (dest.length === 0 || source.length === 0) {
106
+ return { count: null, error: "empty dest or source" };
107
+ }
108
+ const runGit = options.runGit ?? defaultGitRunner;
109
+ runGit(options.projectRoot, ["fetch", "--quiet", "origin", dest]);
110
+ const ranged = `origin/${dest}...origin/${source}`;
111
+ const diffed = runGit(options.projectRoot, ["diff", "--name-only", ranged]);
112
+ if (diffed.code !== 0) {
113
+ return { count: null, error: diffed.stderr || `git diff failed for ${ranged}` };
114
+ }
115
+ const files = diffed.stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
116
+ return { count: files.length, error: null };
117
+ }
118
+ export function formatSyncMaxFilesWarn(options) {
119
+ return (`file-count warn: ${options.count} files origin/${options.dest}...origin/${options.source} ` +
120
+ `exceeds syncMaxFiles=${options.threshold} (${options.provenance})`);
121
+ }
122
+ /**
123
+ * Warn when a detected sync exceeds syncMaxFiles.
124
+ *
125
+ * No warn when the shared detector says this is not a sync.
126
+ */
127
+ export function evaluateSyncMaxFilesWarn(input) {
128
+ const runGit = input.runGit ?? defaultGitRunner;
129
+ const sync = input.sync ??
130
+ detectBranchSyncFromProject({
131
+ projectRoot: input.projectRoot,
132
+ prBase: input.prBase,
133
+ headSha: input.headSha,
134
+ runGit,
135
+ });
136
+ const resolved = resolveSyncMaxFiles(input.projectRoot, input.maxFiles);
137
+ const empty = {
138
+ warn: false,
139
+ reason: "not-sync",
140
+ count: null,
141
+ threshold: resolved.maxFiles,
142
+ provenance: resolved.provenance,
143
+ dest: sync.dest,
144
+ source: sync.source,
145
+ message: "",
146
+ };
147
+ if (!sync.isSync) {
148
+ return empty;
149
+ }
150
+ const counted = countSyncChangedFiles({
151
+ dest: sync.dest,
152
+ source: sync.source,
153
+ projectRoot: input.projectRoot,
154
+ runGit,
155
+ });
156
+ if (counted.count === null) {
157
+ return {
158
+ ...empty,
159
+ reason: "diff-failed",
160
+ message: counted.error ?? "git diff failed",
161
+ };
162
+ }
163
+ if (counted.count <= resolved.maxFiles) {
164
+ return {
165
+ warn: false,
166
+ reason: "within-limit",
167
+ count: counted.count,
168
+ threshold: resolved.maxFiles,
169
+ provenance: resolved.provenance,
170
+ dest: sync.dest,
171
+ source: sync.source,
172
+ message: "",
173
+ };
174
+ }
175
+ return {
176
+ warn: true,
177
+ reason: "exceeds",
178
+ count: counted.count,
179
+ threshold: resolved.maxFiles,
180
+ provenance: resolved.provenance,
181
+ dest: sync.dest,
182
+ source: sync.source,
183
+ message: formatSyncMaxFilesWarn({
184
+ count: counted.count,
185
+ threshold: resolved.maxFiles,
186
+ provenance: resolved.provenance,
187
+ dest: sync.dest,
188
+ source: sync.source,
189
+ }),
190
+ };
191
+ }
192
+ //# sourceMappingURL=sync-max-files.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Consume #3234 `ci_absent_required` from a readiness payload.
3
+ * Returns missing context names, or null when the poll is not absent-required.
4
+ * Pending required check-runs are a wait, not an absence.
5
+ */
6
+ export declare function readAbsentRequiredContexts(payload: Record<string, unknown>): readonly string[] | null;
7
+ /** True once consecutive absent polls have passed the first-poll grace window. */
8
+ export declare function shouldEscalateAbsentRequired(consecutivePolls: number, gracePolls?: number): boolean;
9
+ /** Distinct exit naming that lists the missing required contexts. */
10
+ export declare function formatAbsentRequiredMessage(contexts: readonly string[]): string;
11
+ //# sourceMappingURL=absent-required.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { ABSENT_REQUIRED_GRACE_POLLS } from "./constants.js";
2
+ function asRecord(value) {
3
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
4
+ return null;
5
+ }
6
+ return value;
7
+ }
8
+ function ciFromPayload(payload) {
9
+ const partial = asRecord(payload.partial_data);
10
+ if (partial === null) {
11
+ return null;
12
+ }
13
+ return asRecord(partial.ci);
14
+ }
15
+ function stringList(value) {
16
+ if (!Array.isArray(value)) {
17
+ return [];
18
+ }
19
+ return value.filter((item) => typeof item === "string");
20
+ }
21
+ /**
22
+ * Consume #3234 `ci_absent_required` from a readiness payload.
23
+ * Returns missing context names, or null when the poll is not absent-required.
24
+ * Pending required check-runs are a wait, not an absence.
25
+ */
26
+ export function readAbsentRequiredContexts(payload) {
27
+ const ci = ciFromPayload(payload);
28
+ if (ci !== null) {
29
+ const pending = stringList(ci.pending_required);
30
+ if (pending.length > 0) {
31
+ return null;
32
+ }
33
+ const readyState = ci.ready_state;
34
+ if (readyState === "not_ready_yet") {
35
+ return null;
36
+ }
37
+ if (readyState === "ci_absent_required") {
38
+ return stringList(ci.absent_required);
39
+ }
40
+ // Structured CI present but not absent-required (e.g. ci_failures).
41
+ // Do not let failure-text mention of ci_absent_required override it.
42
+ return null;
43
+ }
44
+ const failures = Array.isArray(payload.failures) ? payload.failures.map(String) : [];
45
+ if (failures.some((failure) => failure.includes("ci_absent_required"))) {
46
+ return [];
47
+ }
48
+ return null;
49
+ }
50
+ /** True once consecutive absent polls have passed the first-poll grace window. */
51
+ export function shouldEscalateAbsentRequired(consecutivePolls, gracePolls = ABSENT_REQUIRED_GRACE_POLLS) {
52
+ return consecutivePolls > gracePolls;
53
+ }
54
+ /** Distinct exit naming that lists the missing required contexts. */
55
+ export function formatAbsentRequiredMessage(contexts) {
56
+ if (contexts.length === 0) {
57
+ return "ABSENT-REQUIRED: required status-check context never appeared";
58
+ }
59
+ return `ABSENT-REQUIRED: ${contexts.join(", ")}`;
60
+ }
61
+ //# sourceMappingURL=absent-required.js.map
@@ -3,6 +3,13 @@ export declare const EXIT_CLEAN = 0;
3
3
  export declare const EXIT_CAP_REACHED = 1;
4
4
  export declare const EXIT_CONFIG_ERROR = 2;
5
5
  export declare const EXIT_PR_TERMINAL = 3;
6
+ /** Sustained required-context absence (#3389). Distinct from CAP-REACHED. */
7
+ export declare const EXIT_ABSENT_REQUIRED = 4;
8
+ /**
9
+ * First-poll absence is normal (check-runs take time after a head push).
10
+ * Escalate on the next consecutive `ci_absent_required` poll (#3389).
11
+ */
12
+ export declare const ABSENT_REQUIRED_GRACE_POLLS = 1;
6
13
  /** Adaptive cadence (seconds, repeats). Last repeat is a soft ceiling. */
7
14
  export declare const DEFAULT_CADENCE: ReadonlyArray<readonly [number, number]>;
8
15
  //# sourceMappingURL=constants.d.ts.map
@@ -3,6 +3,13 @@ export const EXIT_CLEAN = 0;
3
3
  export const EXIT_CAP_REACHED = 1;
4
4
  export const EXIT_CONFIG_ERROR = 2;
5
5
  export const EXIT_PR_TERMINAL = 3;
6
+ /** Sustained required-context absence (#3389). Distinct from CAP-REACHED. */
7
+ export const EXIT_ABSENT_REQUIRED = 4;
8
+ /**
9
+ * First-poll absence is normal (check-runs take time after a head push).
10
+ * Escalate on the next consecutive `ci_absent_required` poll (#3389).
11
+ */
12
+ export const ABSENT_REQUIRED_GRACE_POLLS = 1;
6
13
  /** Adaptive cadence (seconds, repeats). Last repeat is a soft ceiling. */
7
14
  export const DEFAULT_CADENCE = [
8
15
  [60, 3],
@@ -1,5 +1,6 @@
1
+ export { formatAbsentRequiredMessage, readAbsentRequiredContexts, shouldEscalateAbsentRequired, } from "./absent-required.js";
1
2
  export { cadenceIntervalAfterPoll, cadenceIntervals } from "./cadence.js";
2
- export { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
+ export { ABSENT_REQUIRED_GRACE_POLLS, DEFAULT_CADENCE, EXIT_ABSENT_REQUIRED, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
4
  export { cmdPrMonitor, parseMonitorArgs, runMonitor } from "./main.js";
4
5
  export { formatPollStatus, isTerminalPrState, mergeStateFromPayload, monitor, summaryLabelForExit, } from "./monitor.js";
5
6
  export { callReadiness } from "./readiness.js";
@@ -1,5 +1,6 @@
1
+ export { formatAbsentRequiredMessage, readAbsentRequiredContexts, shouldEscalateAbsentRequired, } from "./absent-required.js";
1
2
  export { cadenceIntervalAfterPoll, cadenceIntervals } from "./cadence.js";
2
- export { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
+ export { ABSENT_REQUIRED_GRACE_POLLS, DEFAULT_CADENCE, EXIT_ABSENT_REQUIRED, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
4
  export { cmdPrMonitor, parseMonitorArgs, runMonitor } from "./main.js";
4
5
  export { formatPollStatus, isTerminalPrState, mergeStateFromPayload, monitor, summaryLabelForExit, } from "./monitor.js";
5
6
  export { callReadiness } from "./readiness.js";
@@ -183,6 +183,10 @@ export function runMonitor(argv, options = {}) {
183
183
  if (typeof err === "string" && err.length > 0) {
184
184
  lines.push(` error: ${err}`);
185
185
  }
186
+ const missingRaw = payload.monitor_absent_required;
187
+ if (Array.isArray(missingRaw) && missingRaw.length > 0) {
188
+ lines.push(` missing: ${missingRaw.map(String).join(", ")}`);
189
+ }
186
190
  const failuresRaw = payload.failures;
187
191
  if (Array.isArray(failuresRaw)) {
188
192
  failuresRaw.forEach((fail, index) => {
@@ -1,5 +1,6 @@
1
+ import { formatAbsentRequiredMessage, readAbsentRequiredContexts, shouldEscalateAbsentRequired, } from "./absent-required.js";
1
2
  import { cadenceIntervalAfterPoll, cadenceIntervals } from "./cadence.js";
2
- import { DEFAULT_CADENCE, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
+ import { DEFAULT_CADENCE, EXIT_ABSENT_REQUIRED, EXIT_CAP_REACHED, EXIT_CLEAN, EXIT_CONFIG_ERROR, EXIT_PR_TERMINAL, } from "./constants.js";
3
4
  import { callReadiness } from "./readiness.js";
4
5
  const systemMonotonicClock = {
5
6
  now() {
@@ -141,6 +142,7 @@ export function monitor(prNumber, repo, options = {}) {
141
142
  let lastPayload = {};
142
143
  let lastExit = EXIT_CAP_REACHED;
143
144
  let priorCadenceSeconds = cadenceIntervalAfterPoll(1, cadence);
145
+ let consecutiveAbsentRequired = 0;
144
146
  while (true) {
145
147
  pollIndex += 1;
146
148
  if (pollIndex > maxPolls) {
@@ -166,6 +168,25 @@ export function monitor(prNumber, repo, options = {}) {
166
168
  if (isTerminalPrState(lastPayload)) {
167
169
  return { exitCode: EXIT_PR_TERMINAL, payload: lastPayload, pollCount: pollIndex };
168
170
  }
171
+ const absentContexts = readAbsentRequiredContexts(lastPayload);
172
+ if (absentContexts !== null) {
173
+ consecutiveAbsentRequired += 1;
174
+ if (shouldEscalateAbsentRequired(consecutiveAbsentRequired)) {
175
+ const message = formatAbsentRequiredMessage(absentContexts);
176
+ process.stderr.write(`[monitor_pr] ${message}\n`);
177
+ return {
178
+ exitCode: EXIT_ABSENT_REQUIRED,
179
+ payload: {
180
+ ...lastPayload,
181
+ monitor_absent_required: [...absentContexts],
182
+ },
183
+ pollCount: pollIndex,
184
+ };
185
+ }
186
+ }
187
+ else {
188
+ consecutiveAbsentRequired = 0;
189
+ }
169
190
  const elapsedAfterPoll = clockFn.now() - startedAt;
170
191
  const remaining = capSeconds - elapsedAfterPoll;
171
192
  if (remaining <= 0) {
@@ -187,6 +208,8 @@ export const summaryLabelForExit = (exitCode) => {
187
208
  return "PR-TERMINAL";
188
209
  case EXIT_CONFIG_ERROR:
189
210
  return "CONFIG-ERROR";
211
+ case EXIT_ABSENT_REQUIRED:
212
+ return "ABSENT-REQUIRED";
190
213
  default:
191
214
  return "UNKNOWN";
192
215
  }
@@ -9,11 +9,17 @@ export function classifyMonitorOutcome(monitorReturncode, monitorPayload) {
9
9
  if (monitorPayload.monitor_result === "CAP-REACHED") {
10
10
  return ["cap-reached", EXIT_TIMEOUT_OR_ESCALATION];
11
11
  }
12
+ if (monitorPayload.monitor_result === "ABSENT-REQUIRED") {
13
+ return ["absent-required", EXIT_TIMEOUT_OR_ESCALATION];
14
+ }
12
15
  return ["config-error", EXIT_CONFIG_ERROR];
13
16
  }
14
17
  if (monitorReturncode === 2) {
15
18
  return ["config-error", EXIT_CONFIG_ERROR];
16
19
  }
20
+ if (monitorReturncode === 4) {
21
+ return ["absent-required", EXIT_TIMEOUT_OR_ESCALATION];
22
+ }
17
23
  if (monitorReturncode === 3) {
18
24
  const readiness = typeof monitorPayload.readiness === "object" &&
19
25
  monitorPayload.readiness !== null &&
@@ -23,6 +23,10 @@ export interface EvaluateOptions {
23
23
  readonly skipParentLineage?: boolean;
24
24
  /** Skip origin timestamp freshness (#3363). Default false. */
25
25
  readonly skipOriginFreshness?: boolean;
26
+ /** Skip project-invariant coverage (#3425). Default false. */
27
+ readonly skipProjectInvariants?: boolean;
28
+ /** Skip intended-placement size check (#3424). Default false. */
29
+ readonly skipIntendedPlacement?: boolean;
26
30
  /** Injected origin fetch for tests. Default: live `gh api` REST. */
27
31
  readonly fetchOriginUpdatedAt?: FetchOriginUpdatedAt;
28
32
  }
@@ -3,6 +3,8 @@ import { basename, dirname } from "node:path";
3
3
  import { evaluateIntentCeilingFromEnv } from "../policy/intent-ceiling.js";
4
4
  import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
5
5
  import { evaluateOriginFreshness, } from "../vbrief-reconcile/origin-freshness.js";
6
+ import { evaluateIntendedPlacement, resolveProjectRootFromBrief } from "./intended-placement.js";
7
+ import { evaluateProjectInvariantsGate, resolveProjectRootForInvariants, } from "./project-invariants-gate.js";
6
8
  /** Canonical eligibility folder — only vbrief/active/ may spawn implementation. */
7
9
  export const ACTIVE_FOLDER = "active";
8
10
  /** Canonical eligibility status — only `running` signals an active handoff. */
@@ -168,8 +170,37 @@ export function evaluate(vbriefPath, options = {}) {
168
170
  message: buildReject(path, originFreshness.message),
169
171
  };
170
172
  }
173
+ // #3425: fail closed when an applicable project invariant has no disposition.
174
+ const invariants = evaluateProjectInvariantsGate(record, {
175
+ projectRoot: resolveProjectRootForInvariants(path, options.projectRoot),
176
+ skip: options.skipProjectInvariants === true,
177
+ });
178
+ if (!invariants.ok) {
179
+ return {
180
+ exitCode: 1,
181
+ parentLineage: lineage,
182
+ message: buildReject(path, invariants.message),
183
+ };
184
+ }
185
+ // #3424: declared files vs review-trigger SoT. Missing field is grandfathered
186
+ // (warning). Inspect anomalies fail closed. Size alone is not a hard cap (#1488).
187
+ let placementWarning;
188
+ if (options.skipIntendedPlacement !== true) {
189
+ const projectRoot = resolveProjectRootFromBrief(path, options.projectRoot);
190
+ const placement = evaluateIntendedPlacement(planRecord, { projectRoot });
191
+ if (!placement.ok) {
192
+ return {
193
+ exitCode: 1,
194
+ parentLineage: lineage,
195
+ message: buildReject(path, placement.message),
196
+ };
197
+ }
198
+ if (placement.warning === true) {
199
+ placementWarning = placement.message;
200
+ }
201
+ }
171
202
  // Keep the historical OK line when lineage is N/A (backward-compatible tests / agents).
172
- const message = lineage.applicable
203
+ let message = lineage.applicable
173
204
  ? `OK ${path} -- ready for implementation. parent lineage OK ` +
174
205
  `(${lineage.parent_requirement_ids.length} req IDs` +
175
206
  (lineage.negative_invariant_ids.length > 0
@@ -177,6 +208,9 @@ export function evaluate(vbriefPath, options = {}) {
177
208
  : "") +
178
209
  `).`
179
210
  : `OK ${path} -- ready for implementation.`;
211
+ if (placementWarning !== undefined) {
212
+ message = `${message} ${placementWarning}`;
213
+ }
180
214
  return {
181
215
  exitCode: 0,
182
216
  parentLineage: lineage,
@@ -1,3 +1,7 @@
1
1
  export type { EvaluateOptions, EvaluateResult } from "./evaluate.js";
2
2
  export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_LIFECYCLE_DIRS, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, PREFLIGHT_USAGE_HINT, } from "./evaluate.js";
3
+ export type { IntendedPlacement, IntendedPlacementResult, ParsedIntendedPlacement, } from "./intended-placement.js";
4
+ export { emptyIntendedPlacement, evaluateIntendedPlacement, INTENDED_PLACEMENT_GRANDFATHER_HINT, INTENDED_PLACEMENT_MISSING_HINT, INTENDED_PLACEMENT_OVER_TRIGGER_HINT, INTENDED_PLACEMENT_SCHEMA, parseIntendedPlacement, readIntendedPlacement, stampIntendedPlacement, } from "./intended-placement.js";
5
+ export type { ProjectInvariantsGateOptions, ProjectInvariantsGateResult, } from "./project-invariants-gate.js";
6
+ export { evaluateProjectInvariantsGate, PROJECT_INVARIANT_REMEDIATION, resolveProjectRootForInvariants, } from "./project-invariants-gate.js";
3
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,4 @@
1
1
  export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_LIFECYCLE_DIRS, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, PREFLIGHT_USAGE_HINT, } from "./evaluate.js";
2
+ export { emptyIntendedPlacement, evaluateIntendedPlacement, INTENDED_PLACEMENT_GRANDFATHER_HINT, INTENDED_PLACEMENT_MISSING_HINT, INTENDED_PLACEMENT_OVER_TRIGGER_HINT, INTENDED_PLACEMENT_SCHEMA, parseIntendedPlacement, readIntendedPlacement, stampIntendedPlacement, } from "./intended-placement.js";
3
+ export { evaluateProjectInvariantsGate, PROJECT_INVARIANT_REMEDIATION, resolveProjectRootForInvariants, } from "./project-invariants-gate.js";
2
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * xBRIEF intended-placement field + preflight check (#3424).
3
+ *
4
+ * Preflight keys on declared files vs FILE_SIZE_REVIEW_TRIGGER_LINES.
5
+ * Size alone is not a hard cap (#1488).
6
+ *
7
+ * ## Error policy
8
+ * Every anomaly on a declared file fails closed (exit 1 + remediation hint).
9
+ * Never throw. Never silent-skip. The only skip is first-touch `lstat` ENOENT
10
+ * (planned-new file — nothing to measure). If `lstat` succeeded and a later
11
+ * operation fails, that is an inconsistency → exit 1.
12
+ *
13
+ * ## Threat model
14
+ * Preflight is a quality gate the agent runs against its own declared plan
15
+ * in its own worktree. A concurrent local process that can swap symlinks
16
+ * mid-check can trivially edit the vBRIEF itself. Adversarial-local-attacker
17
+ * TOCTOU is a **non-goal** beyond the fd discipline below. Windows symlink
18
+ * creation requires elevation.
19
+ *
20
+ * ## fd discipline
21
+ * `lstat` rejects symlink entries outright. Resolve + containment-check once.
22
+ * `openSync(abs, O_RDONLY | O_NOFOLLOW)` (plain `O_RDONLY` where `O_NOFOLLOW`
23
+ * is undefined, i.e. Windows). `fstatSync(fd)` confirms a regular file; read
24
+ * from that same fd.
25
+ */
26
+ export declare const INTENDED_PLACEMENT_SCHEMA: "deft.scope.intended_placement.v1";
27
+ export declare const INTENDED_PLACEMENT_MISSING_HINT = "Record plan.metadata.intended_placement with files[] and module_boundary before implementation.";
28
+ export declare const INTENDED_PLACEMENT_OVER_TRIGGER_HINT = "Record plan.metadata.intended_placement.split_plan or cohesion_exemption. Size alone is not a fail-closed cap (#1488 / #3424).";
29
+ export declare const INTENDED_PLACEMENT_GRANDFATHER_HINT = "Pre-#3424 brief: intended_placement is missing (warning). Ingest now stamps the field; record files[] before new work.";
30
+ export interface IntendedPlacement {
31
+ readonly schema?: string;
32
+ readonly files: readonly string[];
33
+ readonly module_boundary?: string;
34
+ readonly split_plan?: string;
35
+ readonly cohesion_exemption?: string;
36
+ }
37
+ export interface IntendedPlacementResult {
38
+ readonly ok: boolean;
39
+ readonly message: string;
40
+ /** Grandfathered missing field — evaluate stays exit 0 and surfaces this. */
41
+ readonly warning?: boolean;
42
+ }
43
+ export type ParsedIntendedPlacement = {
44
+ kind: "missing";
45
+ } | {
46
+ kind: "malformed";
47
+ message: string;
48
+ } | {
49
+ kind: "ok";
50
+ placement: IntendedPlacement;
51
+ };
52
+ export declare function emptyIntendedPlacement(): IntendedPlacement;
53
+ /** Stamp a skeleton intended_placement onto plan.metadata when absent (#3424). */
54
+ export declare function stampIntendedPlacement(plan: Record<string, unknown>): void;
55
+ export declare function parseIntendedPlacement(plan: Record<string, unknown>): ParsedIntendedPlacement;
56
+ export declare function readIntendedPlacement(plan: Record<string, unknown>): IntendedPlacement | null;
57
+ export declare function countFileLines(text: string): number;
58
+ export declare function resolveProjectRootFromBrief(briefPath: string, explicitRoot?: string): string;
59
+ export declare function evaluateIntendedPlacement(plan: Record<string, unknown>, options: {
60
+ projectRoot: string;
61
+ }): IntendedPlacementResult;
62
+ //# sourceMappingURL=intended-placement.d.ts.map