@cassiomc1/forgeloop 0.1.10 → 0.1.12
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.
- package/.cursor/rules/project-loop.mdc +16 -18
- package/.github/copilot-instructions.md +16 -19
- package/AGENTS.md +18 -24
- package/AGENT_COMPATIBILITY.md +4 -211
- package/CLAUDE.md +16 -18
- package/LOOP_ENGINEERING.md +73 -0
- package/LOOP_SYSTEM_DESIGN.md +9 -5
- package/ORCHESTRATOR_INTEGRATION.md +4 -0
- package/PROTOCOL_INTEGRATION.md +171 -0
- package/QUALITY_SCORECARD.md +1 -1
- package/README.md +123 -103
- package/TERMINOLOGY.md +2 -0
- package/THREAT_MODEL.md +4 -0
- package/package.json +5 -4
- package/schemas/current-contract.schema.json +48 -2
- package/schemas/evidence.schema.json +1 -0
- package/schemas/execution-receipt.schema.json +2 -0
- package/schemas/work-state.schema.json +44 -1
- package/src/cli.js +48 -4
- package/src/commands/record-terminal-result.js +18 -0
- package/src/commands/validate-protocol.js +14 -2
- package/src/core/checks.js +20 -0
- package/src/core/completion-artifacts.js +266 -11
- package/src/core/completion-recovery.js +15 -0
- package/src/core/completion-relationships.js +23 -9
- package/src/core/completion.js +130 -2
- package/src/core/contract.js +82 -4
- package/src/core/coverage.js +17 -12
- package/src/core/discovery-surfaces.js +22 -0
- package/src/core/events.js +183 -2
- package/src/core/evidence-readiness.js +333 -0
- package/src/core/evidence.js +22 -4
- package/src/core/execution-prerequisites.js +6 -1
- package/src/core/inspect.js +23 -12
- package/src/core/native-adapters.js +30 -2
- package/src/core/next-action.js +103 -27
- package/src/core/phase.js +68 -5
- package/src/core/protocol.js +28 -1
- package/src/core/receipt.js +5 -0
- package/src/core/schema-validation.js +28 -6
- package/src/core/templates.js +1 -0
- package/src/core/work-state.js +27 -0
- package/conformance/README.md +0 -148
- package/conformance/backend-auth/EXPECTED_ROUTE.json +0 -7
- package/conformance/backend-auth/REQUEST.md +0 -4
- package/conformance/backend-auth/REQUIRED_EVIDENCE.json +0 -3
- package/conformance/backend-auth/REQUIRED_GATES.json +0 -3
- package/conformance/blind-premium-website/EXPECTED_ROUTE.json +0 -7
- package/conformance/blind-premium-website/REQUEST.md +0 -6
- package/conformance/blind-premium-website/REQUIRED_EVIDENCE.json +0 -14
- package/conformance/blind-premium-website/REQUIRED_GATES.json +0 -3
- package/conformance/complete-website/EXPECTED_ROUTE.json +0 -7
- package/conformance/complete-website/REQUEST.md +0 -6
- package/conformance/complete-website/REQUIRED_EVIDENCE.json +0 -3
- package/conformance/complete-website/REQUIRED_GATES.json +0 -3
- package/conformance/docs-only/EXPECTED_ROUTE.json +0 -7
- package/conformance/docs-only/REQUEST.md +0 -4
- package/conformance/docs-only/REQUIRED_EVIDENCE.json +0 -3
- package/conformance/docs-only/REQUIRED_GATES.json +0 -3
- package/conformance/runs/2026-08-11-codex-first-live.md +0 -98
- package/conformance/runs/2026-08-11-codex-second-live.md +0 -87
- package/conformance/runs/2026-08-13-codex-fifth-live.md +0 -386
- package/conformance/runs/2026-08-13-codex-fourth-live.md +0 -309
- package/conformance/runs/2026-08-13-codex-sixth-live.md +0 -412
- package/conformance/simple-bug/EXPECTED_ROUTE.json +0 -7
- package/conformance/simple-bug/REQUEST.md +0 -4
- package/conformance/simple-bug/REQUIRED_EVIDENCE.json +0 -3
- package/conformance/simple-bug/REQUIRED_GATES.json +0 -3
- package/src/core/agent-support.js +0 -89
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { sha256 } from "./manifest.js";
|
|
2
|
+
|
|
3
|
+
export const REQUIREMENT_TYPES = Object.freeze([
|
|
4
|
+
"PRODUCT",
|
|
5
|
+
"VERIFICATION",
|
|
6
|
+
"LIFECYCLE",
|
|
7
|
+
"PUBLICATION",
|
|
8
|
+
"PRODUCTION_READINESS",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
export const TERMINAL_OWNED_TYPES = Object.freeze([
|
|
12
|
+
"LIFECYCLE",
|
|
13
|
+
"PUBLICATION",
|
|
14
|
+
"PRODUCTION_READINESS",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const LIFECYCLE_TERMS = /\b(?:lifecycle reaches|forgeloop reaches|complete returns|validator-backed complete|completion validated|review is approved)\b/i;
|
|
18
|
+
const PUBLICATION_TERMS = /\b(?:publication succeeds|release (?:is )?published|package (?:is )?published|published to|published package)\b/i;
|
|
19
|
+
const PRODUCTION_TERMS = /\b(?:deployment succeeds|production deployment|production readiness|production validation|production smoke|deployed to)\b/i;
|
|
20
|
+
const NON_TERMINAL_TERMS = /\b(?:tests?|lint|build|typecheck|types?|coverage|keyboard|zoom|contrast|motion|checks?|unit|e2e|integration|suite|regression|browser|responsive|html|css|js|component|api)\b/i;
|
|
21
|
+
const CONJUNCTION_TERMS = /\b(?:and|with|then|plus|also|after)\b/i;
|
|
22
|
+
|
|
23
|
+
export function isMixedTerminalRequirement(text) {
|
|
24
|
+
if (typeof text !== "string") return false;
|
|
25
|
+
const hasTerminal = LIFECYCLE_TERMS.test(text) || PUBLICATION_TERMS.test(text) || PRODUCTION_TERMS.test(text);
|
|
26
|
+
if (!hasTerminal) return false;
|
|
27
|
+
return NON_TERMINAL_TERMS.test(text) || CONJUNCTION_TERMS.test(text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stableId(text) {
|
|
31
|
+
return `REQ_${sha256(Buffer.from(text.trim().replace(/\s+/g, " ").toLowerCase())).slice(0, 16).toUpperCase()}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isPublicationStatusSatisfied(actual, required) {
|
|
35
|
+
if (required === "committed") {
|
|
36
|
+
return ["committed", "pushed", "published"].includes(actual);
|
|
37
|
+
}
|
|
38
|
+
if (required === "pushed") {
|
|
39
|
+
return ["pushed", "published"].includes(actual);
|
|
40
|
+
}
|
|
41
|
+
if (required === "published") {
|
|
42
|
+
return actual === "published";
|
|
43
|
+
}
|
|
44
|
+
if (required === "deployed") {
|
|
45
|
+
return actual === "deployed";
|
|
46
|
+
}
|
|
47
|
+
return actual === required;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function classifyRequirement(input) {
|
|
51
|
+
if (typeof input === "string") {
|
|
52
|
+
const text = input.trim();
|
|
53
|
+
const isMixed = isMixedTerminalRequirement(text);
|
|
54
|
+
const type = isMixed
|
|
55
|
+
? "VERIFICATION"
|
|
56
|
+
: LIFECYCLE_TERMS.test(text)
|
|
57
|
+
? "LIFECYCLE"
|
|
58
|
+
: PUBLICATION_TERMS.test(text)
|
|
59
|
+
? "PUBLICATION"
|
|
60
|
+
: PRODUCTION_TERMS.test(text)
|
|
61
|
+
? "PRODUCTION_READINESS"
|
|
62
|
+
: "VERIFICATION";
|
|
63
|
+
let requiredPublicationStatus = undefined;
|
|
64
|
+
if (type === "PUBLICATION") {
|
|
65
|
+
if (/\b(?:publish|published|release published|published package)\b/i.test(text)) {
|
|
66
|
+
requiredPublicationStatus = "published";
|
|
67
|
+
} else if (/\b(?:deploy|deployed)\b/i.test(text)) {
|
|
68
|
+
requiredPublicationStatus = "deployed";
|
|
69
|
+
} else if (/\b(?:push|pushed)\b/i.test(text)) {
|
|
70
|
+
requiredPublicationStatus = "pushed";
|
|
71
|
+
} else if (/\b(?:commit|committed)\b/i.test(text)) {
|
|
72
|
+
requiredPublicationStatus = "committed";
|
|
73
|
+
} else {
|
|
74
|
+
requiredPublicationStatus = "published";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
id: stableId(text),
|
|
79
|
+
text,
|
|
80
|
+
type,
|
|
81
|
+
operator: "SINGLE",
|
|
82
|
+
lifecycleOwned: type === "LIFECYCLE",
|
|
83
|
+
terminalOwned: TERMINAL_OWNED_TYPES.includes(type),
|
|
84
|
+
mixedTerminal: isMixed,
|
|
85
|
+
requiredEvidenceKind: "OBSERVED",
|
|
86
|
+
requiredPublicationStatus,
|
|
87
|
+
requirements: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
91
|
+
throw new TypeError("Requirement must be a string or object");
|
|
92
|
+
}
|
|
93
|
+
const text = input.text ?? input.id;
|
|
94
|
+
const classified = classifyRequirement(text);
|
|
95
|
+
const type = input.type ?? classified.type;
|
|
96
|
+
const isMixed = input.mixedTerminal ?? (typeof text === "string" && isMixedTerminalRequirement(text));
|
|
97
|
+
return {
|
|
98
|
+
...classified,
|
|
99
|
+
...structuredClone(input),
|
|
100
|
+
id: input.id ?? classified.id,
|
|
101
|
+
text,
|
|
102
|
+
type,
|
|
103
|
+
operator: input.operator ?? (input.requirements?.length ? "ALL" : "SINGLE"),
|
|
104
|
+
lifecycleOwned: input.lifecycleOwned ?? type === "LIFECYCLE",
|
|
105
|
+
terminalOwned: input.terminalOwned ?? TERMINAL_OWNED_TYPES.includes(type),
|
|
106
|
+
mixedTerminal: isMixed,
|
|
107
|
+
requiredEvidenceKind: input.requiredEvidenceKind ?? "OBSERVED",
|
|
108
|
+
requiredPublicationStatus: input.requiredPublicationStatus ?? classified.requiredPublicationStatus,
|
|
109
|
+
requirements: (input.requirements ?? []).map(classifyRequirement),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function normalizeRequirements(requirements = []) {
|
|
114
|
+
const byId = new Map();
|
|
115
|
+
for (const raw of requirements) {
|
|
116
|
+
const requirement = classifyRequirement(raw);
|
|
117
|
+
if (!byId.has(requirement.id)) byId.set(requirement.id, requirement);
|
|
118
|
+
}
|
|
119
|
+
return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function ordinaryRequirements(requirements = []) {
|
|
123
|
+
return normalizeRequirements(requirements).filter((requirement) => !requirement.terminalOwned);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function ordinaryLeafRequirements(requirements = []) {
|
|
127
|
+
const result = [];
|
|
128
|
+
function visit(requirement) {
|
|
129
|
+
if (requirement.terminalOwned) return;
|
|
130
|
+
if (requirement.operator === "ALL" && Array.isArray(requirement.requirements) && requirement.requirements.length > 0) {
|
|
131
|
+
for (const child of requirement.requirements) {
|
|
132
|
+
visit(child);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
result.push(requirement);
|
|
137
|
+
}
|
|
138
|
+
for (const req of normalizeRequirements(requirements)) {
|
|
139
|
+
visit(req);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function terminalRequirements(requirements = []) {
|
|
145
|
+
return normalizeRequirements(requirements).filter((requirement) => requirement.terminalOwned);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function lifecycleRequirements(requirements = []) {
|
|
149
|
+
return normalizeRequirements(requirements).filter((requirement) => requirement.type === "LIFECYCLE");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function publicationRequirements(requirements = []) {
|
|
153
|
+
return normalizeRequirements(requirements).filter((requirement) => requirement.type === "PUBLICATION");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function productionReadinessRequirements(requirements = []) {
|
|
157
|
+
return normalizeRequirements(requirements).filter((requirement) => requirement.type === "PRODUCTION_READINESS");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function evaluateTerminalRequirements({ requirements = [], receipt = null } = {}) {
|
|
161
|
+
const normalized = normalizeRequirements(requirements);
|
|
162
|
+
const terminal = terminalRequirements(normalized);
|
|
163
|
+
const result = {
|
|
164
|
+
covered: [],
|
|
165
|
+
pending: [],
|
|
166
|
+
invalid: [],
|
|
167
|
+
errors: [],
|
|
168
|
+
};
|
|
169
|
+
const pubStatus = receipt ? (receipt.publicationStatus ?? "not-published") : "not-published";
|
|
170
|
+
const prodStatus = receipt ? (receipt.productionReadiness ?? "not-verified") : "not-verified";
|
|
171
|
+
const receiptEvidence = Array.isArray(receipt?.evidence) ? receipt.evidence : [];
|
|
172
|
+
|
|
173
|
+
for (const req of terminal) {
|
|
174
|
+
if (req.type === "LIFECYCLE") {
|
|
175
|
+
result.covered.push(req);
|
|
176
|
+
} else if (req.type === "PUBLICATION") {
|
|
177
|
+
const requiredLevel = req.requiredPublicationStatus ?? "published";
|
|
178
|
+
const matchingEvidence = receiptEvidence
|
|
179
|
+
.filter((item) => item.kind === "OBSERVED")
|
|
180
|
+
.filter((item) => item.details?.requirementId === req.id)
|
|
181
|
+
.filter((item) => item.details?.terminalType === "PUBLICATION");
|
|
182
|
+
const latestEvidence = latestAuthoritativeCheck(matchingEvidence);
|
|
183
|
+
const evidenceStatus = latestEvidence?.details?.terminalStatus;
|
|
184
|
+
const evidenceSatisfied = isPublicationStatusSatisfied(evidenceStatus, requiredLevel);
|
|
185
|
+
const globalSatisfied = isPublicationStatusSatisfied(pubStatus, requiredLevel);
|
|
186
|
+
|
|
187
|
+
if (globalSatisfied && evidenceSatisfied) {
|
|
188
|
+
result.covered.push(req);
|
|
189
|
+
} else {
|
|
190
|
+
result.pending.push(req);
|
|
191
|
+
result.errors.push({
|
|
192
|
+
code: "E_PUBLICATION_REQUIREMENT_PENDING",
|
|
193
|
+
message: `The contract explicitly requires publication status '${requiredLevel}', but publication evidence/status is insufficient for requirement: ${req.text}`,
|
|
194
|
+
requirementId: req.id,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
} else if (req.type === "PRODUCTION_READINESS") {
|
|
198
|
+
const matchingEvidence = receiptEvidence
|
|
199
|
+
.filter((item) => item.kind === "OBSERVED")
|
|
200
|
+
.filter((item) => item.details?.requirementId === req.id)
|
|
201
|
+
.filter((item) => item.details?.terminalType === "PRODUCTION_READINESS");
|
|
202
|
+
const latestEvidence = latestAuthoritativeCheck(matchingEvidence);
|
|
203
|
+
const evidenceStatus = latestEvidence?.details?.terminalStatus;
|
|
204
|
+
const evidenceSatisfied = evidenceStatus === "ready" || evidenceStatus === "verified";
|
|
205
|
+
const globalSatisfied = prodStatus === "ready" || prodStatus === "verified";
|
|
206
|
+
|
|
207
|
+
if (globalSatisfied && evidenceSatisfied) {
|
|
208
|
+
result.covered.push(req);
|
|
209
|
+
} else {
|
|
210
|
+
result.pending.push(req);
|
|
211
|
+
result.errors.push({
|
|
212
|
+
code: "E_PRODUCTION_REQUIREMENT_PENDING",
|
|
213
|
+
message: `The contract explicitly requires production readiness, but production readiness evidence/status is insufficient for requirement: ${req.text}`,
|
|
214
|
+
requirementId: req.id,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function matchesRequirement(check, requirement) {
|
|
223
|
+
return check?.requirement === requirement.id || check?.requirement === requirement.text;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function latestAuthoritativeCheck(candidates) {
|
|
227
|
+
if (!candidates || candidates.length === 0) return null;
|
|
228
|
+
return [...candidates].sort((a, b) => {
|
|
229
|
+
const cycleA = a.details?.verificationCycle ?? a.verificationCycle ?? 1;
|
|
230
|
+
const cycleB = b.details?.verificationCycle ?? b.verificationCycle ?? 1;
|
|
231
|
+
if (cycleA !== cycleB) return cycleA - cycleB;
|
|
232
|
+
const timeA = typeof a.timestamp === "string" ? a.timestamp : "";
|
|
233
|
+
const timeB = typeof b.timestamp === "string" ? b.timestamp : "";
|
|
234
|
+
if (timeA !== timeB) return timeA.localeCompare(timeB);
|
|
235
|
+
return 0;
|
|
236
|
+
}).at(-1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function authoritativeChecksForRequirements({ requirements = [], checks = [] } = {}) {
|
|
240
|
+
return normalizeRequirements(requirements).map((requirement) => {
|
|
241
|
+
const candidates = checks.filter((check) => matchesRequirement(check, requirement));
|
|
242
|
+
return {
|
|
243
|
+
requirement,
|
|
244
|
+
check: latestAuthoritativeCheck(candidates),
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function componentStatus(check, requirement, allChecks = []) {
|
|
250
|
+
if (requirement.operator !== "ALL" || !requirement.requirements?.length) return null;
|
|
251
|
+
const components = check?.details?.components;
|
|
252
|
+
const statuses = requirement.requirements.map((child) => {
|
|
253
|
+
if (Array.isArray(components)) {
|
|
254
|
+
const matchingComp = components.filter((item) => (
|
|
255
|
+
item?.requirementId === child.id || item?.requirement === child.text
|
|
256
|
+
)).at(-1);
|
|
257
|
+
if (matchingComp) return matchingComp;
|
|
258
|
+
}
|
|
259
|
+
const childCandidates = allChecks.filter((candidate) => matchesRequirement(candidate, child));
|
|
260
|
+
return latestAuthoritativeCheck(childCandidates);
|
|
261
|
+
});
|
|
262
|
+
if (statuses.some((item) => !item)) return "MISSING";
|
|
263
|
+
if (statuses.some((item) => item.status === "failed")) return "INVALID";
|
|
264
|
+
if (statuses.some((item) => item.status !== "passed" || item.evidenceKind !== "OBSERVED")) return "PARTIAL";
|
|
265
|
+
return "COVERED";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function evaluateRequiredEvidence({ requirements = [], checks = [] } = {}) {
|
|
269
|
+
const normalized = normalizeRequirements(requirements);
|
|
270
|
+
const result = {
|
|
271
|
+
ready: true,
|
|
272
|
+
required: normalized,
|
|
273
|
+
covered: [],
|
|
274
|
+
missing: [],
|
|
275
|
+
partial: [],
|
|
276
|
+
invalid: [],
|
|
277
|
+
lifecyclePending: [],
|
|
278
|
+
reasonCodes: [],
|
|
279
|
+
};
|
|
280
|
+
for (const requirement of normalized) {
|
|
281
|
+
if (requirement.mixedTerminal) {
|
|
282
|
+
result.invalid.push({
|
|
283
|
+
...requirement,
|
|
284
|
+
reasonCode: "E_CIRCULAR_COMPLETION_REQUIREMENT",
|
|
285
|
+
});
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (requirement.terminalOwned || requirement.lifecycleOwned) {
|
|
289
|
+
result.lifecyclePending.push(requirement);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const candidates = checks.filter((check) => matchesRequirement(check, requirement));
|
|
293
|
+
const check = latestAuthoritativeCheck(candidates);
|
|
294
|
+
const compound = componentStatus(check, requirement, checks);
|
|
295
|
+
if (compound === "INVALID" || check?.status === "failed") {
|
|
296
|
+
result.invalid.push(requirement);
|
|
297
|
+
} else if (compound === "PARTIAL") {
|
|
298
|
+
result.partial.push(requirement);
|
|
299
|
+
} else if (compound === "MISSING") {
|
|
300
|
+
result.missing.push(requirement);
|
|
301
|
+
} else if (
|
|
302
|
+
(check?.status === "passed" || compound === "COVERED")
|
|
303
|
+
&& (requirement.requiredEvidenceKind !== "OBSERVED" || check?.evidenceKind === "OBSERVED" || compound === "COVERED")
|
|
304
|
+
&& (compound === null || compound === "COVERED")
|
|
305
|
+
) {
|
|
306
|
+
result.covered.push(requirement);
|
|
307
|
+
} else if (check?.status === "passed" && check.evidenceKind !== "OBSERVED") {
|
|
308
|
+
result.invalid.push({ ...requirement, reasonCode: "E_EVIDENCE_KIND_INVALID" });
|
|
309
|
+
} else if (check) {
|
|
310
|
+
result.partial.push(requirement);
|
|
311
|
+
} else {
|
|
312
|
+
result.missing.push(requirement);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
result.ready = result.missing.length === 0 && result.partial.length === 0 && result.invalid.length === 0;
|
|
316
|
+
if (result.invalid.length) {
|
|
317
|
+
const specificCodes = result.invalid
|
|
318
|
+
.map((item) => item.reasonCode)
|
|
319
|
+
.filter(Boolean);
|
|
320
|
+
if (specificCodes.length > 0) {
|
|
321
|
+
result.reasonCodes.push(...new Set(specificCodes));
|
|
322
|
+
} else {
|
|
323
|
+
result.reasonCodes.push("E_EVIDENCE_INVALID");
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (result.missing.length) result.reasonCodes.push("E_EVIDENCE_REQUIRED");
|
|
327
|
+
if (result.partial.length) result.reasonCodes.push("E_EVIDENCE_PARTIAL");
|
|
328
|
+
const leaves = ordinaryLeafRequirements(normalized);
|
|
329
|
+
const authoritativeLeaves = authoritativeChecksForRequirements({ requirements: leaves, checks });
|
|
330
|
+
result.authoritativeFailures = authoritativeLeaves.filter(({ check }) => check?.status === "failed");
|
|
331
|
+
result.authoritativeBlocked = authoritativeLeaves.filter(({ check }) => check?.status === "blocked");
|
|
332
|
+
return result;
|
|
333
|
+
}
|
package/src/core/evidence.js
CHANGED
|
@@ -19,10 +19,22 @@ function assertNonEmptyString(value, label) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export function createEvidence(input, source, result, details) {
|
|
22
|
+
export function createEvidence(input, source, result, details, cycle) {
|
|
23
23
|
const value = typeof input === "string"
|
|
24
|
-
? {
|
|
25
|
-
|
|
24
|
+
? {
|
|
25
|
+
kind: input,
|
|
26
|
+
source,
|
|
27
|
+
result,
|
|
28
|
+
...(cycle !== undefined ? { verificationCycle: cycle } : {}),
|
|
29
|
+
details: {
|
|
30
|
+
...(details === undefined ? {} : details),
|
|
31
|
+
...(cycle !== undefined ? { verificationCycle: cycle } : {}),
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
: structuredClone(input);
|
|
35
|
+
if (value.details && typeof value.details === "object" && value.verificationCycle !== undefined) {
|
|
36
|
+
value.details.verificationCycle = value.verificationCycle;
|
|
37
|
+
}
|
|
26
38
|
assertEvidence(value);
|
|
27
39
|
return value;
|
|
28
40
|
}
|
|
@@ -36,6 +48,9 @@ export function assertEvidence(value, label = "evidence") {
|
|
|
36
48
|
}
|
|
37
49
|
assertNonEmptyString(value.source, `${label}.source`);
|
|
38
50
|
assertNonEmptyString(value.result, `${label}.result`);
|
|
51
|
+
if (value.verificationCycle !== undefined && (!Number.isInteger(value.verificationCycle) || value.verificationCycle < 1)) {
|
|
52
|
+
throw new EvidenceError(`${label}.verificationCycle must be a positive integer`);
|
|
53
|
+
}
|
|
39
54
|
return value;
|
|
40
55
|
}
|
|
41
56
|
|
|
@@ -50,7 +65,10 @@ export function evidenceMatches(evidence, terms) {
|
|
|
50
65
|
.filter((term) => typeof term === "string" && term.length > 0)
|
|
51
66
|
.map((term) => term.toLowerCase());
|
|
52
67
|
return (evidence ?? []).some((item) => {
|
|
53
|
-
const
|
|
68
|
+
const detailsText = item.details
|
|
69
|
+
? `${item.details.requirementText ?? ""} ${String(item.details.terminalType ?? "").replace(/_/g, " ")}`
|
|
70
|
+
: "";
|
|
71
|
+
const haystack = `${item.source} ${item.result} ${detailsText}`.toLowerCase();
|
|
54
72
|
return normalizedTerms.some((term) => haystack.includes(term));
|
|
55
73
|
});
|
|
56
74
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
|
|
2
2
|
import { readContract } from "./contract.js";
|
|
3
|
-
import { validateEventLedger } from "./events.js";
|
|
3
|
+
import { validateEventLedger, validateStateLedgerCoherence } from "./events.js";
|
|
4
4
|
import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
|
|
5
5
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
6
6
|
import { stateIdentityErrors } from "./completion-relationships.js";
|
|
@@ -195,6 +195,11 @@ export async function evaluateStartExecutionPrerequisites({ target, state, packa
|
|
|
195
195
|
|
|
196
196
|
const ledger = await validateEventLedger(target, packageRoot);
|
|
197
197
|
errors.push(...prerequisiteLedgerErrors(ledger, contract.value.taskId, preflight, route));
|
|
198
|
+
errors.push(...validateStateLedgerCoherence(state, ledger.events).map((error) => issue(
|
|
199
|
+
error.code,
|
|
200
|
+
error.message,
|
|
201
|
+
[ARTIFACT_PATHS.state, ARTIFACT_PATHS.events],
|
|
202
|
+
)));
|
|
198
203
|
errors.push(...persistedPreflightErrors);
|
|
199
204
|
return { errors, requiredArtifacts, contract, route, preflight, persistedPreflight, ledger };
|
|
200
205
|
}
|
package/src/core/inspect.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fileExists, ensureWithin, readBytes } from "./filesystem.js";
|
|
2
|
-
import {
|
|
2
|
+
import { DISCOVERY_SURFACES } from "./discovery-surfaces.js";
|
|
3
3
|
import { readManifest } from "./manifest.js";
|
|
4
4
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
5
5
|
import { inspectSchemaHealth } from "./schema-validation.js";
|
|
@@ -42,15 +42,14 @@ export async function inspectTarget({ target, packageRoot, contractFile = null }
|
|
|
42
42
|
? `${FORGELOOP_KIT_DIR}/schemas`
|
|
43
43
|
: "schemas";
|
|
44
44
|
const doctor = await runDoctor({ target, packageRoot });
|
|
45
|
-
const
|
|
46
|
-
id:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
available: (await Promise.all(
|
|
51
|
-
record.instructionFiles.map(async (relativePath) => fileExists(ensureWithin(target, relativePath))),
|
|
52
|
-
)).some(Boolean),
|
|
45
|
+
const surfaces = await Promise.all(DISCOVERY_SURFACES.map(async (surface) => ({
|
|
46
|
+
id: surface.id,
|
|
47
|
+
path: surface.path,
|
|
48
|
+
kind: surface.kind,
|
|
49
|
+
available: await fileExists(ensureWithin(target, surface.path)),
|
|
53
50
|
})));
|
|
51
|
+
const availableSurfaces = surfaces.filter((surface) => surface.available);
|
|
52
|
+
const protocolActivated = availableSurfaces.length > 0;
|
|
54
53
|
|
|
55
54
|
const findings = [...doctor.findings];
|
|
56
55
|
for (const schema of schemaHealth.schemas) {
|
|
@@ -100,9 +99,20 @@ export async function inspectTarget({ target, packageRoot, contractFile = null }
|
|
|
100
99
|
error: manifestError,
|
|
101
100
|
},
|
|
102
101
|
profile,
|
|
102
|
+
integration: {
|
|
103
|
+
protocolActivated,
|
|
104
|
+
protocolMarker: "FORGELOOP_PROJECT_PROTOCOL=REQUIRED",
|
|
105
|
+
discovery: {
|
|
106
|
+
status: protocolActivated ? "INSTRUCTION_DISCOVERED" : "INSTRUCTION_ABSENT",
|
|
107
|
+
surfaces,
|
|
108
|
+
},
|
|
109
|
+
capability: {
|
|
110
|
+
status: "NOT_VERIFIED",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
103
113
|
adapters: {
|
|
104
|
-
detected:
|
|
105
|
-
|
|
114
|
+
detected: availableSurfaces.map((s) => s.path),
|
|
115
|
+
surfaces,
|
|
106
116
|
},
|
|
107
117
|
protocol: {
|
|
108
118
|
version: PROTOCOL_VERSION,
|
|
@@ -112,7 +122,8 @@ export async function inspectTarget({ target, packageRoot, contractFile = null }
|
|
|
112
122
|
},
|
|
113
123
|
state: { ...state, path: WORK_STATE_PATH, present: statePresent },
|
|
114
124
|
compatibility: {
|
|
115
|
-
|
|
125
|
+
deprecated: true,
|
|
126
|
+
agents: [],
|
|
116
127
|
},
|
|
117
128
|
findings,
|
|
118
129
|
evidence,
|
|
@@ -15,13 +15,40 @@ export function nativeShimReferences(relativePath) {
|
|
|
15
15
|
const prefix = nativeShimPrefix(relativePath);
|
|
16
16
|
return [
|
|
17
17
|
`${prefix}/LOOP_ENGINEERING.md`,
|
|
18
|
-
`${prefix}/
|
|
18
|
+
`${prefix}/PROTOCOL_INTEGRATION.md`,
|
|
19
19
|
];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function nativeShim(relativePath) {
|
|
23
23
|
const kitPrefix = nativeShimPrefix(relativePath);
|
|
24
|
-
return
|
|
24
|
+
return `<!-- FORGELOOP_PROJECT_PROTOCOL=REQUIRED -->
|
|
25
|
+
# ForgeLoop Project Protocol
|
|
26
|
+
|
|
27
|
+
This project is ForgeLoop-enabled.
|
|
28
|
+
|
|
29
|
+
If your execution environment loaded this file, ForgeLoop applies to work
|
|
30
|
+
performed in this project regardless of model, provider, product, IDE,
|
|
31
|
+
agent name, runtime name, or orchestration environment.
|
|
32
|
+
|
|
33
|
+
Do not treat ForgeLoop as vendor-specific, harness-specific, optional,
|
|
34
|
+
advisory, or something to follow only "in spirit".
|
|
35
|
+
|
|
36
|
+
Read and follow:
|
|
37
|
+
- ${kitPrefix}/LOOP_ENGINEERING.md
|
|
38
|
+
- ${kitPrefix}/PROTOCOL_INTEGRATION.md
|
|
39
|
+
|
|
40
|
+
Before changing product or executable files, establish the ForgeLoop contract,
|
|
41
|
+
route, required gates, and READY preflight.
|
|
42
|
+
|
|
43
|
+
Use the project-local ForgeLoop CLI for lifecycle-owned protocol state.
|
|
44
|
+
Never manually synthesize lifecycle chronology or assign ForgeLoop COMPLETE.
|
|
45
|
+
|
|
46
|
+
Before claiming ForgeLoop-verified completion, require
|
|
47
|
+
\`forgeloop complete\` to return \`VALID\`.
|
|
48
|
+
|
|
49
|
+
If a required capability is unavailable, report that ForgeLoop dimension as
|
|
50
|
+
not verified rather than simulating it.
|
|
51
|
+
`;
|
|
25
52
|
}
|
|
26
53
|
|
|
27
54
|
export function resolveNativeReference(relativePath, reference) {
|
|
@@ -45,6 +72,7 @@ export function inspectNativeAdapter(relativePath, bytes) {
|
|
|
45
72
|
missingReferences,
|
|
46
73
|
resolvedReferences,
|
|
47
74
|
hasForgeLoopMarker: /forgeloop/i.test(text),
|
|
75
|
+
hasProtocolMarker: text.includes("FORGELOOP_PROJECT_PROTOCOL=REQUIRED"),
|
|
48
76
|
};
|
|
49
77
|
}
|
|
50
78
|
|