@markstanden/opencode-test-container 0.0.0-stage → 0.1.1

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -2
  3. package/container/Containerfile +24 -0
  4. package/container/README.md +29 -0
  5. package/dist/adapters/opencode/events.d.ts +90 -0
  6. package/dist/adapters/opencode/events.js +213 -0
  7. package/dist/adapters/opencode/runtime.d.ts +87 -0
  8. package/dist/adapters/opencode/runtime.js +136 -0
  9. package/dist/adapters/podman/container.d.ts +62 -0
  10. package/dist/adapters/podman/container.js +119 -0
  11. package/dist/adapters/podman/image.d.ts +50 -0
  12. package/dist/adapters/podman/image.js +124 -0
  13. package/dist/adapters/process.d.ts +38 -0
  14. package/dist/adapters/process.js +71 -0
  15. package/dist/campaign/assess.d.ts +45 -0
  16. package/dist/campaign/assess.js +208 -0
  17. package/dist/campaign/close.d.ts +17 -0
  18. package/dist/campaign/close.js +112 -0
  19. package/dist/campaign/evidence-guard.d.ts +24 -0
  20. package/dist/campaign/evidence-guard.js +49 -0
  21. package/dist/campaign/freeze.d.ts +94 -0
  22. package/dist/campaign/freeze.js +297 -0
  23. package/dist/campaign/inventory.d.ts +62 -0
  24. package/dist/campaign/inventory.js +192 -0
  25. package/dist/campaign/judge.d.ts +209 -0
  26. package/dist/campaign/judge.js +1285 -0
  27. package/dist/campaign/ledger.d.ts +64 -0
  28. package/dist/campaign/ledger.js +149 -0
  29. package/dist/campaign/prepare.d.ts +129 -0
  30. package/dist/campaign/prepare.js +483 -0
  31. package/dist/campaign/primary.d.ts +96 -0
  32. package/dist/campaign/primary.js +189 -0
  33. package/dist/campaign/run.d.ts +151 -0
  34. package/dist/campaign/run.js +562 -0
  35. package/dist/campaign/status.d.ts +76 -0
  36. package/dist/campaign/status.js +237 -0
  37. package/dist/cli/otc.d.ts +51 -0
  38. package/dist/cli/otc.js +435 -0
  39. package/dist/contracts/canonical.d.ts +21 -0
  40. package/dist/contracts/canonical.js +44 -0
  41. package/dist/contracts/types.d.ts +261 -0
  42. package/dist/contracts/types.js +9 -0
  43. package/dist/contracts/validate.d.ts +72 -0
  44. package/dist/contracts/validate.js +1021 -0
  45. package/dist/contracts/version.d.ts +44 -0
  46. package/dist/contracts/version.js +147 -0
  47. package/dist/evaluators/docs/analysis/accounting.d.ts +140 -0
  48. package/dist/evaluators/docs/analysis/accounting.js +237 -0
  49. package/dist/evaluators/docs/analysis/blinding.d.ts +40 -0
  50. package/dist/evaluators/docs/analysis/blinding.js +80 -0
  51. package/dist/evaluators/docs/analysis/bounds.d.ts +40 -0
  52. package/dist/evaluators/docs/analysis/bounds.js +31 -0
  53. package/dist/evaluators/docs/analysis/budget.d.ts +137 -0
  54. package/dist/evaluators/docs/analysis/budget.js +305 -0
  55. package/dist/evaluators/docs/analysis/closure.d.ts +175 -0
  56. package/dist/evaluators/docs/analysis/closure.js +251 -0
  57. package/dist/evaluators/docs/analysis/decision.d.ts +59 -0
  58. package/dist/evaluators/docs/analysis/decision.js +161 -0
  59. package/dist/evaluators/docs/analysis/freeze.d.ts +201 -0
  60. package/dist/evaluators/docs/analysis/freeze.js +395 -0
  61. package/dist/evaluators/docs/analysis/guardrails.d.ts +51 -0
  62. package/dist/evaluators/docs/analysis/guardrails.js +194 -0
  63. package/dist/evaluators/docs/analysis/journal.d.ts +150 -0
  64. package/dist/evaluators/docs/analysis/journal.js +475 -0
  65. package/dist/evaluators/docs/analysis/ledger.d.ts +78 -0
  66. package/dist/evaluators/docs/analysis/ledger.js +145 -0
  67. package/dist/evaluators/docs/analysis/measures.d.ts +302 -0
  68. package/dist/evaluators/docs/analysis/measures.js +1069 -0
  69. package/dist/evaluators/docs/analysis/model.d.ts +236 -0
  70. package/dist/evaluators/docs/analysis/model.js +740 -0
  71. package/dist/evaluators/docs/analysis/pairing.d.ts +68 -0
  72. package/dist/evaluators/docs/analysis/pairing.js +95 -0
  73. package/dist/evaluators/docs/analysis/records.d.ts +229 -0
  74. package/dist/evaluators/docs/analysis/records.js +935 -0
  75. package/dist/evaluators/docs/analysis/recovery.d.ts +225 -0
  76. package/dist/evaluators/docs/analysis/recovery.js +507 -0
  77. package/dist/evaluators/docs/analysis/render.d.ts +235 -0
  78. package/dist/evaluators/docs/analysis/render.js +566 -0
  79. package/dist/evaluators/docs/analysis/replay.d.ts +15 -0
  80. package/dist/evaluators/docs/analysis/replay.js +1272 -0
  81. package/dist/evaluators/docs/analysis/screening/inventory.d.ts +136 -0
  82. package/dist/evaluators/docs/analysis/screening/inventory.js +307 -0
  83. package/dist/evaluators/docs/analysis/screening/noteVersion.d.ts +15 -0
  84. package/dist/evaluators/docs/analysis/screening/noteVersion.js +46 -0
  85. package/dist/evaluators/docs/analysis/screening/reconcile.d.ts +38 -0
  86. package/dist/evaluators/docs/analysis/screening/reconcile.js +40 -0
  87. package/dist/evaluators/docs/analysis/screening/rules.d.ts +163 -0
  88. package/dist/evaluators/docs/analysis/screening/rules.js +330 -0
  89. package/dist/evaluators/docs/analysis/screening/scan.d.ts +72 -0
  90. package/dist/evaluators/docs/analysis/screening/scan.js +362 -0
  91. package/dist/evaluators/docs/analysis/selector.d.ts +68 -0
  92. package/dist/evaluators/docs/analysis/selector.js +123 -0
  93. package/dist/evaluators/docs/assess.d.ts +129 -0
  94. package/dist/evaluators/docs/assess.js +433 -0
  95. package/dist/evaluators/docs/campaign.d.ts +133 -0
  96. package/dist/evaluators/docs/campaign.js +222 -0
  97. package/dist/evaluators/docs/index.d.ts +25 -0
  98. package/dist/evaluators/docs/index.js +25 -0
  99. package/dist/evaluators/docs/journal.d.ts +150 -0
  100. package/dist/evaluators/docs/journal.js +475 -0
  101. package/dist/evaluators/docs/scoring.d.ts +367 -0
  102. package/dist/evaluators/docs/scoring.js +662 -0
  103. package/dist/evaluators/skills/assess.d.ts +94 -0
  104. package/dist/evaluators/skills/assess.js +207 -0
  105. package/dist/index.d.ts +46 -0
  106. package/dist/index.js +29 -0
  107. package/dist/legacy/prod014.d.ts +86 -0
  108. package/dist/legacy/prod014.js +209 -0
  109. package/dist/reporting/report.d.ts +79 -0
  110. package/dist/reporting/report.js +242 -0
  111. package/docs/architecture.md +106 -0
  112. package/docs/capability-matrix.md +84 -0
  113. package/docs/offline-rehearsal.md +75 -0
  114. package/docs/parity-record.md +81 -0
  115. package/docs/pr-description.md +103 -0
  116. package/docs/release-0.1.0.md +52 -0
  117. package/docs/releasing.md +82 -0
  118. package/docs/schema.md +97 -0
  119. package/examples/skills/README.md +13 -0
  120. package/examples/skills/campaign.json +73 -0
  121. package/examples/skills/skills/otel-helper/SKILL.md +10 -0
  122. package/examples/skills/suite.json +29 -0
  123. package/package.json +45 -5
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Configured preservation inventories and local assertion scanning.
3
+ *
4
+ * A missing keyword never proves a lost fact and a matching keyword never
5
+ * proves truth: this module reports recognised mentions, local negation and
6
+ * conflicts between assertions, with exact spans, and leaves every semantic
7
+ * verdict to the judge. Inventories are data (the manifest scenario's
8
+ * configuration); no fact name is hard-coded here.
9
+ *
10
+ * Summary and Facts are scanned as separate surfaces, and each assertion is
11
+ * evaluated independently so a rejecting clause in one sentence cannot
12
+ * condemn an affirmative claim in another. Failed-approach bullets are
13
+ * context, not affirmative claims.
14
+ */
15
+ import type { PreservationInventorySpec } from "../model.ts";
16
+ /** One independently evaluated assertion with its document offsets. */
17
+ export interface Assertion {
18
+ /** The assertion text. */
19
+ text: string;
20
+ /** The full bullet or Summary line the assertion came from. */
21
+ context: string;
22
+ /** Offset of the assertion start within the document. */
23
+ start: number;
24
+ /** Offset just past the assertion end within the document. */
25
+ end: number;
26
+ }
27
+ /** The claim surface of one document. */
28
+ export interface ClaimSurface {
29
+ /** The Summary value, or an empty string. */
30
+ summary: string;
31
+ /** Assertions of the Summary line with document offsets. */
32
+ summaryAssertions: Assertion[];
33
+ /** Fact bullets with their document offsets. */
34
+ facts: Array<{
35
+ text: string;
36
+ offset: number;
37
+ }>;
38
+ /** Failed-approach bullets, kept as review context only. */
39
+ failedApproaches: Array<{
40
+ text: string;
41
+ offset: number;
42
+ }>;
43
+ }
44
+ /** One span of document text. */
45
+ export interface Span {
46
+ /** The matched text. */
47
+ text: string;
48
+ /** Offset of the match start within the document. */
49
+ start: number;
50
+ /** Offset just past the match end within the document. */
51
+ end: number;
52
+ }
53
+ /** One inventory trigger, with its context and exact spans. */
54
+ export interface InventoryTrigger {
55
+ /** What kind of trigger this is. */
56
+ kind: "negated-fact" | "obsolete-current" | "conflict";
57
+ /** The inventory fact id. */
58
+ factId: string;
59
+ /** The full bullet or Summary text the assertion came from. */
60
+ context: string;
61
+ /** The evaluated assertion text. */
62
+ assertion: string;
63
+ /** The assertion's span within the document. */
64
+ span: {
65
+ start: number;
66
+ end: number;
67
+ };
68
+ /** The fact mention's span. */
69
+ mention: Span;
70
+ /** The negation cue's span, when one applied. */
71
+ negation?: Span | undefined;
72
+ /** The opposing assertion in a conflict. */
73
+ other?: {
74
+ assertion: string;
75
+ span: {
76
+ start: number;
77
+ end: number;
78
+ };
79
+ } | undefined;
80
+ }
81
+ /** The result of scanning one document against an inventory. */
82
+ export interface InventoryScan {
83
+ /** Local negation, obsolete-current and conflict triggers, span-sorted. */
84
+ triggers: InventoryTrigger[];
85
+ /** Still-valid facts with no recognised mention. */
86
+ missing: string[];
87
+ /** Facts with at least one affirmative (or, for obsolete claims, any) mention. */
88
+ recognised: string[];
89
+ }
90
+ /**
91
+ * Split text into assertions, keeping each assertion's document offsets.
92
+ * @param text The text to split.
93
+ * @param baseOffset The offset of `text` within the document.
94
+ * @param context The full bullet or Summary line the text belongs to.
95
+ * @returns The nonblank assertions in order.
96
+ */
97
+ export declare function assertionsOf(text: string, baseOffset: number, context?: string): Assertion[];
98
+ /**
99
+ * Read a document's Summary, Facts and Failed-approach surfaces with offsets.
100
+ * @param document The document body.
101
+ * @returns The claim surface.
102
+ */
103
+ export declare function claimSurfaceOf(document: string): ClaimSurface;
104
+ /**
105
+ * Scan one document against a configured inventory.
106
+ * @param document The document body.
107
+ * @param inventory The scenario's configured inventory.
108
+ * @returns Triggers with spans, plus recognised and missing facts.
109
+ */
110
+ export declare function scanInventory(document: string, inventory: PreservationInventorySpec): InventoryScan;
111
+ /** The deterministic cross-check result for one replacement document. */
112
+ export interface PreservationScreenResult {
113
+ /** Rejected on an explicit contradiction; unknown when a fact is absent. */
114
+ status: "accepted" | "rejected" | "unknown";
115
+ /** Still-valid facts with no recognised mention. */
116
+ missing: string[];
117
+ /** Whether a current obsolete claim survived. */
118
+ obsolete: boolean;
119
+ /** Fact ids with a local contradiction or conflict. */
120
+ contradicted: string[];
121
+ /** The triggers behind a rejection, with full context and spans. */
122
+ triggers: InventoryTrigger[];
123
+ /** Always true: a keyword screen never decides the semantic guardrail. */
124
+ requiresSemanticReview: true;
125
+ }
126
+ /**
127
+ * Screen a replacement document against an inventory as a pre-check.
128
+ *
129
+ * This is a cross-check only. `accepted` still requires the evidence-based
130
+ * semantic review; `unknown` means a keyword was absent, never that a fact
131
+ * was lost.
132
+ * @param document The resulting document body.
133
+ * @param inventory The scenario's configured inventory.
134
+ * @returns The screen result with every rejection's triggers.
135
+ */
136
+ export declare function preservationScreen(document: string, inventory: PreservationInventorySpec): PreservationScreenResult;
@@ -0,0 +1,307 @@
1
+ /** A section heading line (text starts with a non-blank character). */
2
+ const HEADING = /^#{2,3}[ \t]+([^ \t].*)$/;
3
+ /** A bullet line (text starts with a non-blank character). */
4
+ const BULLET = /^[ \t]*[-*][ \t]+([^ \t].*)$/;
5
+ /** A Summary line. */
6
+ const SUMMARY = /^Summary:(.*)$/i;
7
+ /** Sentence/clause separators used to evaluate assertions independently. */
8
+ const SEPARATOR = /;\s*|\.\s+(?=[A-Z`])/g;
9
+ /**
10
+ * Split text into assertions, keeping each assertion's document offsets.
11
+ * @param text The text to split.
12
+ * @param baseOffset The offset of `text` within the document.
13
+ * @param context The full bullet or Summary line the text belongs to.
14
+ * @returns The nonblank assertions in order.
15
+ */
16
+ export function assertionsOf(text, baseOffset, context = text) {
17
+ const assertions = [];
18
+ let cursor = 0;
19
+ SEPARATOR.lastIndex = 0;
20
+ for (let match = SEPARATOR.exec(text); match !== null; match = SEPARATOR.exec(text)) {
21
+ pushAssertion(assertions, text, baseOffset, cursor, match.index, context);
22
+ cursor = match.index + match[0].length;
23
+ }
24
+ pushAssertion(assertions, text, baseOffset, cursor, text.length, context);
25
+ return assertions;
26
+ }
27
+ /**
28
+ * Append one trimmed, nonblank assertion slice.
29
+ * @param assertions The accumulator.
30
+ * @param text The whole text.
31
+ * @param baseOffset The text's document offset.
32
+ * @param from Slice start.
33
+ * @param to Slice end.
34
+ * @param context The full bullet or Summary line.
35
+ */
36
+ function pushAssertion(assertions, text, baseOffset, from, to, context) {
37
+ const raw = text.slice(from, to);
38
+ const trimmed = raw.trim();
39
+ if (trimmed === "")
40
+ return;
41
+ const leading = raw.length - raw.trimStart().length;
42
+ assertions.push({
43
+ text: trimmed,
44
+ context,
45
+ start: baseOffset + from + leading,
46
+ end: baseOffset + from + leading + trimmed.length,
47
+ });
48
+ }
49
+ /**
50
+ * Read a document's Summary, Facts and Failed-approach surfaces with offsets.
51
+ * @param document The document body.
52
+ * @returns The claim surface.
53
+ */
54
+ export function claimSurfaceOf(document) {
55
+ const surface = {
56
+ summary: "",
57
+ summaryAssertions: [],
58
+ facts: [],
59
+ failedApproaches: [],
60
+ };
61
+ let section = "none";
62
+ let offset = 0;
63
+ for (const line of document.split("\n")) {
64
+ section = lineSection(surface, line, offset, section);
65
+ offset += line.length + 1;
66
+ }
67
+ return surface;
68
+ }
69
+ /**
70
+ * Apply one document line to the claim surface.
71
+ * @param surface The claim surface accumulator.
72
+ * @param line The raw line.
73
+ * @param offset The line's document offset.
74
+ * @param section The section state before this line.
75
+ * @returns The section state after this line.
76
+ */
77
+ function lineSection(surface, line, offset, section) {
78
+ const trimmed = line.trim();
79
+ const heading = HEADING.exec(trimmed);
80
+ if (heading !== null)
81
+ return sectionOf((heading[1] ?? "").trim());
82
+ const summary = SUMMARY.exec(trimmed);
83
+ if (summary !== null) {
84
+ recordSummary(surface, line, offset, (summary[1] ?? "").trim());
85
+ return section;
86
+ }
87
+ const bullet = BULLET.exec(line);
88
+ if (bullet !== null && section !== "none") {
89
+ recordBullet(surface, bullet[1] ?? "", line, offset, section);
90
+ }
91
+ return section;
92
+ }
93
+ /**
94
+ * Record a Summary line's value and assertions.
95
+ * @param surface The claim surface.
96
+ * @param line The raw line.
97
+ * @param offset The line's document offset.
98
+ * @param value The trimmed Summary value.
99
+ */
100
+ function recordSummary(surface, line, offset, value) {
101
+ surface.summary = value;
102
+ surface.summaryAssertions =
103
+ value === ""
104
+ ? []
105
+ : assertionsOf(value, offset + line.indexOf(value), line.trim());
106
+ }
107
+ /**
108
+ * Record one bullet line in its section.
109
+ * @param surface The claim surface.
110
+ * @param text The bullet text.
111
+ * @param line The raw line.
112
+ * @param offset The line's document offset.
113
+ * @param section The current section.
114
+ */
115
+ function recordBullet(surface, text, line, offset, section) {
116
+ const entry = { text, offset: offset + line.indexOf(text) };
117
+ if (section === "facts")
118
+ surface.facts.push(entry);
119
+ else
120
+ surface.failedApproaches.push(entry);
121
+ }
122
+ /**
123
+ * Classify a section heading for claim-surface purposes.
124
+ * @param name The heading text.
125
+ * @returns The section kind.
126
+ */
127
+ function sectionOf(name) {
128
+ if (/^facts$/i.test(name))
129
+ return "facts";
130
+ if (/^failed\s+(approaches|attempts)$/i.test(name))
131
+ return "failed";
132
+ return "none";
133
+ }
134
+ /**
135
+ * Find the first match of a regex source in an assertion, as a span.
136
+ * @param source The regex source.
137
+ * @param assertion The assertion.
138
+ * @param baseOffset The assertion's document offset.
139
+ * @returns The span, or undefined.
140
+ */
141
+ function spanIn(source, assertion, baseOffset) {
142
+ const match = new RegExp(source, "i").exec(assertion.text);
143
+ if (match === null)
144
+ return undefined;
145
+ return {
146
+ text: match[0],
147
+ start: baseOffset + match.index,
148
+ end: baseOffset + match.index + match[0].length,
149
+ };
150
+ }
151
+ /**
152
+ * Collect every mention of one fact across the Summary and Facts assertions.
153
+ * @param fact The configured fact.
154
+ * @param assertions The assertions to scan, with document offsets.
155
+ * @param negationCues The inventory's negation cue sources.
156
+ * @returns The mentions in document order.
157
+ */
158
+ function mentionsOf(fact, assertions, negationCues) {
159
+ const patterns = fact.obsoleteCurrentPatterns ?? fact.mentionPatterns ?? [];
160
+ const mentions = [];
161
+ for (const assertion of assertions) {
162
+ for (const pattern of patterns) {
163
+ const span = spanIn(pattern, assertion, assertion.start);
164
+ if (span === undefined)
165
+ continue;
166
+ const negation = negationCues
167
+ .map((cue) => spanIn(cue, assertion, assertion.start))
168
+ .find((cue) => cue !== undefined);
169
+ mentions.push({ assertion, span, negation });
170
+ break;
171
+ }
172
+ }
173
+ return mentions;
174
+ }
175
+ /**
176
+ * Build the triggers one fact's mentions imply.
177
+ * @param fact The configured fact.
178
+ * @param mentions The fact's mentions.
179
+ * @returns Triggers for local negation, obsolete-current claims and conflicts.
180
+ */
181
+ function factTriggers(fact, mentions) {
182
+ const obsolete = fact.obsoleteCurrentPatterns !== undefined;
183
+ const triggers = [];
184
+ const affirmative = mentions.filter((mention) => mention.negation === undefined);
185
+ for (const mention of mentions) {
186
+ const base = {
187
+ factId: fact.id,
188
+ context: mention.assertion.context,
189
+ assertion: mention.assertion.text,
190
+ span: {
191
+ start: mention.assertion.start,
192
+ end: mention.assertion.end,
193
+ },
194
+ mention: mention.span,
195
+ };
196
+ if (obsolete) {
197
+ if (mention.negation === undefined) {
198
+ triggers.push({
199
+ ...base,
200
+ kind: "obsolete-current",
201
+ });
202
+ }
203
+ continue;
204
+ }
205
+ if (mention.negation === undefined)
206
+ continue;
207
+ triggers.push({
208
+ ...base,
209
+ kind: "negated-fact",
210
+ negation: mention.negation,
211
+ });
212
+ const opposing = affirmative[0];
213
+ if (opposing !== undefined && opposing !== mention) {
214
+ triggers.push({
215
+ ...base,
216
+ kind: "conflict",
217
+ negation: mention.negation,
218
+ other: {
219
+ assertion: opposing.assertion.text,
220
+ span: {
221
+ start: opposing.assertion.start,
222
+ end: opposing.assertion.end,
223
+ },
224
+ },
225
+ });
226
+ }
227
+ }
228
+ return triggers;
229
+ }
230
+ /**
231
+ * Scan one document against a configured inventory.
232
+ * @param document The document body.
233
+ * @param inventory The scenario's configured inventory.
234
+ * @returns Triggers with spans, plus recognised and missing facts.
235
+ */
236
+ export function scanInventory(document, inventory) {
237
+ const surface = claimSurfaceOf(document);
238
+ const assertions = [
239
+ ...surface.summaryAssertions,
240
+ ...surface.facts.flatMap((fact) => assertionsOf(fact.text, fact.offset, fact.text)),
241
+ ];
242
+ const triggers = [];
243
+ const missing = [];
244
+ const recognised = [];
245
+ for (const fact of inventory.facts) {
246
+ const mentions = mentionsOf(fact, assertions, inventory.negationCues);
247
+ triggers.push(...factTriggers(fact, mentions));
248
+ const obsolete = fact.obsoleteCurrentPatterns !== undefined;
249
+ seenFact(fact, mentions, obsolete, missing, recognised);
250
+ }
251
+ triggers.sort((left, right) => left.span.start - right.span.start);
252
+ return { triggers, missing, recognised };
253
+ }
254
+ /**
255
+ * Record whether a fact is recognised or missing.
256
+ * @param fact The configured fact.
257
+ * @param mentions The fact's mentions.
258
+ * @param obsolete Whether the fact is an obsolete-claim fact.
259
+ * @param missing The missing accumulator.
260
+ * @param recognised The recognised accumulator.
261
+ */
262
+ function seenFact(fact, mentions, obsolete, missing, recognised) {
263
+ if (obsolete) {
264
+ if (mentions.length > 0)
265
+ recognised.push(fact.id);
266
+ return;
267
+ }
268
+ const affirmative = mentions.some((mention) => mention.negation === undefined);
269
+ if (affirmative)
270
+ recognised.push(fact.id);
271
+ else
272
+ missing.push(fact.id);
273
+ }
274
+ /**
275
+ * Screen a replacement document against an inventory as a pre-check.
276
+ *
277
+ * This is a cross-check only. `accepted` still requires the evidence-based
278
+ * semantic review; `unknown` means a keyword was absent, never that a fact
279
+ * was lost.
280
+ * @param document The resulting document body.
281
+ * @param inventory The scenario's configured inventory.
282
+ * @returns The screen result with every rejection's triggers.
283
+ */
284
+ export function preservationScreen(document, inventory) {
285
+ const scan = scanInventory(document, inventory);
286
+ const obsolete = scan.triggers.some((trigger) => trigger.kind === "obsolete-current");
287
+ const contradicted = [
288
+ ...new Set(scan.triggers
289
+ .filter((trigger) => trigger.kind !== "obsolete-current")
290
+ .map((trigger) => trigger.factId)),
291
+ ];
292
+ const status = obsolete
293
+ ? "rejected"
294
+ : contradicted.length > 0
295
+ ? "rejected"
296
+ : scan.missing.length > 0
297
+ ? "unknown"
298
+ : "accepted";
299
+ return {
300
+ status,
301
+ missing: scan.missing,
302
+ obsolete,
303
+ contradicted,
304
+ triggers: scan.triggers,
305
+ requiresSemanticReview: true,
306
+ };
307
+ }
@@ -0,0 +1,15 @@
1
+ /** The two classifications the historical NOTE-02 review used. */
2
+ export type NoteVersionVerdict = "useful-update" | "misleading";
3
+ /**
4
+ * Split Summary/Facts assertions so one rejection cannot mask another.
5
+ * @param document The document body.
6
+ * @returns The assertion texts from the claim surface.
7
+ */
8
+ export declare function claimAssertions(document: string): string[];
9
+ /**
10
+ * Classify a NOTE-02 documentation update.
11
+ * @param document The replacement document body.
12
+ * @returns `misleading` when the superseded 22 floor is still presented as
13
+ * current, otherwise `useful-update`.
14
+ */
15
+ export declare function noteVersionVerdict(document: string): NoteVersionVerdict;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The NOTE-02 partial-fix rule, scoped to the historical correction.
3
+ *
4
+ * A documentation update that presents the superseded Node 22 floor as the
5
+ * supported/current contract in its Summary or Facts is misleading; a
6
+ * completed Node 24 migration is a useful update. Assertions are evaluated
7
+ * separately so a rejecting clause in one sentence cannot suppress an
8
+ * affirmative claim in another, and Failed approaches stay context.
9
+ *
10
+ * This helper is deliberately not a general semantic classifier.
11
+ */
12
+ import { claimSurfaceOf } from "./inventory.js";
13
+ /** Whether one assertion rejects the Node 22 floor. */
14
+ const rejects22 = (text) => /Node\s*22[^\n.]{0,50}\b(?:no longer|not|never|unsupported|deprecated|superseded|must not)\b/i.test(text) ||
15
+ /\b(?:no longer|not|never)\b[^\n.]{0,30}\b(?:support|use|require)\b[^\n.]{0,20}Node\s*22/i.test(text);
16
+ /** Whether one assertion presents the Node 22 floor as current. */
17
+ const asserts22 = (text) => /Node\s*22/i.test(text) ||
18
+ /22\s*or newer/i.test(text) ||
19
+ /\bfloor\b[^\n]{0,12}\b22\b/i.test(text) ||
20
+ /\bminimum\b[^\n]{0,24}\b22\b/i.test(text) ||
21
+ /required\s+is\s+22/i.test(text);
22
+ /**
23
+ * Split Summary/Facts assertions so one rejection cannot mask another.
24
+ * @param document The document body.
25
+ * @returns The assertion texts from the claim surface.
26
+ */
27
+ export function claimAssertions(document) {
28
+ const surface = claimSurfaceOf(document);
29
+ return [
30
+ ...surface.summaryAssertions.map((assertion) => assertion.text),
31
+ ...surface.facts.flatMap((fact) => fact.text
32
+ .split(/\n+|(?<=[.!?;])\s+/)
33
+ .map((part) => part.trim())
34
+ .filter(Boolean)),
35
+ ];
36
+ }
37
+ /**
38
+ * Classify a NOTE-02 documentation update.
39
+ * @param document The replacement document body.
40
+ * @returns `misleading` when the superseded 22 floor is still presented as
41
+ * current, otherwise `useful-update`.
42
+ */
43
+ export function noteVersionVerdict(document) {
44
+ const claims = claimAssertions(document).some((assertion) => asserts22(assertion) && !rejects22(assertion));
45
+ return claims ? "misleading" : "useful-update";
46
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Reconciliation between static-screen findings and the recorded judge layer.
3
+ *
4
+ * A screen finding is not a verdict: this module reports whether the judge's
5
+ * reason or evidence text references the finding, and keeps the two layers
6
+ * distinct. Exact-text reference is deliberately conservative — a paraphrase
7
+ * counts as not referenced, and a short excerpt cannot establish anything.
8
+ */
9
+ import type { EvidenceRole, Finding, TrialIdentity } from "./rules.ts";
10
+ /** The judge text a reconciliation reads. */
11
+ export interface JudgementText {
12
+ /** The judgement's stated reason. */
13
+ reason: string;
14
+ /** The judgement's evidence quotes. */
15
+ evidence: string[];
16
+ }
17
+ /** One finding's reconciliation status against the judge layer. */
18
+ export interface ReconcileRow {
19
+ /** The finding's rule id. */
20
+ ruleId: string;
21
+ /** The finding's inventory fact id, when present. */
22
+ factId: string | undefined;
23
+ /** The trial the finding came from. */
24
+ trial: TrialIdentity;
25
+ /** The evidence role the finding cites. */
26
+ role: EvidenceRole;
27
+ /** The finding's root-relative source reference. */
28
+ source: string;
29
+ /** Whether the judge text references the finding's excerpt. */
30
+ status: "referenced" | "not-referenced" | "unresolved";
31
+ }
32
+ /**
33
+ * Reconcile screen findings with one judgement layer.
34
+ * @param findings The screen findings.
35
+ * @param judgement The selected judgement's reason and evidence, when judged.
36
+ * @returns One row per finding: referenced, not-referenced or unresolved.
37
+ */
38
+ export declare function reconcileFindings(findings: readonly Finding[], judgement: JudgementText | undefined): ReconcileRow[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Normalise text for conservative excerpt comparison.
3
+ * @param text The text to normalise.
4
+ * @returns Lowercase words with markdown punctuation collapsed.
5
+ */
6
+ function normalise(text) {
7
+ return text
8
+ .toLowerCase()
9
+ .replace(/[`*_#|]/g, " ")
10
+ .replace(/\s+/g, " ")
11
+ .trim();
12
+ }
13
+ /**
14
+ * Reconcile screen findings with one judgement layer.
15
+ * @param findings The screen findings.
16
+ * @param judgement The selected judgement's reason and evidence, when judged.
17
+ * @returns One row per finding: referenced, not-referenced or unresolved.
18
+ */
19
+ export function reconcileFindings(findings, judgement) {
20
+ const rows = findings.map((finding) => ({
21
+ ruleId: finding.ruleId,
22
+ factId: finding.factId,
23
+ trial: finding.trial,
24
+ role: finding.role,
25
+ source: finding.source.reference,
26
+ status: "unresolved",
27
+ }));
28
+ if (judgement === undefined)
29
+ return rows;
30
+ const text = normalise([judgement.reason, ...judgement.evidence].join(" "));
31
+ return findings.map((finding, index) => {
32
+ const excerpt = normalise(finding.excerpt);
33
+ const status = excerpt.length < 8
34
+ ? "unresolved"
35
+ : text.includes(excerpt)
36
+ ? "referenced"
37
+ : "not-referenced";
38
+ return { ...rows[index], status };
39
+ });
40
+ }