@deftai/directive-core 0.101.0 → 0.103.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 (75) hide show
  1. package/dist/authz/classify.js +97 -8
  2. package/dist/check/cached-orchestrator.d.ts +4 -0
  3. package/dist/check/cached-orchestrator.js +70 -5
  4. package/dist/check/cli-native-gates.d.ts +43 -0
  5. package/dist/check/cli-native-gates.js +84 -0
  6. package/dist/check/index.d.ts +1 -0
  7. package/dist/check/index.js +1 -0
  8. package/dist/check/named-cause.js +10 -0
  9. package/dist/doctor/checks.d.ts +5 -6
  10. package/dist/doctor/checks.js +13 -56
  11. package/dist/eval/later-graduation.d.ts +39 -0
  12. package/dist/eval/later-graduation.js +91 -0
  13. package/dist/eval/report.d.ts +6 -0
  14. package/dist/eval/report.js +27 -8
  15. package/dist/index.d.ts +1 -0
  16. package/dist/index.js +1 -0
  17. package/dist/init-deposit/hygiene.d.ts +7 -1
  18. package/dist/init-deposit/hygiene.js +11 -3
  19. package/dist/init-deposit/scaffold.d.ts +12 -0
  20. package/dist/init-deposit/scaffold.js +81 -8
  21. package/dist/intake/issue-ingest.js +42 -0
  22. package/dist/policy/ceremony-dial-escalation.d.ts +52 -0
  23. package/dist/policy/ceremony-dial-escalation.js +92 -0
  24. package/dist/policy/ceremony-dial.d.ts +3 -1
  25. package/dist/policy/ceremony-dial.js +52 -1
  26. package/dist/policy/check-resume.d.ts +9 -38
  27. package/dist/policy/check-resume.js +18 -156
  28. package/dist/policy/coverage-debt.d.ts +18 -37
  29. package/dist/policy/coverage-debt.js +38 -147
  30. package/dist/policy/index.d.ts +1 -1
  31. package/dist/policy/index.js +1 -1
  32. package/dist/product-first-done-gate/acceptance.js +22 -0
  33. package/dist/product-first-done-gate/empty-resolution.d.ts +26 -0
  34. package/dist/product-first-done-gate/empty-resolution.js +38 -0
  35. package/dist/product-first-done-gate/evaluate.d.ts +40 -2
  36. package/dist/product-first-done-gate/evaluate.js +253 -61
  37. package/dist/product-first-done-gate/index.d.ts +2 -1
  38. package/dist/product-first-done-gate/index.js +2 -1
  39. package/dist/product-first-done-gate/types.d.ts +3 -0
  40. package/dist/product-first-done-gate/types.js +0 -7
  41. package/dist/run-summary/emit.d.ts +11 -1
  42. package/dist/run-summary/emit.js +48 -2
  43. package/dist/run-summary/index.d.ts +2 -1
  44. package/dist/run-summary/index.js +2 -1
  45. package/dist/run-summary/path.d.ts +1 -1
  46. package/dist/run-summary/path.js +1 -1
  47. package/dist/run-summary/share.d.ts +25 -0
  48. package/dist/run-summary/share.js +106 -0
  49. package/dist/run-summary/types.d.ts +60 -2
  50. package/dist/run-summary/types.js +7 -0
  51. package/dist/scope/acceptance-activate-gate.d.ts +23 -0
  52. package/dist/scope/acceptance-activate-gate.js +70 -0
  53. package/dist/scope/index.d.ts +1 -0
  54. package/dist/scope/index.js +1 -0
  55. package/dist/scope/transition.js +5 -0
  56. package/dist/session/index.d.ts +0 -1
  57. package/dist/session/index.js +0 -1
  58. package/dist/session/orientation-compression.js +19 -9
  59. package/dist/session/session-start.d.ts +6 -0
  60. package/dist/session/session-start.js +44 -22
  61. package/dist/session/toolchain-preflight.d.ts +15 -0
  62. package/dist/session/toolchain-preflight.js +65 -5
  63. package/dist/verify-ac/clauses.d.ts +51 -0
  64. package/dist/verify-ac/clauses.js +490 -0
  65. package/dist/verify-ac/evaluate.d.ts +73 -0
  66. package/dist/verify-ac/evaluate.js +167 -0
  67. package/dist/verify-ac/flag.d.ts +35 -0
  68. package/dist/verify-ac/flag.js +104 -0
  69. package/dist/verify-ac/index.d.ts +10 -0
  70. package/dist/verify-ac/index.js +10 -0
  71. package/package.json +7 -3
  72. package/dist/policy/coverage-check-resume-presets.d.ts +0 -46
  73. package/dist/policy/coverage-check-resume-presets.js +0 -228
  74. package/dist/session/coverage-check-resume-nudge.d.ts +0 -34
  75. package/dist/session/coverage-check-resume-nudge.js +0 -66
@@ -10,6 +10,9 @@
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
  import { join } from "node:path";
13
+ import { allGatesCliDispatchable, GLOBAL_CLI_REMEDY, isCliNativeGate, } from "../check/cli-native-gates.js";
14
+ import { isFrameworkRepoRoot, isFrameworkSourceContext } from "../check/context.js";
15
+ import { CONSUMER_CHECK_GATES, checkGateId, FRAMEWORK_CHECK_GATES, } from "../check/gate-lists.js";
13
16
  import { defaultWhich } from "../doctor/which.js";
14
17
  export const TOOLCHAIN_PREFLIGHT_TOOLS = ["task", "pnpm", "node", "git"];
15
18
  const REMEDY = {
@@ -73,16 +76,35 @@ function formatFindingLine(finding) {
73
76
  if (finding.present) {
74
77
  return `[deft preflight] ${finding.tool}: ok`;
75
78
  }
79
+ if (finding.impact === "none") {
80
+ const cause = finding.cause ?? "absent";
81
+ return `[deft preflight] ${finding.tool}: absent (impact: none) — ${cause}`;
82
+ }
76
83
  const cause = finding.cause ?? "missing";
77
84
  const remedy = finding.remedy ?? "install the tool";
78
85
  return `[deft preflight] ${finding.tool}: MISSING — cause: ${cause}; remedy: ${remedy}`;
79
86
  }
87
+ function inferConsumerDeposit(options) {
88
+ if (options.consumerDeposit !== undefined)
89
+ return options.consumerDeposit;
90
+ const project = options.projectRoot;
91
+ if (project === undefined)
92
+ return false;
93
+ // Same-path roots are framework-source only when the tree is the source repo.
94
+ if (isFrameworkRepoRoot(project))
95
+ return false;
96
+ const framework = options.frameworkRoot ?? project;
97
+ return !isFrameworkSourceContext(framework, project);
98
+ }
80
99
  /**
81
100
  * Run done-gate toolchain preflight. Pure probe — never installs or builds.
82
101
  */
83
102
  export function runToolchainPreflight(options = {}) {
84
103
  const which = options.which ?? defaultWhich;
85
104
  const exists = options.exists ?? existsSync;
105
+ const consumerDeposit = inferConsumerDeposit(options);
106
+ const composed = options.composedGates ?? (consumerDeposit ? CONSUMER_CHECK_GATES : FRAMEWORK_CHECK_GATES);
107
+ const allCli = allGatesCliDispatchable(composed);
86
108
  const findings = [];
87
109
  for (const tool of TOOLCHAIN_PREFLIGHT_TOOLS) {
88
110
  findings.push(probeTool(tool, which));
@@ -90,13 +112,48 @@ export function runToolchainPreflight(options = {}) {
90
112
  if (options.probeCliDist !== false) {
91
113
  findings.push(probeCliDist(options.frameworkRoot ?? options.projectRoot, which, exists));
92
114
  }
93
- const missingCritical = findings.filter((f) => !f.present && (f.tool === "task" || f.tool === "pnpm" || f.tool === "node"));
115
+ const cliPresent = findings.some((f) => f.tool === "cli_dist" && f.present);
94
116
  const taskMissing = findings.some((f) => f.tool === "task" && !f.present);
95
117
  const pnpmMissing = findings.some((f) => f.tool === "pnpm" && !f.present);
96
118
  const nodeMissing = findings.some((f) => f.tool === "node" && !f.present);
119
+ // #3335: in a deposit whose composed gates are all CLI-dispatchable, a
120
+ // missing task binary is impact none — no install-go-task remedy.
121
+ if (taskMissing && allCli) {
122
+ const idx = findings.findIndex((f) => f.tool === "task");
123
+ const prior = findings[idx];
124
+ if (idx >= 0 && prior !== undefined) {
125
+ findings[idx] = {
126
+ ...prior,
127
+ impact: "none",
128
+ remedy: cliPresent || nodeMissing ? null : GLOBAL_CLI_REMEDY,
129
+ cause: cliPresent
130
+ ? "go-task absent; CLI-native gates dispatch via global deft/directive (#3335)"
131
+ : "go-task absent and no global deft/directive CLI",
132
+ };
133
+ }
134
+ }
135
+ const missingCritical = findings.filter((f) => {
136
+ if (f.present || f.impact === "none")
137
+ return false;
138
+ return f.tool === "task" || f.tool === "pnpm" || f.tool === "node";
139
+ });
97
140
  const skip = new Set();
98
- if (taskMissing || nodeMissing) {
99
- // Dynamic: orchestrator expands SKIP_ALL_GATES to the live gate list.
141
+ if (nodeMissing) {
142
+ skip.add(SKIP_ALL_GATES);
143
+ }
144
+ else if (taskMissing && !allCli) {
145
+ if (!cliPresent) {
146
+ skip.add(SKIP_ALL_GATES);
147
+ }
148
+ else {
149
+ for (const spec of composed) {
150
+ const id = checkGateId(spec);
151
+ if (!isCliNativeGate(id))
152
+ skip.add(id);
153
+ }
154
+ }
155
+ }
156
+ else if (taskMissing && allCli && !cliPresent) {
100
157
  skip.add(SKIP_ALL_GATES);
101
158
  }
102
159
  else if (pnpmMissing) {
@@ -104,7 +161,7 @@ export function runToolchainPreflight(options = {}) {
104
161
  skip.add(id);
105
162
  }
106
163
  }
107
- const degraded = missingCritical.length > 0;
164
+ const degraded = missingCritical.length > 0 || (taskMissing && allCli && !cliPresent);
108
165
  const ok = !degraded;
109
166
  const status = ok ? "ok" : "degraded";
110
167
  const lines = [];
@@ -115,7 +172,9 @@ export function runToolchainPreflight(options = {}) {
115
172
  }
116
173
  }
117
174
  if (ok) {
118
- lines.push("[deft preflight] done-gate toolchain ready (task, pnpm, node)");
175
+ lines.push(taskMissing && allCli
176
+ ? "[deft preflight] done-gate toolchain ready (CLI dispatch; go-task not required) (#3335)"
177
+ : "[deft preflight] done-gate toolchain ready (task, pnpm, node)");
119
178
  }
120
179
  else {
121
180
  lines.push("[deft preflight] degraded mode: directive check will skip toolchain-dependent gates " +
@@ -144,6 +203,7 @@ export function toolchainPreflightToDict(result) {
144
203
  present: f.present,
145
204
  cause: f.cause,
146
205
  remedy: f.remedy,
206
+ ...(f.impact !== undefined ? { impact: f.impact } : {}),
147
207
  })),
148
208
  skip_gate_ids: [...result.skipGateIds],
149
209
  };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Rung-2 derived AC: decompose the task statement into testable clauses
3
+ * at intake; walk every clause against the shipped artifact at done (#3323).
4
+ */
5
+ export type ClauseOutcome = "verified" | "unverifiable" | "failed";
6
+ export interface AcceptanceClauseReading {
7
+ readonly text: string;
8
+ readonly artifact_path: string | null;
9
+ }
10
+ export interface AcceptanceClause {
11
+ readonly id: number;
12
+ readonly text: string;
13
+ readonly artifact_path: string | null;
14
+ readonly ambiguous: boolean;
15
+ readonly readings?: readonly AcceptanceClauseReading[];
16
+ readonly chosen_reading?: number;
17
+ }
18
+ export interface ClauseWalkResult {
19
+ readonly id: number;
20
+ readonly text: string;
21
+ readonly artifact_path: string | null;
22
+ readonly outcome: ClauseOutcome;
23
+ readonly detail: string;
24
+ }
25
+ export interface ClauseWalkReport {
26
+ readonly clauses: readonly ClauseWalkResult[];
27
+ readonly failed: readonly ClauseWalkResult[];
28
+ readonly unverifiable: readonly ClauseWalkResult[];
29
+ readonly verified: readonly ClauseWalkResult[];
30
+ readonly ok: boolean;
31
+ readonly message: string;
32
+ }
33
+ /** Numbered independently testable clauses from the task statement (#3323). */
34
+ export declare function deriveAcceptanceClauses(taskStatement: string): AcceptanceClause[];
35
+ export declare function readAcceptanceClauses(acceptance: unknown): AcceptanceClause[];
36
+ export declare function serializeAcceptanceClauses(clauses: readonly AcceptanceClause[]): Record<string, unknown>[];
37
+ /**
38
+ * When no commands are stated, stamp derived clauses onto plan.acceptance
39
+ * and promote source_rung to derived. Leaves stated-command acceptance alone.
40
+ */
41
+ export declare function stampDerivedClausesOnAcceptance(plan: Record<string, unknown>, taskStatement: string): {
42
+ readonly plan: Record<string, unknown>;
43
+ readonly clauses: readonly AcceptanceClause[];
44
+ };
45
+ export declare function isScratchArtifactPath(artifactPath: string): boolean;
46
+ export declare function walkAcceptanceClauses(clauses: readonly AcceptanceClause[], projectRoot: string): ClauseWalkReport;
47
+ /** Done reports lead with failed/unverifiable; unverifiable is never dropped. */
48
+ export declare function formatClauseWalkMessage(report: Omit<ClauseWalkReport, "message"> & {
49
+ readonly message?: string;
50
+ }, prior?: string): string;
51
+ //# sourceMappingURL=clauses.d.ts.map
@@ -0,0 +1,490 @@
1
+ /**
2
+ * Rung-2 derived AC: decompose the task statement into testable clauses
3
+ * at intake; walk every clause against the shipped artifact at done (#3323).
4
+ */
5
+ import { existsSync, readFileSync, statSync } from "node:fs";
6
+ import { isAbsolute, relative, resolve } from "node:path";
7
+ import { findAcHeading, parseListItems, sliceAcSection } from "../intake/markdown-scanners.js";
8
+ const SECTION_HEADING = /^(#{1,6})\s+(acceptance(?:\s+criteria|\s+sketch)?|fix)\s*$/i;
9
+ const LABELED_AC_PREFIXES = ["test:", "acceptance:", "acceptancecriteria:"];
10
+ const META_CLAUSE = /^(relates?\s+#|refs?\s+#)/i;
11
+ const FILE_EXT = /\.(?:ts|tsx|js|mjs|cjs|json|md|go|py|yml|yaml|txt)$/i;
12
+ const SCRATCH_SEGMENTS = new Set([
13
+ "tmp",
14
+ "temp",
15
+ ".deft-scratch",
16
+ "node_modules",
17
+ "scratch",
18
+ "buffer",
19
+ ]);
20
+ const EXISTENCE_CLAIM = /\b(?:exists?|stored on|written to|emitted? (?:at|to)|at its stated path|artifact path)\b/i;
21
+ const NEGATED_EXISTENCE = /\b(?:does not exist|doesn't exist|must not exist|never exists?|not exist)\b/i;
22
+ function asRecord(value) {
23
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
24
+ return value;
25
+ }
26
+ return null;
27
+ }
28
+ function isNonEmptyString(value) {
29
+ return typeof value === "string" && value.trim().length > 0;
30
+ }
31
+ function normalizeClauseText(text) {
32
+ return text.replace(/\s+/g, " ").trim();
33
+ }
34
+ function looksLikeFilePath(token) {
35
+ const t = token.trim().replace(/^['"`]|['"`]$/g, "");
36
+ if (t.length < 3 || t.includes("://") || t.includes(" ")) {
37
+ return false;
38
+ }
39
+ if (t.startsWith("plan.") || (t.includes(":") && !t.includes("/") && !t.includes("\\"))) {
40
+ return false;
41
+ }
42
+ const unified = t.replace(/\\/g, "/");
43
+ if (FILE_EXT.test(unified)) {
44
+ return true;
45
+ }
46
+ return unified.includes("/") && !unified.startsWith("#");
47
+ }
48
+ function extractPathTokens(text) {
49
+ const found = [];
50
+ const seen = new Set();
51
+ const push = (raw) => {
52
+ const token = raw.trim().replace(/^['"`]|['"`]$/g, "");
53
+ if (!looksLikeFilePath(token) || seen.has(token)) {
54
+ return;
55
+ }
56
+ seen.add(token);
57
+ found.push(token);
58
+ };
59
+ const backtick = /`([^`\n]{2,200})`/g;
60
+ let match = backtick.exec(text);
61
+ while (match !== null) {
62
+ push(match[1] ?? "");
63
+ match = backtick.exec(text);
64
+ }
65
+ const bare = /(?<![A-Za-z0-9_])((?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.[A-Za-z][A-Za-z0-9]+)(?![A-Za-z0-9_])/g;
66
+ match = bare.exec(text);
67
+ while (match !== null) {
68
+ push(match[1] ?? "");
69
+ match = bare.exec(text);
70
+ }
71
+ return found;
72
+ }
73
+ function isMetaClause(text) {
74
+ return META_CLAUSE.test(text.trim());
75
+ }
76
+ function collectSectionItems(text, headingRe) {
77
+ const items = [];
78
+ let offset = 0;
79
+ for (const line of text.split("\n")) {
80
+ const match = headingRe.exec(line.trim());
81
+ if (match !== null) {
82
+ const hashes = (match[1] ?? "#").length;
83
+ const section = sliceAcSection(text, {
84
+ level: hashes,
85
+ sectionStart: offset + line.length + 1,
86
+ });
87
+ for (const item of parseListItems(section)) {
88
+ const title = normalizeClauseText(item.title.replace(/\*\*/g, ""));
89
+ if (title.length > 0 && !isMetaClause(title)) {
90
+ items.push(title);
91
+ }
92
+ }
93
+ }
94
+ offset += line.length + 1;
95
+ }
96
+ return items;
97
+ }
98
+ function matchLabeledAcLine(line) {
99
+ const trimmed = line.trim();
100
+ if (trimmed.length === 0) {
101
+ return null;
102
+ }
103
+ const lower = trimmed.toLowerCase();
104
+ for (const prefix of LABELED_AC_PREFIXES) {
105
+ if (!lower.startsWith(prefix)) {
106
+ continue;
107
+ }
108
+ const body = trimmed.slice(prefix.length).trim();
109
+ return body.length > 0 ? body : null;
110
+ }
111
+ return null;
112
+ }
113
+ function collectLabeledLines(text) {
114
+ const items = [];
115
+ for (const line of text.split("\n")) {
116
+ const body = matchLabeledAcLine(line);
117
+ if (body !== null && !isMetaClause(body)) {
118
+ items.push(normalizeClauseText(body));
119
+ }
120
+ }
121
+ return items;
122
+ }
123
+ function collectPathBearingLines(text) {
124
+ const items = [];
125
+ for (const item of parseListItems(text)) {
126
+ const title = normalizeClauseText(item.title.replace(/\*\*/g, ""));
127
+ if (title.length === 0 || isMetaClause(title)) {
128
+ continue;
129
+ }
130
+ if (extractPathTokens(title).length > 0) {
131
+ items.push(title);
132
+ }
133
+ }
134
+ return items;
135
+ }
136
+ /** Numbered independently testable clauses from the task statement (#3323). */
137
+ export function deriveAcceptanceClauses(taskStatement) {
138
+ const text = taskStatement.trim();
139
+ if (text.length === 0) {
140
+ return [];
141
+ }
142
+ const acHeading = findAcHeading(text);
143
+ const raw = [];
144
+ if (acHeading !== null) {
145
+ raw.push(...parseListItems(sliceAcSection(text, acHeading))
146
+ .map((item) => normalizeClauseText(item.title.replace(/\*\*/g, "")))
147
+ .filter((title) => title.length > 0 && !isMetaClause(title)));
148
+ }
149
+ raw.push(...collectSectionItems(text, SECTION_HEADING));
150
+ raw.push(...collectLabeledLines(text));
151
+ if (raw.length === 0) {
152
+ raw.push(...collectPathBearingLines(text));
153
+ }
154
+ const seen = new Set();
155
+ const clauses = [];
156
+ for (const line of raw) {
157
+ const key = line.toLowerCase();
158
+ if (seen.has(key)) {
159
+ continue;
160
+ }
161
+ seen.add(key);
162
+ clauses.push(buildClause(clauses.length + 1, line));
163
+ }
164
+ return clauses;
165
+ }
166
+ function buildClause(id, text) {
167
+ const paths = extractPathTokens(text);
168
+ if (paths.length <= 1) {
169
+ return {
170
+ id,
171
+ text,
172
+ artifact_path: paths[0] ?? null,
173
+ ambiguous: false,
174
+ };
175
+ }
176
+ const readings = paths.map((artifact_path) => ({
177
+ text: `${text} [reading: ${artifact_path}]`,
178
+ artifact_path,
179
+ }));
180
+ return {
181
+ id,
182
+ text,
183
+ artifact_path: paths[0] ?? null,
184
+ ambiguous: true,
185
+ readings,
186
+ chosen_reading: 0,
187
+ };
188
+ }
189
+ export function readAcceptanceClauses(acceptance) {
190
+ const rec = asRecord(acceptance);
191
+ if (rec === null || !Array.isArray(rec.clauses)) {
192
+ return [];
193
+ }
194
+ const out = [];
195
+ for (const [index, entry] of rec.clauses.entries()) {
196
+ const row = asRecord(entry);
197
+ if (row === null) {
198
+ continue;
199
+ }
200
+ const text = isNonEmptyString(row.text) ? normalizeClauseText(row.text) : "";
201
+ if (text.length === 0) {
202
+ continue;
203
+ }
204
+ const artifact = isNonEmptyString(row.artifact_path)
205
+ ? row.artifact_path.trim()
206
+ : isNonEmptyString(row.artifactPath)
207
+ ? row.artifactPath.trim()
208
+ : null;
209
+ const readings = Array.isArray(row.readings)
210
+ ? row.readings
211
+ .map((reading) => {
212
+ const rr = asRecord(reading);
213
+ if (rr === null || !isNonEmptyString(rr.text)) {
214
+ return null;
215
+ }
216
+ const path = isNonEmptyString(rr.artifact_path)
217
+ ? rr.artifact_path.trim()
218
+ : isNonEmptyString(rr.artifactPath)
219
+ ? rr.artifactPath.trim()
220
+ : null;
221
+ return { text: rr.text.trim(), artifact_path: path };
222
+ })
223
+ .filter((r) => r !== null)
224
+ : [];
225
+ const ambiguous = row.ambiguous === true || readings.length > 1;
226
+ const chosen = typeof row.chosen_reading === "number"
227
+ ? row.chosen_reading
228
+ : typeof row.chosenReading === "number"
229
+ ? row.chosenReading
230
+ : 0;
231
+ const chosenPath = ambiguous && readings[chosen] !== undefined ? readings[chosen].artifact_path : artifact;
232
+ out.push({
233
+ id: typeof row.id === "number" && row.id > 0 ? row.id : index + 1,
234
+ text,
235
+ artifact_path: chosenPath,
236
+ ambiguous,
237
+ ...(readings.length > 0 ? { readings, chosen_reading: chosen } : {}),
238
+ });
239
+ }
240
+ return out;
241
+ }
242
+ export function serializeAcceptanceClauses(clauses) {
243
+ return clauses.map((clause) => {
244
+ const row = {
245
+ id: clause.id,
246
+ text: clause.text,
247
+ artifact_path: clause.artifact_path,
248
+ ambiguous: clause.ambiguous,
249
+ };
250
+ if (clause.ambiguous && clause.readings !== undefined) {
251
+ row.readings = clause.readings.map((reading) => ({
252
+ text: reading.text,
253
+ artifact_path: reading.artifact_path,
254
+ }));
255
+ row.chosen_reading = clause.chosen_reading ?? 0;
256
+ }
257
+ return row;
258
+ });
259
+ }
260
+ /**
261
+ * When no commands are stated, stamp derived clauses onto plan.acceptance
262
+ * and promote source_rung to derived. Leaves stated-command acceptance alone.
263
+ */
264
+ export function stampDerivedClausesOnAcceptance(plan, taskStatement) {
265
+ const rec = asRecord(plan.acceptance);
266
+ if (rec === null) {
267
+ return { plan, clauses: [] };
268
+ }
269
+ if (rec.none_stated !== true) {
270
+ return { plan, clauses: [] };
271
+ }
272
+ const commands = rec.commands;
273
+ if (Array.isArray(commands) && commands.length > 0) {
274
+ return { plan, clauses: [] };
275
+ }
276
+ const clauses = deriveAcceptanceClauses(taskStatement);
277
+ if (clauses.length === 0) {
278
+ return { plan, clauses: [] };
279
+ }
280
+ return {
281
+ plan: {
282
+ ...plan,
283
+ acceptance: {
284
+ ...rec,
285
+ none_stated: true,
286
+ source_rung: "derived",
287
+ derived_reason: `derived ${clauses.length} independently testable clauses from the task statement before product edit (#3323)`,
288
+ clauses: serializeAcceptanceClauses(clauses),
289
+ },
290
+ },
291
+ clauses,
292
+ };
293
+ }
294
+ export function isScratchArtifactPath(artifactPath) {
295
+ const unified = artifactPath.replace(/\\/g, "/").toLowerCase();
296
+ return unified.split("/").some((seg) => SCRATCH_SEGMENTS.has(seg));
297
+ }
298
+ function isContained(root, child) {
299
+ const rel = relative(resolve(root), resolve(child));
300
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
301
+ }
302
+ function extractExpectedTokens(clause) {
303
+ const tokens = [];
304
+ const seen = new Set();
305
+ const quoted = /["']([^"'\n]{3,80})["']/g;
306
+ let match = quoted.exec(clause.text);
307
+ while (match !== null) {
308
+ const token = (match[1] ?? "").trim();
309
+ const skip = token.length === 0 ||
310
+ token === clause.artifact_path ||
311
+ looksLikeFilePath(token) ||
312
+ seen.has(token);
313
+ if (!skip) {
314
+ seen.add(token);
315
+ tokens.push(token);
316
+ }
317
+ match = quoted.exec(clause.text);
318
+ }
319
+ return tokens;
320
+ }
321
+ function walkOne(clause, projectRoot) {
322
+ const artifactPath = clause.artifact_path;
323
+ if (artifactPath === null || artifactPath.trim().length === 0) {
324
+ return {
325
+ id: clause.id,
326
+ text: clause.text,
327
+ artifact_path: artifactPath,
328
+ outcome: "unverifiable",
329
+ detail: "no artifact path bound",
330
+ };
331
+ }
332
+ if (isScratchArtifactPath(artifactPath)) {
333
+ return {
334
+ id: clause.id,
335
+ text: clause.text,
336
+ artifact_path: artifactPath,
337
+ outcome: "failed",
338
+ detail: "artifact path is a buffer/scratch copy, not the shipped path",
339
+ };
340
+ }
341
+ const abs = resolve(projectRoot, artifactPath);
342
+ if (!isContained(projectRoot, abs)) {
343
+ return {
344
+ id: clause.id,
345
+ text: clause.text,
346
+ artifact_path: artifactPath,
347
+ outcome: "failed",
348
+ detail: "artifact path escaped the project root",
349
+ };
350
+ }
351
+ if (!existsSync(abs)) {
352
+ if (NEGATED_EXISTENCE.test(clause.text)) {
353
+ return {
354
+ id: clause.id,
355
+ text: clause.text,
356
+ artifact_path: artifactPath,
357
+ outcome: "verified",
358
+ detail: `artifact correctly absent at ${artifactPath}`,
359
+ };
360
+ }
361
+ return {
362
+ id: clause.id,
363
+ text: clause.text,
364
+ artifact_path: artifactPath,
365
+ outcome: "failed",
366
+ detail: `artifact missing at stated path ${artifactPath}`,
367
+ };
368
+ }
369
+ try {
370
+ if (!statSync(abs).isFile()) {
371
+ return {
372
+ id: clause.id,
373
+ text: clause.text,
374
+ artifact_path: artifactPath,
375
+ outcome: "failed",
376
+ detail: `stated path is not a shipped file: ${artifactPath}`,
377
+ };
378
+ }
379
+ }
380
+ catch {
381
+ return {
382
+ id: clause.id,
383
+ text: clause.text,
384
+ artifact_path: artifactPath,
385
+ outcome: "failed",
386
+ detail: `artifact unreadable at stated path ${artifactPath}`,
387
+ };
388
+ }
389
+ if (NEGATED_EXISTENCE.test(clause.text)) {
390
+ return {
391
+ id: clause.id,
392
+ text: clause.text,
393
+ artifact_path: artifactPath,
394
+ outcome: "failed",
395
+ detail: `artifact exists at ${artifactPath} but the clause requires absence`,
396
+ };
397
+ }
398
+ const expected = extractExpectedTokens(clause);
399
+ if (expected.length > 0) {
400
+ let body = "";
401
+ try {
402
+ body = readFileSync(abs, "utf8");
403
+ }
404
+ catch {
405
+ return {
406
+ id: clause.id,
407
+ text: clause.text,
408
+ artifact_path: artifactPath,
409
+ outcome: "failed",
410
+ detail: `artifact unreadable at stated path ${artifactPath}`,
411
+ };
412
+ }
413
+ const missing = expected.filter((token) => !body.includes(token));
414
+ if (missing.length > 0) {
415
+ return {
416
+ id: clause.id,
417
+ text: clause.text,
418
+ artifact_path: artifactPath,
419
+ outcome: "failed",
420
+ detail: `expected token(s) missing from ${artifactPath}: ${missing.join(", ")}`,
421
+ };
422
+ }
423
+ return {
424
+ id: clause.id,
425
+ text: clause.text,
426
+ artifact_path: artifactPath,
427
+ outcome: "verified",
428
+ detail: `tokens present in shipped artifact ${artifactPath}`,
429
+ };
430
+ }
431
+ if (EXISTENCE_CLAIM.test(clause.text)) {
432
+ return {
433
+ id: clause.id,
434
+ text: clause.text,
435
+ artifact_path: artifactPath,
436
+ outcome: "verified",
437
+ detail: `shipped artifact exists at ${artifactPath}`,
438
+ };
439
+ }
440
+ return {
441
+ id: clause.id,
442
+ text: clause.text,
443
+ artifact_path: artifactPath,
444
+ outcome: "unverifiable",
445
+ detail: `cannot evaluate behavioral claim against shipped artifact ${artifactPath}`,
446
+ };
447
+ }
448
+ export function walkAcceptanceClauses(clauses, projectRoot) {
449
+ const walked = clauses.map((clause) => walkOne(clause, projectRoot));
450
+ const failed = walked.filter((row) => row.outcome === "failed");
451
+ const unverifiable = walked.filter((row) => row.outcome === "unverifiable");
452
+ const verified = walked.filter((row) => row.outcome === "verified");
453
+ const ok = failed.length === 0 && (verified.length > 0 || walked.length === 0);
454
+ return {
455
+ clauses: walked,
456
+ failed,
457
+ unverifiable,
458
+ verified,
459
+ ok,
460
+ message: formatClauseWalkMessage({
461
+ clauses: walked,
462
+ failed,
463
+ unverifiable,
464
+ verified,
465
+ ok,
466
+ message: "",
467
+ }),
468
+ };
469
+ }
470
+ function formatOne(row) {
471
+ const path = row.artifact_path ?? "(no path)";
472
+ return ` [${row.outcome}] clause ${row.id} @ ${path}: ${row.text} — ${row.detail}`;
473
+ }
474
+ /** Done reports lead with failed/unverifiable; unverifiable is never dropped. */
475
+ export function formatClauseWalkMessage(report, prior) {
476
+ const lead = [...report.failed, ...report.unverifiable];
477
+ const rest = report.verified;
478
+ const lines = [
479
+ `verify:ac clause walk (#3323): ${report.verified.length} verified, ` +
480
+ `${report.unverifiable.length} unverifiable, ${report.failed.length} failed`,
481
+ ...lead.map(formatOne),
482
+ ...rest.map(formatOne),
483
+ ];
484
+ const body = lines.join("\n");
485
+ if (prior !== undefined && prior.trim().length > 0) {
486
+ return `${body}\n${prior}`;
487
+ }
488
+ return body;
489
+ }
490
+ //# sourceMappingURL=clauses.js.map