@ramplab/generator 0.1.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.
- package/LICENSE +21 -0
- package/README.md +23 -0
- package/dist/agentSdkRunner.d.ts +64 -0
- package/dist/agentSdkRunner.d.ts.map +1 -0
- package/dist/agentSdkRunner.js +158 -0
- package/dist/agentSdkRunner.js.map +1 -0
- package/dist/assembleStage.d.ts +41 -0
- package/dist/assembleStage.d.ts.map +1 -0
- package/dist/assembleStage.js +33 -0
- package/dist/assembleStage.js.map +1 -0
- package/dist/authorStage.d.ts +123 -0
- package/dist/authorStage.d.ts.map +1 -0
- package/dist/authorStage.js +284 -0
- package/dist/authorStage.js.map +1 -0
- package/dist/cloneRepo.d.ts +72 -0
- package/dist/cloneRepo.d.ts.map +1 -0
- package/dist/cloneRepo.js +104 -0
- package/dist/cloneRepo.js.map +1 -0
- package/dist/flagship.d.ts +62 -0
- package/dist/flagship.d.ts.map +1 -0
- package/dist/flagship.js +216 -0
- package/dist/flagship.js.map +1 -0
- package/dist/generateLab.d.ts +151 -0
- package/dist/generateLab.d.ts.map +1 -0
- package/dist/generateLab.js +291 -0
- package/dist/generateLab.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/intake.d.ts +31 -0
- package/dist/intake.d.ts.map +1 -0
- package/dist/intake.js +45 -0
- package/dist/intake.js.map +1 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +213 -0
- package/dist/live.js.map +1 -0
- package/dist/mapStage.d.ts +84 -0
- package/dist/mapStage.d.ts.map +1 -0
- package/dist/mapStage.js +241 -0
- package/dist/mapStage.js.map +1 -0
- package/dist/pass1.d.ts +85 -0
- package/dist/pass1.d.ts.map +1 -0
- package/dist/pass1.js +81 -0
- package/dist/pass1.js.map +1 -0
- package/dist/pipeline.d.ts +73 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +70 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/planStage.d.ts +141 -0
- package/dist/planStage.d.ts.map +1 -0
- package/dist/planStage.js +275 -0
- package/dist/planStage.js.map +1 -0
- package/dist/progress.d.ts +45 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +2 -0
- package/dist/progress.js.map +1 -0
- package/dist/repoCommit.d.ts +41 -0
- package/dist/repoCommit.d.ts.map +1 -0
- package/dist/repoCommit.js +84 -0
- package/dist/repoCommit.js.map +1 -0
- package/dist/repoSize.d.ts +23 -0
- package/dist/repoSize.d.ts.map +1 -0
- package/dist/repoSize.js +74 -0
- package/dist/repoSize.js.map +1 -0
- package/dist/resolveAnchors.d.ts +142 -0
- package/dist/resolveAnchors.d.ts.map +1 -0
- package/dist/resolveAnchors.js +242 -0
- package/dist/resolveAnchors.js.map +1 -0
- package/dist/verifyStage.d.ts +150 -0
- package/dist/verifyStage.d.ts.map +1 -0
- package/dist/verifyStage.js +461 -0
- package/dist/verifyStage.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Anchor, LabSpec } from '@ramplab/spec';
|
|
2
|
+
/**
|
|
3
|
+
* Anchor resolution — mechanical grounding enforcement (PLAN.md §3).
|
|
4
|
+
*
|
|
5
|
+
* `resolveAnchors(spec, repoDir)` checks every anchor in the spec's base
|
|
6
|
+
* content against the actual repository directory:
|
|
7
|
+
*
|
|
8
|
+
* - the anchored file exists (repo-relative; paths that escape `repoDir`
|
|
9
|
+
* never resolve),
|
|
10
|
+
* - the line range, if given, is valid against the file's real line count,
|
|
11
|
+
* - the symbol, if given, matches textually inside the anchored region.
|
|
12
|
+
*
|
|
13
|
+
* v1 is deliberately textual and language-agnostic — no parsers, no
|
|
14
|
+
* tree-sitter. A "symbol match" is a token-boundary substring match, which
|
|
15
|
+
* works uniformly across TS, Ruby, Go, Python, Java, C#, Dart, Kotlin,
|
|
16
|
+
* Swift, ... (tree-sitter-backed resolution is a v2 option).
|
|
17
|
+
*
|
|
18
|
+
* The function is deterministic and pure over `(spec, directory)`: no
|
|
19
|
+
* globals, no clock, no mutation of the input spec. Given the same spec and
|
|
20
|
+
* the same directory contents it always returns the same result.
|
|
21
|
+
*/
|
|
22
|
+
/** Why an individual anchor did or did not resolve. */
|
|
23
|
+
export type AnchorStatus = 'resolved' | 'file-missing' | 'lines-out-of-range' | 'symbol-not-found';
|
|
24
|
+
/**
|
|
25
|
+
* What to do with teachable units whose anchors fail to resolve:
|
|
26
|
+
* - `drop` (default): remove the unit from the returned base spec — the
|
|
27
|
+
* grounding constraint is enforced structurally.
|
|
28
|
+
* - `flag`: keep the spec intact and only report — for review flows where
|
|
29
|
+
* a human decides.
|
|
30
|
+
*/
|
|
31
|
+
export type ResolutionPolicy = 'drop' | 'flag';
|
|
32
|
+
/** Per-teachable-unit outcome, reflecting the active policy. */
|
|
33
|
+
export type UnitOutcome = 'resolved' | 'dropped' | 'flagged';
|
|
34
|
+
/** The resolution verdict for one anchor of one base widget. */
|
|
35
|
+
export interface AnchorResolution {
|
|
36
|
+
moduleId: string;
|
|
37
|
+
widgetId: string;
|
|
38
|
+
/**
|
|
39
|
+
* Ordinal of the anchor within its widget, in deterministic walk order.
|
|
40
|
+
* For widgets with a single top-level `anchors` array (callouts) this is
|
|
41
|
+
* the array index; nested carriers (system-map nodes/edges, data-model
|
|
42
|
+
* annotations) are walked depth-first in spec order.
|
|
43
|
+
*/
|
|
44
|
+
anchorIndex: number;
|
|
45
|
+
/** JSON path of the anchor within its widget (e.g. `["nodes", 0, "anchors", 1]`). */
|
|
46
|
+
anchorPath: (string | number)[];
|
|
47
|
+
/** The anchor as checked (fingerprint populated when resolved). */
|
|
48
|
+
anchor: Anchor;
|
|
49
|
+
status: AnchorStatus;
|
|
50
|
+
/** Human-readable explanation of the status. */
|
|
51
|
+
detail: string;
|
|
52
|
+
/**
|
|
53
|
+
* `sha256:<hex>` content hash of the anchored region (the line range if
|
|
54
|
+
* given, otherwise the whole file), only present when resolved. Line
|
|
55
|
+
* endings are normalized to `\n` before hashing so fingerprints are
|
|
56
|
+
* stable across checkout conventions.
|
|
57
|
+
*/
|
|
58
|
+
fingerprint?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The verdict for one teachable unit (a base widget). A unit is resolved
|
|
62
|
+
* only if *every* one of its anchors resolved — each claim's grounding must
|
|
63
|
+
* check out.
|
|
64
|
+
*/
|
|
65
|
+
export interface UnitResolution {
|
|
66
|
+
moduleId: string;
|
|
67
|
+
widgetId: string;
|
|
68
|
+
outcome: UnitOutcome;
|
|
69
|
+
anchors: AnchorResolution[];
|
|
70
|
+
}
|
|
71
|
+
export interface ResolutionSummary {
|
|
72
|
+
totalAnchors: number;
|
|
73
|
+
resolvedAnchors: number;
|
|
74
|
+
totalUnits: number;
|
|
75
|
+
resolvedUnits: number;
|
|
76
|
+
/** Units dropped or flagged, depending on policy. */
|
|
77
|
+
unresolvedUnits: number;
|
|
78
|
+
}
|
|
79
|
+
/** Structured report of a full resolution pass over a spec's base content. */
|
|
80
|
+
export interface ResolutionReport {
|
|
81
|
+
policy: ResolutionPolicy;
|
|
82
|
+
/** Every anchor checked, in spec order. */
|
|
83
|
+
anchors: AnchorResolution[];
|
|
84
|
+
/** Every teachable unit (base widget), in spec order. */
|
|
85
|
+
units: UnitResolution[];
|
|
86
|
+
summary: ResolutionSummary;
|
|
87
|
+
}
|
|
88
|
+
export interface ResolveAnchorsOptions {
|
|
89
|
+
/** @default 'drop' */
|
|
90
|
+
policy?: ResolutionPolicy;
|
|
91
|
+
}
|
|
92
|
+
export interface ResolveAnchorsResult {
|
|
93
|
+
/**
|
|
94
|
+
* The spec after resolution. Resolved anchors carry fingerprints. Under
|
|
95
|
+
* the `drop` policy, unresolved units are removed from their modules;
|
|
96
|
+
* under `flag` the structure is untouched. The human overlay is never
|
|
97
|
+
* modified — only the machine-owned base is subject to grounding
|
|
98
|
+
* enforcement.
|
|
99
|
+
*/
|
|
100
|
+
spec: LabSpec;
|
|
101
|
+
report: ResolutionReport;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Resolve every base-content anchor in `spec` against the repository at
|
|
105
|
+
* `repoDir`, enforcing the grounding constraint per the chosen policy.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolveAnchors(spec: LabSpec, repoDir: string, options?: ResolveAnchorsOptions): ResolveAnchorsResult;
|
|
108
|
+
/**
|
|
109
|
+
* The content an anchor points at, or why it could not be read. The failure
|
|
110
|
+
* kinds mirror the mechanical {@link AnchorStatus} values (symbol matching is
|
|
111
|
+
* a separate concern layered on top by `resolveAnchor`).
|
|
112
|
+
*/
|
|
113
|
+
export type AnchorRegion = {
|
|
114
|
+
kind: 'ok';
|
|
115
|
+
text: string;
|
|
116
|
+
} | {
|
|
117
|
+
kind: 'file-missing';
|
|
118
|
+
detail: string;
|
|
119
|
+
} | {
|
|
120
|
+
kind: 'lines-out-of-range';
|
|
121
|
+
detail: string;
|
|
122
|
+
};
|
|
123
|
+
/** Share one cache across many {@link readAnchorRegion} calls over one repo. */
|
|
124
|
+
export declare function createAnchorFileCache(): AnchorFileCache;
|
|
125
|
+
export type AnchorFileCache = Map<string, LoadedFile>;
|
|
126
|
+
/**
|
|
127
|
+
* Read the actual repository content an anchor points at: the line range if
|
|
128
|
+
* given, otherwise the whole file. Line endings are normalized to `\n`. This
|
|
129
|
+
* is the single anchor-region reader — anchor resolution fingerprints what it
|
|
130
|
+
* returns, and the verify stage shows it to the adversarial verifier — so
|
|
131
|
+
* both stages are guaranteed to be looking at the same bytes.
|
|
132
|
+
*/
|
|
133
|
+
export declare function readAnchorRegion(anchor: Pick<Anchor, 'file' | 'lines'>, repoDir: string, cache?: AnchorFileCache): AnchorRegion;
|
|
134
|
+
export type LoadedFile = {
|
|
135
|
+
kind: 'ok';
|
|
136
|
+
lines: string[];
|
|
137
|
+
lineCount: number;
|
|
138
|
+
} | {
|
|
139
|
+
kind: 'missing';
|
|
140
|
+
detail: string;
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=resolveAnchors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveAnchors.d.ts","sourceRoot":"","sources":["../src/resolveAnchors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,uDAAuD;AACvD,MAAM,MAAM,YAAY,GACpB,UAAU,GACV,cAAc,GACd,oBAAoB,GACpB,kBAAkB,CAAC;AAEvB;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/C,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAE7D,gEAAgE;AAChE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,UAAU,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAChC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,2CAA2C;IAC3C,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,sBAAsB;IACtB,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CA6CtB;AAuED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,gFAAgF;AAChF,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED,MAAM,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEtD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EACtC,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,eAAyC,GAC/C,YAAY,CAmBd;AAMD,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, resolve, sep } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Resolve every base-content anchor in `spec` against the repository at
|
|
6
|
+
* `repoDir`, enforcing the grounding constraint per the chosen policy.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveAnchors(spec, repoDir, options = {}) {
|
|
9
|
+
const policy = options.policy ?? 'drop';
|
|
10
|
+
const repoRoot = resolve(repoDir);
|
|
11
|
+
const fileCache = new Map();
|
|
12
|
+
const units = [];
|
|
13
|
+
const allAnchors = [];
|
|
14
|
+
for (const module of spec.base.modules) {
|
|
15
|
+
for (const widget of module.widgets) {
|
|
16
|
+
const anchors = collectAnchorRefs(widget).map((ref, anchorIndex) => resolveAnchor(ref.anchor, {
|
|
17
|
+
repoRoot,
|
|
18
|
+
fileCache,
|
|
19
|
+
moduleId: module.id,
|
|
20
|
+
widgetId: widget.id,
|
|
21
|
+
anchorIndex,
|
|
22
|
+
anchorPath: ref.path,
|
|
23
|
+
}));
|
|
24
|
+
const unitResolved = anchors.every((a) => a.status === 'resolved');
|
|
25
|
+
units.push({
|
|
26
|
+
moduleId: module.id,
|
|
27
|
+
widgetId: widget.id,
|
|
28
|
+
outcome: unitResolved ? 'resolved' : policy === 'drop' ? 'dropped' : 'flagged',
|
|
29
|
+
anchors,
|
|
30
|
+
});
|
|
31
|
+
allAnchors.push(...anchors);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const report = {
|
|
35
|
+
policy,
|
|
36
|
+
anchors: allAnchors,
|
|
37
|
+
units,
|
|
38
|
+
summary: {
|
|
39
|
+
totalAnchors: allAnchors.length,
|
|
40
|
+
resolvedAnchors: allAnchors.filter((a) => a.status === 'resolved').length,
|
|
41
|
+
totalUnits: units.length,
|
|
42
|
+
resolvedUnits: units.filter((u) => u.outcome === 'resolved').length,
|
|
43
|
+
unresolvedUnits: units.filter((u) => u.outcome !== 'resolved').length,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
return { spec: buildOutputSpec(spec, report), report };
|
|
47
|
+
}
|
|
48
|
+
function resolveAnchor(anchor, ctx) {
|
|
49
|
+
const base = {
|
|
50
|
+
moduleId: ctx.moduleId,
|
|
51
|
+
widgetId: ctx.widgetId,
|
|
52
|
+
anchorIndex: ctx.anchorIndex,
|
|
53
|
+
anchorPath: ctx.anchorPath,
|
|
54
|
+
};
|
|
55
|
+
const fail = (status, detail) => ({
|
|
56
|
+
...base,
|
|
57
|
+
anchor: cloneAnchor(anchor),
|
|
58
|
+
status,
|
|
59
|
+
detail,
|
|
60
|
+
});
|
|
61
|
+
const loaded = readAnchorRegion(anchor, ctx.repoRoot, ctx.fileCache);
|
|
62
|
+
if (loaded.kind !== 'ok') {
|
|
63
|
+
return fail(loaded.kind, loaded.detail);
|
|
64
|
+
}
|
|
65
|
+
const region = loaded.text;
|
|
66
|
+
if (anchor.symbol !== undefined && !symbolPattern(anchor.symbol).test(region)) {
|
|
67
|
+
const where = anchor.lines
|
|
68
|
+
? `lines ${anchor.lines.start}-${anchor.lines.end} of "${anchor.file}"`
|
|
69
|
+
: `"${anchor.file}"`;
|
|
70
|
+
return fail('symbol-not-found', `Symbol "${anchor.symbol}" not found in ${where}.`);
|
|
71
|
+
}
|
|
72
|
+
const fingerprint = `sha256:${createHash('sha256').update(region, 'utf8').digest('hex')}`;
|
|
73
|
+
const resolvedAnchor = cloneAnchor(anchor);
|
|
74
|
+
resolvedAnchor.fingerprint = fingerprint;
|
|
75
|
+
return {
|
|
76
|
+
...base,
|
|
77
|
+
anchor: resolvedAnchor,
|
|
78
|
+
status: 'resolved',
|
|
79
|
+
detail: anchor.lines
|
|
80
|
+
? `Resolved lines ${anchor.lines.start}-${anchor.lines.end} of "${anchor.file}".`
|
|
81
|
+
: `Resolved "${anchor.file}".`,
|
|
82
|
+
fingerprint,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Textual, language-agnostic symbol matching: the symbol must appear in the
|
|
87
|
+
* region as a whole token — not embedded inside a longer identifier (so
|
|
88
|
+
* "greet" never matches "greeting"). Symbols containing punctuation (Ruby's
|
|
89
|
+
* `empty?`, operators, etc.) are matched literally.
|
|
90
|
+
*/
|
|
91
|
+
function symbolPattern(symbol) {
|
|
92
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
93
|
+
return new RegExp(`(?<![A-Za-z0-9_$])${escaped}(?![A-Za-z0-9_$])`);
|
|
94
|
+
}
|
|
95
|
+
/** Share one cache across many {@link readAnchorRegion} calls over one repo. */
|
|
96
|
+
export function createAnchorFileCache() {
|
|
97
|
+
return new Map();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Read the actual repository content an anchor points at: the line range if
|
|
101
|
+
* given, otherwise the whole file. Line endings are normalized to `\n`. This
|
|
102
|
+
* is the single anchor-region reader — anchor resolution fingerprints what it
|
|
103
|
+
* returns, and the verify stage shows it to the adversarial verifier — so
|
|
104
|
+
* both stages are guaranteed to be looking at the same bytes.
|
|
105
|
+
*/
|
|
106
|
+
export function readAnchorRegion(anchor, repoDir, cache = createAnchorFileCache()) {
|
|
107
|
+
const repoRoot = resolve(repoDir);
|
|
108
|
+
const file = loadFile(anchor.file, repoRoot, cache);
|
|
109
|
+
if (file.kind === 'missing') {
|
|
110
|
+
return { kind: 'file-missing', detail: file.detail };
|
|
111
|
+
}
|
|
112
|
+
let regionLines = file.lines;
|
|
113
|
+
if (anchor.lines) {
|
|
114
|
+
const { start, end } = anchor.lines;
|
|
115
|
+
if (end > file.lineCount || start > file.lineCount) {
|
|
116
|
+
return {
|
|
117
|
+
kind: 'lines-out-of-range',
|
|
118
|
+
detail: `Line range ${start}-${end} exceeds "${anchor.file}" (${file.lineCount} line${file.lineCount === 1 ? '' : 's'}).`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
regionLines = file.lines.slice(start - 1, end);
|
|
122
|
+
}
|
|
123
|
+
return { kind: 'ok', text: regionLines.join('\n') };
|
|
124
|
+
}
|
|
125
|
+
function loadFile(relPath, repoRoot, cache) {
|
|
126
|
+
const cached = cache.get(relPath);
|
|
127
|
+
if (cached)
|
|
128
|
+
return cached;
|
|
129
|
+
const loaded = loadFileUncached(relPath, repoRoot);
|
|
130
|
+
cache.set(relPath, loaded);
|
|
131
|
+
return loaded;
|
|
132
|
+
}
|
|
133
|
+
function loadFileUncached(relPath, repoRoot) {
|
|
134
|
+
if (isAbsolute(relPath)) {
|
|
135
|
+
return { kind: 'missing', detail: `Anchor file "${relPath}" must be repo-relative, not absolute.` };
|
|
136
|
+
}
|
|
137
|
+
const absPath = resolve(repoRoot, relPath);
|
|
138
|
+
if (absPath !== repoRoot && !absPath.startsWith(repoRoot + sep)) {
|
|
139
|
+
return { kind: 'missing', detail: `Anchor file "${relPath}" escapes the repository root.` };
|
|
140
|
+
}
|
|
141
|
+
let raw;
|
|
142
|
+
try {
|
|
143
|
+
const stat = statSync(absPath);
|
|
144
|
+
if (!stat.isFile()) {
|
|
145
|
+
return { kind: 'missing', detail: `Anchor path "${relPath}" is not a regular file.` };
|
|
146
|
+
}
|
|
147
|
+
raw = readFileSync(absPath, 'utf8');
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { kind: 'missing', detail: `Anchor file "${relPath}" does not exist in the repository.` };
|
|
151
|
+
}
|
|
152
|
+
// Normalize line endings so line counts and fingerprints are identical
|
|
153
|
+
// across CRLF/LF checkouts of the same content.
|
|
154
|
+
const normalized = raw.replace(/\r\n?/g, '\n');
|
|
155
|
+
const lines = normalized.split('\n');
|
|
156
|
+
// A trailing newline does not start an extra line ("a\n" is one line).
|
|
157
|
+
const lineCount = lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
|
|
158
|
+
return { kind: 'ok', lines, lineCount };
|
|
159
|
+
}
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
// Output spec construction
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
/**
|
|
164
|
+
* Build the returned spec without mutating the input: fingerprints stamped
|
|
165
|
+
* onto resolved anchors, and (under `drop`) unresolved units filtered out of
|
|
166
|
+
* their modules. Module and overlay structure is otherwise preserved.
|
|
167
|
+
*/
|
|
168
|
+
function buildOutputSpec(spec, report) {
|
|
169
|
+
const out = structuredClone(spec);
|
|
170
|
+
const outcomeByUnit = new Map();
|
|
171
|
+
for (const unit of report.units) {
|
|
172
|
+
outcomeByUnit.set(unitKey(unit.moduleId, unit.widgetId), unit.outcome);
|
|
173
|
+
}
|
|
174
|
+
const fingerprintByAnchor = new Map();
|
|
175
|
+
for (const res of report.anchors) {
|
|
176
|
+
if (res.fingerprint !== undefined) {
|
|
177
|
+
fingerprintByAnchor.set(anchorKey(res.moduleId, res.widgetId, res.anchorIndex), res.fingerprint);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const module of out.base.modules) {
|
|
181
|
+
if (report.policy === 'drop') {
|
|
182
|
+
module.widgets = module.widgets.filter((widget) => outcomeByUnit.get(unitKey(module.id, widget.id)) !== 'dropped');
|
|
183
|
+
}
|
|
184
|
+
for (const widget of module.widgets) {
|
|
185
|
+
// Same deterministic walk as during resolution, so ordinals line up.
|
|
186
|
+
collectAnchorRefs(widget).forEach((ref, index) => {
|
|
187
|
+
const fingerprint = fingerprintByAnchor.get(anchorKey(module.id, widget.id, index));
|
|
188
|
+
if (fingerprint !== undefined) {
|
|
189
|
+
ref.anchor.fingerprint = fingerprint;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function unitKey(moduleId, widgetId) {
|
|
197
|
+
return `${moduleId}${widgetId}`;
|
|
198
|
+
}
|
|
199
|
+
function anchorKey(moduleId, widgetId, anchorIndex) {
|
|
200
|
+
return `${unitKey(moduleId, widgetId)}${anchorIndex}`;
|
|
201
|
+
}
|
|
202
|
+
function cloneAnchor(anchor) {
|
|
203
|
+
return structuredClone(anchor);
|
|
204
|
+
}
|
|
205
|
+
function isAnchorLike(value) {
|
|
206
|
+
return (typeof value === 'object' &&
|
|
207
|
+
value !== null &&
|
|
208
|
+
typeof value.file === 'string');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Collect every anchor a widget carries, wherever it lives: top-level
|
|
212
|
+
* `anchors` arrays (callouts), nested carriers (system-map nodes/edges,
|
|
213
|
+
* data-model annotations at any depth), and single-anchor `source` fields.
|
|
214
|
+
* Structural rather than per-widget-type, so new widget shapes are covered
|
|
215
|
+
* automatically as long as they keep the schema's `anchors`/`source` naming.
|
|
216
|
+
* Depth-first in spec order — deterministic for a given spec.
|
|
217
|
+
*/
|
|
218
|
+
function collectAnchorRefs(value, path = [], out = []) {
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
value.forEach((item, index) => collectAnchorRefs(item, [...path, index], out));
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
if (value === null || typeof value !== 'object') {
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
for (const [key, child] of Object.entries(value)) {
|
|
227
|
+
if (key === 'anchors' && Array.isArray(child)) {
|
|
228
|
+
child.forEach((anchor, index) => {
|
|
229
|
+
if (isAnchorLike(anchor))
|
|
230
|
+
out.push({ anchor, path: [...path, key, index] });
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
else if (key === 'source' && isAnchorLike(child)) {
|
|
234
|
+
out.push({ anchor: child, path: [...path, key] });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
collectAnchorRefs(child, [...path, key], out);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=resolveAnchors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolveAnchors.js","sourceRoot":"","sources":["../src/resolveAnchors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type Anchor, type LabModule, type LabSpec } from '@ramplab/spec';
|
|
2
|
+
import { type ModelRunner } from './pipeline.js';
|
|
3
|
+
/**
|
|
4
|
+
* The verify stage (PLAN.md §3–§4): adversarial grounding enforcement.
|
|
5
|
+
*
|
|
6
|
+
* Anchor resolution proves a claim *points at real code*; this stage proves
|
|
7
|
+
* the claim is *true of that code*. An independent verifier agent receives
|
|
8
|
+
* each machine-generated claim together with the ACTUAL content of its
|
|
9
|
+
* anchored regions (read via the same `readAnchorRegion` that resolution
|
|
10
|
+
* fingerprints, so verifier and resolver see identical bytes) and tries to
|
|
11
|
+
* REFUTE it. Per-claim verdicts: `verified` | `refuted` | `unverifiable`,
|
|
12
|
+
* each with a reason. Anything not verified is dropped from the base spec —
|
|
13
|
+
* the overlay (human tribal knowledge) is never touched.
|
|
14
|
+
*
|
|
15
|
+
* **Batching (deliberate):** one verifier call per module, covering every
|
|
16
|
+
* claim in that module's widgets. Per-claim calls would cost one agent
|
|
17
|
+
* invocation per sentence of the lab; per-module batches keep the call count
|
|
18
|
+
* at the same order as the author fan-out (which produced the claims in the
|
|
19
|
+
* first place), keep each prompt's code context focused on one subsystem's
|
|
20
|
+
* files, and still yield strictly per-claim verdicts because the output
|
|
21
|
+
* contract demands exactly one verdict per listed claim id. Batches run in
|
|
22
|
+
* parallel under the same worker-pool/concurrency cap as authoring.
|
|
23
|
+
*
|
|
24
|
+
* **Drop semantics:** the unit of removal is the whole teachable widget —
|
|
25
|
+
* consistent with `UnitResolution` granularity in anchor resolution — with
|
|
26
|
+
* one refinement: a quiz drops only the failed question, and the quiz widget
|
|
27
|
+
* itself only when no questions remain. A module emptied by drops is removed
|
|
28
|
+
* (the spec schema demands ≥1 widget-bearing structure stays parseable) and
|
|
29
|
+
* recorded in the report.
|
|
30
|
+
*
|
|
31
|
+
* **Failure policy (consistent with the author stage):** a batch whose
|
|
32
|
+
* verifier output is still invalid after bounded retries fails the whole
|
|
33
|
+
* stage with an error naming the module — verification is a grounding
|
|
34
|
+
* guarantee, so there is no silent-skip mode. Other in-flight batches finish
|
|
35
|
+
* first so all failures are reported at once.
|
|
36
|
+
*/
|
|
37
|
+
/** Default cap on concurrent verifier agents. */
|
|
38
|
+
export declare const DEFAULT_VERIFY_CONCURRENCY = 4;
|
|
39
|
+
/** The three per-claim verdicts. Only `verified` keeps a claim in the lab. */
|
|
40
|
+
export type ClaimVerdictValue = 'verified' | 'refuted' | 'unverifiable';
|
|
41
|
+
/** One machine-generated claim, extracted from a base widget. */
|
|
42
|
+
export interface VerificationClaim {
|
|
43
|
+
moduleId: string;
|
|
44
|
+
widgetId: string;
|
|
45
|
+
/**
|
|
46
|
+
* Stable claim id, unique within its module batch:
|
|
47
|
+
* `<widgetId>#<part>` (e.g. `greet-walkthrough#step-1`,
|
|
48
|
+
* `greetings-quiz#question-q-greet-return`).
|
|
49
|
+
*/
|
|
50
|
+
claimId: string;
|
|
51
|
+
/** For quiz claims: the question id, so drops can target the question. */
|
|
52
|
+
questionId?: string;
|
|
53
|
+
/** The claim text shown to the verifier. */
|
|
54
|
+
claim: string;
|
|
55
|
+
/** The anchors grounding the claim, as they appear in the spec. */
|
|
56
|
+
anchors: Anchor[];
|
|
57
|
+
}
|
|
58
|
+
/** A claim plus its verdict — one row of the verification report. */
|
|
59
|
+
export interface ClaimVerdict extends VerificationClaim {
|
|
60
|
+
verdict: ClaimVerdictValue;
|
|
61
|
+
/** The verifier's justification (or the mechanical reason). */
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
export interface VerificationDroppedWidget {
|
|
65
|
+
moduleId: string;
|
|
66
|
+
widgetId: string;
|
|
67
|
+
/** One entry per failed claim: `<claimId>: <verdict> — <reason>`. */
|
|
68
|
+
reasons: string[];
|
|
69
|
+
}
|
|
70
|
+
export interface VerificationDroppedQuestion {
|
|
71
|
+
moduleId: string;
|
|
72
|
+
widgetId: string;
|
|
73
|
+
questionId: string;
|
|
74
|
+
reason: string;
|
|
75
|
+
}
|
|
76
|
+
export interface VerificationDrops {
|
|
77
|
+
/** Whole widgets removed (any failed claim; quizzes only when emptied). */
|
|
78
|
+
widgets: VerificationDroppedWidget[];
|
|
79
|
+
/** Individual quiz questions removed. */
|
|
80
|
+
questions: VerificationDroppedQuestion[];
|
|
81
|
+
/** Modules removed because verification left them without widgets. */
|
|
82
|
+
modules: string[];
|
|
83
|
+
}
|
|
84
|
+
export interface VerificationSummary {
|
|
85
|
+
totalClaims: number;
|
|
86
|
+
verified: number;
|
|
87
|
+
refuted: number;
|
|
88
|
+
unverifiable: number;
|
|
89
|
+
/** `verified / totalClaims`; 1 when there were no claims to check. */
|
|
90
|
+
passRate: number;
|
|
91
|
+
}
|
|
92
|
+
/** Structured report of a full verification pass over a spec's base content. */
|
|
93
|
+
export interface VerificationReport {
|
|
94
|
+
/** Every claim checked, in spec order, with its verdict and reason. */
|
|
95
|
+
claims: ClaimVerdict[];
|
|
96
|
+
drops: VerificationDrops;
|
|
97
|
+
summary: VerificationSummary;
|
|
98
|
+
}
|
|
99
|
+
export interface VerifyStageOptions {
|
|
100
|
+
model: string;
|
|
101
|
+
/** The assembled, anchor-resolved spec whose base content is on trial. */
|
|
102
|
+
spec: LabSpec;
|
|
103
|
+
/**
|
|
104
|
+
* Units already flagged by anchor resolution (the `flag` policy keeps
|
|
105
|
+
* them for human review). Verification skips them: their grounding failed
|
|
106
|
+
* mechanically, so there is no anchored code to check the claim against,
|
|
107
|
+
* and dropping them here would defeat the review-flow policy.
|
|
108
|
+
*/
|
|
109
|
+
flaggedUnits?: readonly {
|
|
110
|
+
moduleId: string;
|
|
111
|
+
widgetId: string;
|
|
112
|
+
}[];
|
|
113
|
+
/**
|
|
114
|
+
* How many times to re-prompt a batch after invalid verifier output.
|
|
115
|
+
* @default 1
|
|
116
|
+
*/
|
|
117
|
+
maxRetries?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Max verifier agents in flight at once.
|
|
120
|
+
* @default 4
|
|
121
|
+
*/
|
|
122
|
+
concurrency?: number;
|
|
123
|
+
}
|
|
124
|
+
export interface VerifyStageResult {
|
|
125
|
+
/** The spec with every non-verified unit dropped, revalidated. */
|
|
126
|
+
spec: LabSpec;
|
|
127
|
+
report: VerificationReport;
|
|
128
|
+
/**
|
|
129
|
+
* Verifier attempts per module batch (1 = first output was valid).
|
|
130
|
+
* Modules whose claims never needed a model call are absent.
|
|
131
|
+
*/
|
|
132
|
+
attempts: Record<string, number>;
|
|
133
|
+
/** Summed cost across all batches and attempts, if the runner reports it. */
|
|
134
|
+
costUsd: number | undefined;
|
|
135
|
+
}
|
|
136
|
+
/** One batch's terminal failure inside the verify stage. */
|
|
137
|
+
export interface VerifyBatchFailure {
|
|
138
|
+
moduleId: string;
|
|
139
|
+
attempts: number;
|
|
140
|
+
lastFailure: string;
|
|
141
|
+
}
|
|
142
|
+
/** Raised when at least one module batch never produced valid verdicts. */
|
|
143
|
+
export declare class VerifyStageError extends Error {
|
|
144
|
+
readonly failures: readonly VerifyBatchFailure[];
|
|
145
|
+
constructor(failures: readonly VerifyBatchFailure[]);
|
|
146
|
+
}
|
|
147
|
+
export declare function runVerifyStage(runner: ModelRunner, repoDir: string, options: VerifyStageOptions): Promise<VerifyStageResult>;
|
|
148
|
+
/** Extract every claim carried by a module's widgets, in spec order. */
|
|
149
|
+
export declare function collectModuleClaims(module: LabModule): VerificationClaim[];
|
|
150
|
+
//# sourceMappingURL=verifyStage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifyStage.d.ts","sourceRoot":"","sources":["../src/verifyStage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EAGX,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAsB,KAAK,WAAW,EAAsB,MAAM,eAAe,CAAC;AAGzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,iDAAiD;AACjD,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAE5C,8EAA8E;AAC9E,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;AAExE,iEAAiE;AACjE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAa,SAAQ,iBAAiB;IACrD,OAAO,EAAE,iBAAiB,CAAC;IAC3B,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,OAAO,EAAE,yBAAyB,EAAE,CAAC;IACrC,yCAAyC;IACzC,SAAS,EAAE,2BAA2B,EAAE,CAAC;IACzC,sEAAsE;IACtE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,EAAE,iBAAiB,CAAC;IACzB,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,IAAI,EAAE,OAAO,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjE;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,kEAAkE;IAClE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,kBAAkB,CAAC;IAC3B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6EAA6E;IAC7E,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2EAA2E;AAC3E,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAC;gBAErC,QAAQ,EAAE,SAAS,kBAAkB,EAAE;CAepD;AASD,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CA2D5B;AAMD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,iBAAiB,EAAE,CAM1E"}
|