specrails-core 5.2.3 → 5.4.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/README.md +3 -0
- package/bin/specrails-core.mjs +5 -0
- package/dist/agent-runtime/capabilities.d.ts +27 -0
- package/dist/agent-runtime/capabilities.js +73 -0
- package/dist/agent-runtime/capabilities.js.map +1 -0
- package/dist/agent-runtime/cli-executor.d.ts +43 -0
- package/dist/agent-runtime/cli-executor.js +405 -0
- package/dist/agent-runtime/cli-executor.js.map +1 -0
- package/dist/agent-runtime/cli-process.d.ts +32 -0
- package/dist/agent-runtime/cli-process.js +159 -0
- package/dist/agent-runtime/cli-process.js.map +1 -0
- package/dist/agent-runtime/cli.d.ts +39 -0
- package/dist/agent-runtime/cli.js +216 -0
- package/dist/agent-runtime/cli.js.map +1 -0
- package/dist/agent-runtime/codex-schema.d.ts +4 -0
- package/dist/agent-runtime/codex-schema.js +31 -0
- package/dist/agent-runtime/codex-schema.js.map +1 -0
- package/dist/agent-runtime/config.d.ts +11 -0
- package/dist/agent-runtime/config.js +263 -0
- package/dist/agent-runtime/config.js.map +1 -0
- package/dist/agent-runtime/core-host.d.ts +41 -0
- package/dist/agent-runtime/core-host.js +132 -0
- package/dist/agent-runtime/core-host.js.map +1 -0
- package/dist/agent-runtime/durable-store.d.ts +22 -0
- package/dist/agent-runtime/durable-store.js +205 -0
- package/dist/agent-runtime/durable-store.js.map +1 -0
- package/dist/agent-runtime/efficiency-summary.d.ts +67 -0
- package/dist/agent-runtime/efficiency-summary.js +52 -0
- package/dist/agent-runtime/efficiency-summary.js.map +1 -0
- package/dist/agent-runtime/efficiency-types.d.ts +60 -0
- package/dist/agent-runtime/efficiency-types.js +8 -0
- package/dist/agent-runtime/efficiency-types.js.map +1 -0
- package/dist/agent-runtime/efficiency.d.ts +5 -0
- package/dist/agent-runtime/efficiency.js +35 -0
- package/dist/agent-runtime/efficiency.js.map +1 -0
- package/dist/agent-runtime/evaluation-corpus.d.ts +13 -0
- package/dist/agent-runtime/evaluation-corpus.js +8 -0
- package/dist/agent-runtime/evaluation-corpus.js.map +1 -0
- package/dist/agent-runtime/evaluation.d.ts +43 -0
- package/dist/agent-runtime/evaluation.js +171 -0
- package/dist/agent-runtime/evaluation.js.map +1 -0
- package/dist/agent-runtime/executor-types.d.ts +125 -0
- package/dist/agent-runtime/executor-types.js +29 -0
- package/dist/agent-runtime/executor-types.js.map +1 -0
- package/dist/agent-runtime/executors.d.ts +23 -0
- package/dist/agent-runtime/executors.js +48 -0
- package/dist/agent-runtime/executors.js.map +1 -0
- package/dist/agent-runtime/gemini-policy.d.ts +8 -0
- package/dist/agent-runtime/gemini-policy.js +36 -0
- package/dist/agent-runtime/gemini-policy.js.map +1 -0
- package/dist/agent-runtime/graph/artifacts.d.ts +26 -0
- package/dist/agent-runtime/graph/artifacts.js +205 -0
- package/dist/agent-runtime/graph/artifacts.js.map +1 -0
- package/dist/agent-runtime/graph/nodes.d.ts +39 -0
- package/dist/agent-runtime/graph/nodes.js +321 -0
- package/dist/agent-runtime/graph/nodes.js.map +1 -0
- package/dist/agent-runtime/graph/review-policy.d.ts +18 -0
- package/dist/agent-runtime/graph/review-policy.js +32 -0
- package/dist/agent-runtime/graph/review-policy.js.map +1 -0
- package/dist/agent-runtime/graph/roles.d.ts +47 -0
- package/dist/agent-runtime/graph/roles.js +190 -0
- package/dist/agent-runtime/graph/roles.js.map +1 -0
- package/dist/agent-runtime/graph/state.d.ts +83 -0
- package/dist/agent-runtime/graph/state.js +23 -0
- package/dist/agent-runtime/graph/state.js.map +1 -0
- package/dist/agent-runtime/graph-checkpointer.d.ts +61 -0
- package/dist/agent-runtime/graph-checkpointer.js +137 -0
- package/dist/agent-runtime/graph-checkpointer.js.map +1 -0
- package/dist/agent-runtime/index.d.ts +20 -0
- package/dist/agent-runtime/index.js +21 -0
- package/dist/agent-runtime/index.js.map +1 -0
- package/dist/agent-runtime/kimi-acp.d.ts +11 -0
- package/dist/agent-runtime/kimi-acp.js +190 -0
- package/dist/agent-runtime/kimi-acp.js.map +1 -0
- package/dist/agent-runtime/openai-executor.d.ts +26 -0
- package/dist/agent-runtime/openai-executor.js +192 -0
- package/dist/agent-runtime/openai-executor.js.map +1 -0
- package/dist/agent-runtime/openspec-tool-server.d.ts +1 -0
- package/dist/agent-runtime/openspec-tool-server.js +37 -0
- package/dist/agent-runtime/openspec-tool-server.js.map +1 -0
- package/dist/agent-runtime/openspec.d.ts +123 -0
- package/dist/agent-runtime/openspec.js +263 -0
- package/dist/agent-runtime/openspec.js.map +1 -0
- package/dist/agent-runtime/prompts.d.ts +44 -0
- package/dist/agent-runtime/prompts.js +310 -0
- package/dist/agent-runtime/prompts.js.map +1 -0
- package/dist/agent-runtime/provider-diagnostic.d.ts +2 -0
- package/dist/agent-runtime/provider-diagnostic.js +25 -0
- package/dist/agent-runtime/provider-diagnostic.js.map +1 -0
- package/dist/agent-runtime/repository-context.d.ts +25 -0
- package/dist/agent-runtime/repository-context.js +100 -0
- package/dist/agent-runtime/repository-context.js.map +1 -0
- package/dist/agent-runtime/review-context.d.ts +14 -0
- package/dist/agent-runtime/review-context.js +41 -0
- package/dist/agent-runtime/review-context.js.map +1 -0
- package/dist/agent-runtime/role-routing.d.ts +10 -0
- package/dist/agent-runtime/role-routing.js +29 -0
- package/dist/agent-runtime/role-routing.js.map +1 -0
- package/dist/agent-runtime/role-state.d.ts +19 -0
- package/dist/agent-runtime/role-state.js +24 -0
- package/dist/agent-runtime/role-state.js.map +1 -0
- package/dist/agent-runtime/runtime-identity.d.ts +11 -0
- package/dist/agent-runtime/runtime-identity.js +33 -0
- package/dist/agent-runtime/runtime-identity.js.map +1 -0
- package/dist/agent-runtime/tool-event.d.ts +3 -0
- package/dist/agent-runtime/tool-event.js +24 -0
- package/dist/agent-runtime/tool-event.js.map +1 -0
- package/dist/agent-runtime/verification-plan.d.ts +55 -0
- package/dist/agent-runtime/verification-plan.js +206 -0
- package/dist/agent-runtime/verification-plan.js.map +1 -0
- package/dist/agent-runtime/workflow-types.d.ts +226 -0
- package/dist/agent-runtime/workflow-types.js +2 -0
- package/dist/agent-runtime/workflow-types.js.map +1 -0
- package/dist/agent-runtime/workflow.d.ts +17 -0
- package/dist/agent-runtime/workflow.js +636 -0
- package/dist/agent-runtime/workflow.js.map +1 -0
- package/dist/agent-runtime/workspace-tools.d.ts +29 -0
- package/dist/agent-runtime/workspace-tools.js +282 -0
- package/dist/agent-runtime/workspace-tools.js.map +1 -0
- package/dist/installer/cli.d.ts +35 -0
- package/dist/installer/cli.js +3 -0
- package/dist/installer/cli.js.map +1 -1
- package/dist/installer/commands/doctor.d.ts +27 -0
- package/dist/installer/commands/framework.d.ts +85 -0
- package/dist/installer/commands/init.d.ts +147 -0
- package/dist/installer/commands/update.d.ts +56 -0
- package/dist/installer/commands/v5-migration.d.ts +32 -0
- package/dist/installer/phases/framework-lifecycle.d.ts +53 -0
- package/dist/installer/phases/install-config.d.ts +64 -0
- package/dist/installer/phases/manifest.d.ts +45 -0
- package/dist/installer/phases/prereqs.d.ts +51 -0
- package/dist/installer/phases/provider-detect.d.ts +89 -0
- package/dist/installer/phases/scaffold.d.ts +211 -0
- package/dist/installer/phases/scaffold.js +39 -109
- package/dist/installer/phases/scaffold.js.map +1 -1
- package/dist/installer/runtime/kimi.d.ts +84 -0
- package/dist/installer/runtime/pipeline-state.d.ts +351 -0
- package/dist/installer/runtime/pipeline-state.js +514 -41
- package/dist/installer/runtime/pipeline-state.js.map +1 -1
- package/dist/installer/util/errors.d.ts +46 -0
- package/dist/installer/util/exec.d.ts +41 -0
- package/dist/installer/util/fs.d.ts +153 -0
- package/dist/installer/util/git.d.ts +44 -0
- package/dist/installer/util/install-transaction.d.ts +29 -0
- package/dist/installer/util/logger.d.ts +31 -0
- package/dist/installer/util/paths.d.ts +34 -0
- package/dist/installer/util/prompts.d.ts +23 -0
- package/dist/installer/util/registry.d.ts +174 -0
- package/dist/installer/util/template.d.ts +23 -0
- package/docs/README.md +1 -0
- package/docs/agent-runtime-efficiency.md +65 -0
- package/docs/agent-runtime.md +339 -0
- package/integration-contract.json +80 -7
- package/package.json +20 -2
- package/schemas/agent-runtime.schema.json +77 -0
- package/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
- package/templates/codex-skills/batch-implement/SKILL.md +33 -58
- package/templates/codex-skills/implement/SKILL.md +21 -124
- package/templates/codex-skills/retry/SKILL.md +8 -34
- package/templates/commands/specrails/batch-implement.md +21 -16
- package/templates/commands/specrails/implement.md +17 -276
- package/templates/commands/specrails/retry.md +6 -34
- package/templates/gemini-commands/batch-implement.toml +34 -28
- package/templates/gemini-commands/implement.toml +34 -55
- package/templates/gemini-commands/retry.toml +10 -16
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { artifactPath, hash, resolveOpenSpecCli, runOpenSpec } from '../openspec.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { checkArchive, pipelineStateDirectory, transitionPipeline, validateVerificationRequest, } from '../../installer/runtime/pipeline-state.js';
|
|
6
|
+
export const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
7
|
+
export function object(value) {
|
|
8
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
9
|
+
throw new Error('Expected a structured JSON object from agent');
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/** Accepts the object wherever the model put it: bare, fenced, or surrounded by commentary. */
|
|
13
|
+
export function parseAgentObject(text) {
|
|
14
|
+
if (text.length > 2_000_000)
|
|
15
|
+
throw new Error('Structured agent response is too large');
|
|
16
|
+
const trimmed = text.trim();
|
|
17
|
+
const candidates = [trimmed, trimmed.replace(/^```(?:json)?\s*\n([\s\S]*?)\n```$/, '$1')];
|
|
18
|
+
const fenced = /```(?:json)?\s*\n([\s\S]*?)\n```/.exec(trimmed);
|
|
19
|
+
if (fenced)
|
|
20
|
+
candidates.push(fenced[1]);
|
|
21
|
+
const first = trimmed.indexOf('{'), last = trimmed.lastIndexOf('}');
|
|
22
|
+
if (first >= 0 && last > first)
|
|
23
|
+
candidates.push(trimmed.slice(first, last + 1));
|
|
24
|
+
let failure;
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
try {
|
|
27
|
+
return object(JSON.parse(candidate));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
failure = error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw failure instanceof Error ? failure : new Error('Expected a structured JSON object from agent');
|
|
34
|
+
}
|
|
35
|
+
/** All generated paths are relative and reject symlink ancestors, including
|
|
36
|
+
* already-existing archive/spec directories. */
|
|
37
|
+
export function child(root, relative) {
|
|
38
|
+
const target = path.resolve(root, relative);
|
|
39
|
+
const rel = path.relative(root, target);
|
|
40
|
+
if (!rel || rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel))
|
|
41
|
+
throw new Error('Artifact path escapes repository');
|
|
42
|
+
let cursor = root;
|
|
43
|
+
for (const part of rel.split(path.sep)) {
|
|
44
|
+
cursor = path.join(cursor, part);
|
|
45
|
+
if (existsSync(cursor) && lstatSync(cursor).isSymbolicLink())
|
|
46
|
+
throw new Error('Refusing symlink artifact path: ' + relative);
|
|
47
|
+
}
|
|
48
|
+
return target;
|
|
49
|
+
}
|
|
50
|
+
export function write(file, content) {
|
|
51
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
52
|
+
const temp = file + '.' + randomUUID() + '.tmp';
|
|
53
|
+
try {
|
|
54
|
+
writeFileSync(temp, content, { flag: 'wx', mode: 0o600 });
|
|
55
|
+
renameSync(temp, file);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
rmSync(temp, { force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function journal(context) {
|
|
62
|
+
return JSON.parse(readFileSync(path.join(pipelineStateDirectory(context), 'state.json'), 'utf8'));
|
|
63
|
+
}
|
|
64
|
+
export function parseArchitecture(raw) {
|
|
65
|
+
if (!['high', 'medium', 'low'].includes(String(raw.confidence)))
|
|
66
|
+
throw new Error('Invalid design confidence');
|
|
67
|
+
if (raw.question !== undefined && (typeof raw.question !== 'string' || raw.question.length > 4000))
|
|
68
|
+
throw new Error('Invalid architect question');
|
|
69
|
+
if (raw.planningDepth !== undefined && raw.planningDepth !== 'focused' && raw.planningDepth !== 'full')
|
|
70
|
+
throw new Error('Invalid planning depth');
|
|
71
|
+
if (raw.planningReason !== undefined && (typeof raw.planningReason !== 'string' || !raw.planningReason.trim() || raw.planningReason.length > 2000))
|
|
72
|
+
throw new Error('Invalid planning reason');
|
|
73
|
+
const references = (key) => {
|
|
74
|
+
const value = raw[key];
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return [];
|
|
77
|
+
if (!Array.isArray(value) || value.length > 20 || value.some(item => typeof item !== 'string' || !item.trim() || item.length > 1000))
|
|
78
|
+
throw new Error('Invalid architect ' + key);
|
|
79
|
+
return value;
|
|
80
|
+
};
|
|
81
|
+
const referencePatterns = references('referencePatterns'), riskFlags = references('riskFlags');
|
|
82
|
+
return { confidence: raw.confidence, ...(typeof raw.question === 'string' && raw.question.trim() ? { question: raw.question.trim() } : {}), verification: raw.verification,
|
|
83
|
+
planningDepth: raw.planningDepth === 'focused' && referencePatterns.length > 0 && riskFlags.length === 0 ? 'focused' : 'full',
|
|
84
|
+
...(typeof raw.planningReason === 'string' ? { planningReason: raw.planningReason } : {}), referencePatterns, riskFlags, };
|
|
85
|
+
}
|
|
86
|
+
export function writeDesignConfidence(context, change, architecture, options = {}) {
|
|
87
|
+
// A question may precede change creation. Do not fabricate a change directory in that case.
|
|
88
|
+
if (!existsSync(child(context.artifactRoot, 'openspec/changes/' + change + '/.openspec.yaml')))
|
|
89
|
+
return;
|
|
90
|
+
const confidence = options.assumed && architecture.confidence === 'low'
|
|
91
|
+
? { confidence: 'medium', assumed: true, reportedConfidence: 'low', question: architecture.question }
|
|
92
|
+
: { confidence: architecture.confidence, question: architecture.question };
|
|
93
|
+
write(child(context.artifactRoot, 'openspec/changes/' + change + '/design-confidence.json'), JSON.stringify(confidence, null, 2) + '\n');
|
|
94
|
+
}
|
|
95
|
+
/** Commands the architect proposed for repositories the configuration leaves uncovered. */
|
|
96
|
+
export function proposedVerification(context, configured, verification) {
|
|
97
|
+
if (verification === undefined || verification === null)
|
|
98
|
+
return [];
|
|
99
|
+
if (!Array.isArray(verification) || verification.length > 100)
|
|
100
|
+
throw new Error('Invalid proposed verification commands');
|
|
101
|
+
const covered = new Set(configured.map(command => command.repositoryId));
|
|
102
|
+
const proposed = verification.map(item => {
|
|
103
|
+
const command = object(item);
|
|
104
|
+
if (typeof command.repositoryId !== 'string' || typeof command.command !== 'string' || !command.command.trim() || !Array.isArray(command.args) || !command.args.every(arg => typeof arg === 'string'))
|
|
105
|
+
throw new Error('Invalid proposed verification command');
|
|
106
|
+
return { repositoryId: command.repositoryId, command: command.command, args: command.args, ...(typeof command.cwd === 'string' && command.cwd ? { cwd: command.cwd } : {}) };
|
|
107
|
+
}).filter(command => !covered.has(command.repositoryId) && context.repositories.some(repository => repository.id === command.repositoryId));
|
|
108
|
+
if (proposed.length)
|
|
109
|
+
validateVerificationRequest(context, { kind: 'scoped', commands: proposed });
|
|
110
|
+
return proposed;
|
|
111
|
+
}
|
|
112
|
+
/** Publish an archive prepared by the real CLI. The durable write set makes a
|
|
113
|
+
* crash between main-spec updates recoverable without applying a delta twice. */
|
|
114
|
+
async function archiveWithOpenSpec(context, change, active, signal) {
|
|
115
|
+
const directory = pipelineStateDirectory(context);
|
|
116
|
+
const receipt = path.join(directory, 'openspec-archive.json');
|
|
117
|
+
const files = (root, prefix = '') => {
|
|
118
|
+
if (!existsSync(root))
|
|
119
|
+
return [];
|
|
120
|
+
return readdirSync(root, { withFileTypes: true }).flatMap(entry => {
|
|
121
|
+
const relative = prefix + entry.name;
|
|
122
|
+
if (entry.isSymbolicLink())
|
|
123
|
+
throw new Error('OpenSpec archive refuses symlink trees');
|
|
124
|
+
return entry.isDirectory() ? files(path.join(root, entry.name), relative + '/') : [relative];
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
const activeHash = hash(JSON.stringify(files(active).sort().map(file => [file, readFileSync(artifactPath(active, file), 'utf8')])));
|
|
128
|
+
let plan;
|
|
129
|
+
if (existsSync(receipt))
|
|
130
|
+
plan = JSON.parse(readFileSync(receipt, 'utf8'));
|
|
131
|
+
else {
|
|
132
|
+
const staging = mkdtempSync(path.join(directory, '.openspec-archive-'));
|
|
133
|
+
try {
|
|
134
|
+
const source = artifactPath(context.artifactRoot, 'openspec');
|
|
135
|
+
files(source); // Reject symlinks before copying or invoking the external CLI.
|
|
136
|
+
cpSync(source, path.join(staging, 'openspec'), { recursive: true });
|
|
137
|
+
await runOpenSpec(resolveOpenSpecCli(), staging, ['archive', change, '--yes'], signal);
|
|
138
|
+
const archives = readdirSync(path.join(staging, 'openspec/changes/archive')).filter(name => name.endsWith('-' + change));
|
|
139
|
+
if (archives.length !== 1)
|
|
140
|
+
throw new Error('OpenSpec archive destination is ambiguous');
|
|
141
|
+
const destination = 'openspec/changes/archive/' + archives[0];
|
|
142
|
+
if (existsSync(artifactPath(context.artifactRoot, destination)))
|
|
143
|
+
throw new Error('OpenSpec archive destination already exists');
|
|
144
|
+
const beforeRoot = path.join(source, 'specs'), afterRoot = path.join(staging, 'openspec/specs');
|
|
145
|
+
const names = new Set([...files(beforeRoot), ...files(afterRoot)]);
|
|
146
|
+
const contents = (root, name) => existsSync(path.join(root, name)) ? readFileSync(path.join(root, name), 'utf8') : null;
|
|
147
|
+
plan = { activeHash, destination, writes: [...names].map(name => ({ path: 'openspec/specs/' + name, before: contents(beforeRoot, name), after: contents(afterRoot, name) })).filter(item => item.before !== item.after) };
|
|
148
|
+
write(receipt, JSON.stringify(plan));
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
rmSync(staging, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (plan.activeHash !== activeHash)
|
|
155
|
+
throw new Error('Reviewed artifacts changed since the OpenSpec archive was prepared');
|
|
156
|
+
if (!new RegExp('^openspec/changes/archive/[0-9]{4}-[0-9]{2}-[0-9]{2}-' + change + '$').test(plan.destination))
|
|
157
|
+
throw new Error('Invalid OpenSpec archive receipt');
|
|
158
|
+
const targets = plan.writes.map(item => {
|
|
159
|
+
if (!item.path.startsWith('openspec/specs/'))
|
|
160
|
+
throw new Error('Invalid OpenSpec archive write');
|
|
161
|
+
const target = artifactPath(context.artifactRoot, item.path);
|
|
162
|
+
const current = existsSync(target) ? readFileSync(target, 'utf8') : null;
|
|
163
|
+
if (current !== item.before && current !== item.after)
|
|
164
|
+
throw new Error('Main specification changed during archive; refusing to overwrite: ' + item.path);
|
|
165
|
+
return { ...item, target };
|
|
166
|
+
});
|
|
167
|
+
// The external CLI ran asynchronously; recheck the candidate and artifacts before publishing.
|
|
168
|
+
signal?.throwIfAborted();
|
|
169
|
+
checkArchive(context);
|
|
170
|
+
// Validate every path and preimage before publishing the first output.
|
|
171
|
+
const destination = artifactPath(context.artifactRoot, plan.destination);
|
|
172
|
+
if (existsSync(destination))
|
|
173
|
+
throw new Error('Archive destination already exists');
|
|
174
|
+
for (const item of targets) {
|
|
175
|
+
if (item.after === null)
|
|
176
|
+
rmSync(item.target, { force: true });
|
|
177
|
+
else
|
|
178
|
+
write(item.target, item.after);
|
|
179
|
+
}
|
|
180
|
+
mkdirSync(path.dirname(destination), { recursive: true });
|
|
181
|
+
renameSync(active, destination);
|
|
182
|
+
}
|
|
183
|
+
export async function archive(context, change, signal) {
|
|
184
|
+
const state = journal(context);
|
|
185
|
+
if (state.phases.archive.status !== 'done') {
|
|
186
|
+
const active = artifactPath(context.artifactRoot, 'openspec/changes/' + change);
|
|
187
|
+
if (existsSync(active)) {
|
|
188
|
+
checkArchive(context);
|
|
189
|
+
transitionPipeline(context, 'archive', 'running');
|
|
190
|
+
await archiveWithOpenSpec(context, change, active, signal);
|
|
191
|
+
}
|
|
192
|
+
// If the process died after rename, Core rechecks the saved archive approval.
|
|
193
|
+
transitionPipeline(context, 'archive', 'done');
|
|
194
|
+
}
|
|
195
|
+
// A crash can also occur after Core's archive receipt but before these host
|
|
196
|
+
// ownership markers. Reconcile them when repeating the interrupted step.
|
|
197
|
+
if (context.ownership.git === 'host') {
|
|
198
|
+
const current = journal(context);
|
|
199
|
+
if (current.phases.ship.status !== 'skipped')
|
|
200
|
+
transitionPipeline(context, 'ship', 'skipped');
|
|
201
|
+
if (current.phases.ci.status !== 'skipped')
|
|
202
|
+
transitionPipeline(context, 'ci', 'skipped');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../src/agent-runtime/graph/artifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type OpenSpecRoleContext } from '../openspec.js';
|
|
2
|
+
import { type AcceptanceReport, type PipelineContext } from '../../installer/runtime/pipeline-state.js';
|
|
3
|
+
import type { AgentResult, AgentRole, RuntimeConfig } from '../executor-types.js';
|
|
4
|
+
import { type FrozenCriterion } from '../prompts.js';
|
|
5
|
+
import type { WorkflowNode, WorkflowState } from '../workflow-types.js';
|
|
6
|
+
import { type ReviewPolicy } from './review-policy.js';
|
|
7
|
+
import type { RoleInvoker } from './roles.js';
|
|
8
|
+
import type { CoreNodeId, CoreStateType, DeveloperRecord, VerificationRecord } from './state.js';
|
|
9
|
+
/** Autonomous investigation passes before a low-confidence design asks or proceeds. */
|
|
10
|
+
export declare const MAX_DEEPEN_PASSES = 1;
|
|
11
|
+
export interface CoreNodeDeps {
|
|
12
|
+
archiveApproved?: () => boolean;
|
|
13
|
+
context: PipelineContext;
|
|
14
|
+
config: RuntimeConfig;
|
|
15
|
+
openspec: Record<AgentRole, OpenSpecRoleContext>;
|
|
16
|
+
change: string;
|
|
17
|
+
/** Development visits allowed per invocation, including correction cycles. */
|
|
18
|
+
attempts: number;
|
|
19
|
+
policy: ReviewPolicy;
|
|
20
|
+
invoke: RoleInvoker;
|
|
21
|
+
/** Host-facing narration of what the runtime did with a role's reply. */
|
|
22
|
+
note(role: AgentRole, text: string): void;
|
|
23
|
+
onVerificationOutput?: (text: string) => void;
|
|
24
|
+
}
|
|
25
|
+
type CoreNode = WorkflowNode<CoreStateType>;
|
|
26
|
+
/** Attempts count from the latest explicit resume: a human continuing a blocked run grants a fresh budget. */
|
|
27
|
+
export declare function developerVisitsSinceResume(checkpoint: WorkflowState): number;
|
|
28
|
+
/** The developer's structured summary, or an honest prose record when the provider returned text. */
|
|
29
|
+
export declare function developerRecord(output: Record<string, unknown> | undefined, text: string, result: AgentResult, provider: string): DeveloperRecord;
|
|
30
|
+
/**
|
|
31
|
+
* The acceptance report Core records for the reviewer: the frozen criteria the
|
|
32
|
+
* reviewer certified by coordinates, the verification commands Core actually ran
|
|
33
|
+
* as required checks, repositories admitted without a check as unavailable ones,
|
|
34
|
+
* and the reviewer's own inspections as supplementary evidence.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildAcceptanceReport(criteria: FrozenCriterion[], verification: VerificationRecord | null, raw: unknown): AcceptanceReport;
|
|
37
|
+
/** The five Core phases as LangGraph nodes. Declaration order defines "downstream" for invalidation. */
|
|
38
|
+
export declare function coreNodes(deps: CoreNodeDeps): Record<CoreNodeId, CoreNode>;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { addDeveloperChecks, bindPlan, expandedPlanCommands, initializeVerificationPlan, readVerificationPlan, validateProposedChecks } from '../verification-plan.js';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { OpenSpecTools } from '../openspec.js';
|
|
4
|
+
import { candidateManifest, fingerprintCandidate, frozenAcceptanceCriteria, recordAcceptance, transitionPipeline, validateAcceptanceReport, verifyPipeline, } from '../../installer/runtime/pipeline-state.js';
|
|
5
|
+
import { ARCHITECT_OUTPUT_SCHEMA, DEVELOPER_OUTPUT_SCHEMA, REVIEW_OUTPUT_SCHEMA, correctionInstructions, deepenInstructions, roleInstructions } from '../prompts.js';
|
|
6
|
+
import { archive, child, journal, object, parseArchitecture, proposedVerification, write, writeDesignConfidence } from './artifacts.js';
|
|
7
|
+
import { evaluateReview } from './review-policy.js';
|
|
8
|
+
import { boundedReviewManifest, reviewChanges } from '../review-context.js';
|
|
9
|
+
/** Autonomous investigation passes before a low-confidence design asks or proceeds. */
|
|
10
|
+
export const MAX_DEEPEN_PASSES = 1;
|
|
11
|
+
const DEVELOPER_SUMMARY_LIMIT = 32_000;
|
|
12
|
+
/** Deterministic steps spend nothing; provider spend is reported by the role invoker as it happens. */
|
|
13
|
+
const NO_SPEND = { costUsd: 0, inputTokens: 0, outputTokens: 0 };
|
|
14
|
+
function failed(error) { return { status: 'failed', error }; }
|
|
15
|
+
function plural(count, noun) { return `${count} ${noun}${count === 1 ? '' : 's'}`; }
|
|
16
|
+
function feedbackFor(state) {
|
|
17
|
+
return { verification: state.verifyResult, review: state.review };
|
|
18
|
+
}
|
|
19
|
+
/** Attempts count from the latest explicit resume: a human continuing a blocked run grants a fresh budget. */
|
|
20
|
+
export function developerVisitsSinceResume(checkpoint) {
|
|
21
|
+
const resumed = [...checkpoint.events].reverse().find(event => event.type === 'workflow_resumed');
|
|
22
|
+
return checkpoint.history.filter(attempt => attempt.stepId === 'developer' && (!resumed || attempt.startedAt >= resumed.timestamp)).length;
|
|
23
|
+
}
|
|
24
|
+
function uncoveredRepositories(context, plan) {
|
|
25
|
+
return context.repositories.filter(repository => !plan.some(command => command.repositoryId === repository.id)).map(repository => repository.id);
|
|
26
|
+
}
|
|
27
|
+
function strings(value, limit) {
|
|
28
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string' && item.trim().length > 0).map(item => item.trim().slice(0, 1000)).slice(0, limit) : [];
|
|
29
|
+
}
|
|
30
|
+
/** The developer's structured summary, or an honest prose record when the provider returned text. */
|
|
31
|
+
export function developerRecord(output, text, result, provider) {
|
|
32
|
+
const base = { provider, ...(result.sessionId ? { sessionId: result.sessionId } : {}) };
|
|
33
|
+
if (!output || typeof output.summary !== 'string' || !output.summary.trim()) {
|
|
34
|
+
return { ...base, summary: text.slice(-DEVELOPER_SUMMARY_LIMIT), files: [], tests: [], incomplete: [], structured: false };
|
|
35
|
+
}
|
|
36
|
+
const incomplete = Array.isArray(output.incomplete) ? output.incomplete.flatMap(item => {
|
|
37
|
+
const entry = item && typeof item === 'object' && !Array.isArray(item) ? item : undefined;
|
|
38
|
+
return entry && typeof entry.task === 'string' && entry.task.trim() ? [{ task: entry.task.trim().slice(0, 1000), reason: typeof entry.reason === 'string' ? entry.reason.trim().slice(0, 2000) : '' }] : [];
|
|
39
|
+
}).slice(0, 200) : [];
|
|
40
|
+
return {
|
|
41
|
+
...base, structured: true,
|
|
42
|
+
summary: output.summary.trim().slice(-DEVELOPER_SUMMARY_LIMIT),
|
|
43
|
+
files: strings(output.files, 500), tests: strings(output.tests, 500),
|
|
44
|
+
...(typeof output.verification === 'string' && output.verification.trim() ? { verification: output.verification.trim().slice(0, 4000) } : {}),
|
|
45
|
+
incomplete,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function architectNode(deps) {
|
|
49
|
+
const { context, config, change, invoke, note } = deps;
|
|
50
|
+
const accept = (output) => {
|
|
51
|
+
const architecture = parseArchitecture(object(output));
|
|
52
|
+
if (config.efficiency?.planning === 'full' || context.repositories.length > 1 || /\b(?:migration|security|authentication|authorization|public contract|public api|migraci[oó]n|seguridad)\b/i.test(context.specs.map(spec => spec.title + ' ' + spec.description).join('\n')))
|
|
53
|
+
architecture.planningDepth = 'full';
|
|
54
|
+
// Malformed proposals are repaired inside the same session, like any other structural defect.
|
|
55
|
+
proposedVerification(context, config.verification, architecture.verification);
|
|
56
|
+
return architecture;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
effect: 'write', ends: ['developer'],
|
|
60
|
+
async run(state, step) {
|
|
61
|
+
transitionPipeline(context, 'architect', 'running');
|
|
62
|
+
// A resumed node collects the requester's answer first, so the pass that
|
|
63
|
+
// asked the question is never repeated.
|
|
64
|
+
const answers = [...state.answers];
|
|
65
|
+
const fresh = [];
|
|
66
|
+
if (step.pending?.kind === 'question') {
|
|
67
|
+
const reply = step.interrupt(step.pending);
|
|
68
|
+
answers.push(reply.answer);
|
|
69
|
+
fresh.push(reply.answer);
|
|
70
|
+
note('architect', 'Continuing the architecture with the answer to the blocking question.');
|
|
71
|
+
}
|
|
72
|
+
const prompt = roleInstructions('architect', context, change, { definition: config.rolePrompts?.architect, verification: config.verification, answers, planning: config.efficiency?.planning });
|
|
73
|
+
const first = await invoke('architect', step, { prompt, structured: true, outputSchema: ARCHITECT_OUTPUT_SCHEMA }, accept);
|
|
74
|
+
if (!first.ok)
|
|
75
|
+
return failed(first.error);
|
|
76
|
+
let architecture = first.value;
|
|
77
|
+
let passes = state.deepenPasses;
|
|
78
|
+
if (architecture.confidence === 'low' && passes < MAX_DEEPEN_PASSES) {
|
|
79
|
+
passes += 1;
|
|
80
|
+
note('architect', `Design confidence is low${architecture.question ? ` (${architecture.question})` : ''}; asking the architect to investigate the code further before deciding.`);
|
|
81
|
+
const second = await invoke('architect', step, { kind: 'deepen', prompt: deepenInstructions(architecture.question), fallbackPrompt: prompt + '\nPrevious response (bounded):\n' + first.text.slice(-32_000) + '\n' + deepenInstructions(architecture.question), structured: true, outputSchema: ARCHITECT_OUTPUT_SCHEMA, resumeSessionId: first.result.sessionId }, accept);
|
|
82
|
+
if (second.ok)
|
|
83
|
+
architecture = second.value;
|
|
84
|
+
else
|
|
85
|
+
note('architect', `The investigation pass could not be used (${second.error}); keeping the first design.`);
|
|
86
|
+
}
|
|
87
|
+
let assumed = false;
|
|
88
|
+
if (architecture.confidence === 'low') {
|
|
89
|
+
const question = architecture.question ?? 'The architect could not choose between several plausible designs; inspect proposal.md and state the intended behavior.';
|
|
90
|
+
if ((config.architect?.onLowConfidence ?? 'ask') === 'ask') {
|
|
91
|
+
// The draft artifacts are written first so the requester can read the
|
|
92
|
+
// proposal while answering; the resumed pass replaces them.
|
|
93
|
+
writeDesignConfidence(context, change, architecture);
|
|
94
|
+
transitionPipeline(context, 'architect', 'blocked', 'Design confidence is low');
|
|
95
|
+
note('architect', 'Design confidence is still low; pausing until the blocking question is answered.');
|
|
96
|
+
step.interrupt({ kind: 'question', question });
|
|
97
|
+
}
|
|
98
|
+
assumed = true;
|
|
99
|
+
note('architect', 'Design confidence is still low; proceeding on the stated assumptions because architect.onLowConfidence is "proceed".');
|
|
100
|
+
}
|
|
101
|
+
const workflow = new OpenSpecTools(deps.openspec.architect, step.signal);
|
|
102
|
+
workflow.assertParticipation();
|
|
103
|
+
const applied = await workflow.assertReady();
|
|
104
|
+
const specs = Object.keys((await workflow.status()).artifactPaths).length ? (applied.contextFiles.specs ?? []) : [];
|
|
105
|
+
const names = (Array.isArray(specs) ? specs : [specs]).map(file => path.basename(path.dirname(file)));
|
|
106
|
+
const proposed = proposedVerification(context, config.verification, architecture.verification);
|
|
107
|
+
writeDesignConfidence(context, change, architecture, { assumed });
|
|
108
|
+
const effective = initializeVerificationPlan(context, config.verification, proposed, config.efficiency?.verification?.maxConcurrency);
|
|
109
|
+
bindPlan(context, effective);
|
|
110
|
+
const plan = expandedPlanCommands(context, effective);
|
|
111
|
+
const uncovered = uncoveredRepositories(context, plan);
|
|
112
|
+
transitionPipeline(context, 'architect', 'done');
|
|
113
|
+
note('architect', `Architecture written: ${plural(applied.progress.total, 'task')}, spec${names.length === 1 ? '' : 's'} ${names.join(', ')}, confidence ${architecture.confidence}.`
|
|
114
|
+
+ (proposed.length ? ` Verification proposed by the architect: ${proposed.map(command => [command.command, ...command.args].join(' ')).join('; ')}.` : '')
|
|
115
|
+
+ (uncovered.length ? ` No verification command for ${uncovered.join(', ')}; the reviewer will inspect that work without automated checks.` : ''));
|
|
116
|
+
const record = {
|
|
117
|
+
change, tasks: applied.progress.total, specs: names, confidence: architecture.confidence,
|
|
118
|
+
planningDepth: architecture.planningDepth, referencePatterns: architecture.referencePatterns, riskFlags: architecture.riskFlags,
|
|
119
|
+
...(architecture.planningReason ? { planningReason: architecture.planningReason } : {}),
|
|
120
|
+
...(architecture.question ? { question: architecture.question } : {}), ...(assumed ? { assumed: true } : {}),
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
status: 'succeeded', next: 'developer',
|
|
124
|
+
update: { plan, unverifiedRepositories: uncovered, architecture: record, answers: fresh, deepenPasses: passes },
|
|
125
|
+
output: { change, verification: plan, unverifiedRepositories: uncovered, confidence: architecture.confidence, assumed, deepenPasses: passes },
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function developerNode(deps) {
|
|
131
|
+
const { context, config, change, invoke, note } = deps;
|
|
132
|
+
return {
|
|
133
|
+
effect: 'write', ends: ['verify'],
|
|
134
|
+
async run(state, step) {
|
|
135
|
+
if (developerVisitsSinceResume(step.checkpoint) > deps.attempts) {
|
|
136
|
+
return { status: 'blocked', error: 'Implementation correction limit reached; inspect the feedback in the log and resume to grant more attempts', usage: NO_SPEND };
|
|
137
|
+
}
|
|
138
|
+
transitionPipeline(context, 'developer', 'running');
|
|
139
|
+
await new OpenSpecTools(deps.openspec.developer, step.signal).assertReady();
|
|
140
|
+
const feedback = feedbackFor(state);
|
|
141
|
+
const provider = config.agents.developer.provider;
|
|
142
|
+
const full = roleInstructions('developer', context, change, { definition: config.rolePrompts?.developer, feedback, verification: state.plan }) + (config.efficiency?.acceptDeveloperChecks !== false ? '\nYou may propose verificationChecks in your JSON summary. These are additive required checks, never replacements for the baseline. Use kind command with structured command/args, or kind harness with entrypoint and 1–8 relative source files (64 KiB each, 256 KiB total). Core persists harnesses outside delivery, appends the absolute entrypoint to argv and supplies SPECRAILS_CHECK_REPO_ROOT. Stable keys revise your own checks; omission retains them. Do not claim a check passed until Core has executed it.' : '');
|
|
143
|
+
const previous = state.development;
|
|
144
|
+
const resumable = previous?.sessionId !== undefined && previous.provider === provider && developerVisitsSinceResume(step.checkpoint) > 1;
|
|
145
|
+
const outcome = await invoke('developer', step, {
|
|
146
|
+
kind: developerVisitsSinceResume(step.checkpoint) > 1 ? 'correction' : 'initial',
|
|
147
|
+
...(resumable
|
|
148
|
+
? { prompt: correctionInstructions('developer', feedback), resumeSessionId: previous.sessionId, fallbackPrompt: full }
|
|
149
|
+
: { prompt: full }),
|
|
150
|
+
structured: true, lenient: true, outputSchema: DEVELOPER_OUTPUT_SCHEMA,
|
|
151
|
+
}, (output, text, result) => {
|
|
152
|
+
if (!output && /"verificationChecks"\s*:/.test(text))
|
|
153
|
+
throw new Error('Malformed developer verificationChecks response');
|
|
154
|
+
const checks = validateProposedChecks(context, output?.verificationChecks);
|
|
155
|
+
if (checks.length && config.efficiency?.acceptDeveloperChecks === false)
|
|
156
|
+
throw new Error('Developer verification proposals are disabled by the frozen policy');
|
|
157
|
+
return { ...developerRecord(output, text, result, provider), ...(checks.length ? { verificationChecks: checks } : {}) };
|
|
158
|
+
});
|
|
159
|
+
if (!outcome.ok)
|
|
160
|
+
return failed(outcome.error);
|
|
161
|
+
new OpenSpecTools(deps.openspec.developer, step.signal).assertParticipation();
|
|
162
|
+
const record = outcome.value;
|
|
163
|
+
const effective = addDeveloperChecks(context, record.verificationChecks ?? []);
|
|
164
|
+
bindPlan(context, effective);
|
|
165
|
+
note('developer', record.structured
|
|
166
|
+
? `Developer finished: ${plural(record.files.length, 'file')} changed, ${plural(record.tests.length, 'test file')} touched${record.incomplete.length ? `, ${plural(record.incomplete.length, 'task')} left incomplete` : ''}.`
|
|
167
|
+
: 'Developer finished without the structured summary; the prose summary is recorded instead.');
|
|
168
|
+
for (const item of record.incomplete)
|
|
169
|
+
note('developer', `Pending task: ${item.task} — ${item.reason}`);
|
|
170
|
+
if (record.structured && record.incomplete.length && record.files.length === 0 && record.tests.length === 0) {
|
|
171
|
+
return { status: 'blocked', error: `Developer could not make progress: ${record.incomplete.map(item => `${item.task}: ${item.reason}`).join('; ')}`, output: record };
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
status: 'succeeded', next: 'verify', update: { development: record, plan: expandedPlanCommands(context, effective) },
|
|
175
|
+
output: { summary: record.summary, provider, ...(record.sessionId ? { sessionId: record.sessionId } : {}), files: record.files, tests: record.tests, incomplete: record.incomplete, structured: record.structured },
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function verifyNode(deps) {
|
|
181
|
+
const { context, note } = deps;
|
|
182
|
+
return {
|
|
183
|
+
effect: 'write', ends: ['reviewer', 'developer'],
|
|
184
|
+
async run(_state, step) {
|
|
185
|
+
// Unchecked tasks are developer feedback, not a workflow failure: the
|
|
186
|
+
// developer sees exactly which tasks remain and continues its session.
|
|
187
|
+
const workflow = new OpenSpecTools(deps.openspec.developer, step.signal);
|
|
188
|
+
const applied = await workflow.assertReady();
|
|
189
|
+
const open = applied.tasks.filter(task => !task.done).map(task => task.description);
|
|
190
|
+
if (open.length) {
|
|
191
|
+
note('developer', `Verification skipped: ${plural(open.length, 'task')} still unchecked in tasks.md; returning to the developer.`);
|
|
192
|
+
const evidence = { valid: false, reason: 'Required implementation tasks remain unchecked in tasks.md', incompleteTasks: open, unverifiedRepositories: [], commands: [] };
|
|
193
|
+
return { status: 'succeeded', next: 'developer', update: { verifyResult: evidence }, output: evidence, usage: NO_SPEND };
|
|
194
|
+
}
|
|
195
|
+
const effective = readVerificationPlan(context);
|
|
196
|
+
if (!effective)
|
|
197
|
+
throw new Error('Verification plan is unavailable');
|
|
198
|
+
const plan = expandedPlanCommands(context, effective);
|
|
199
|
+
const uncovered = uncoveredRepositories(context, plan);
|
|
200
|
+
const receipt = await verifyPipeline(context, { kind: 'full', planHash: effective.planHash, commands: plan, ...(uncovered.length ? { unverified: true } : {}) }, deps.onVerificationOutput, step.signal, { onEvidence: async (kind, payload) => { await step.reportEfficiencyActivity?.(kind, payload); }, maxConcurrency: effective.executionPolicy.maxConcurrency, ...(step.remainingBudget().maxDurationMs === undefined ? {} : { deadline: Date.now() + step.remainingBudget().maxDurationMs }) });
|
|
201
|
+
const evidence = {
|
|
202
|
+
valid: receipt.valid, ...(receipt.reason ? { reason: receipt.reason } : {}), receiptId: receipt.id, unverifiedRepositories: uncovered,
|
|
203
|
+
commands: receipt.commands.map(({ evidenceId, repositoryId, command, args, exitCode, output }) => ({ ...(evidenceId ? { evidenceId } : {}), repositoryId, command, args, exitCode, output: output.slice(-2000) })),
|
|
204
|
+
};
|
|
205
|
+
if (!receipt.valid) {
|
|
206
|
+
note('developer', `Verification failed (${receipt.reason ?? 'a command failed'}); returning to the developer with the exact output.`);
|
|
207
|
+
return { status: 'succeeded', next: 'developer', update: { verifyResult: evidence }, output: evidence, usage: NO_SPEND };
|
|
208
|
+
}
|
|
209
|
+
transitionPipeline(context, 'developer', 'done');
|
|
210
|
+
note('developer', plan.length ? `Verification passed: ${plural(plan.length, 'command')} exited 0.` : 'No verification commands available; relying on task completion and review.');
|
|
211
|
+
return { status: 'succeeded', next: 'reviewer', update: { verifyResult: evidence }, output: evidence, usage: NO_SPEND };
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* The acceptance report Core records for the reviewer: the frozen criteria the
|
|
217
|
+
* reviewer certified by coordinates, the verification commands Core actually ran
|
|
218
|
+
* as required checks, repositories admitted without a check as unavailable ones,
|
|
219
|
+
* and the reviewer's own inspections as supplementary evidence.
|
|
220
|
+
*/
|
|
221
|
+
export function buildAcceptanceReport(criteria, verification, raw) {
|
|
222
|
+
const input = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
|
223
|
+
const rows = Array.isArray(input.criteria) ? input.criteria.map(item => object(item)) : [];
|
|
224
|
+
const certified = criteria.map((item) => {
|
|
225
|
+
const row = rows.find(candidate => String(candidate.specId) === item.specId && candidate.criterionIndex === item.criterionIndex);
|
|
226
|
+
if (!row)
|
|
227
|
+
throw new Error(`Invalid acceptance report: criterion ${item.specId}#${item.criterionIndex} was not certified`);
|
|
228
|
+
return { ...item, status: row.status, evidence: row.evidence, ...(row.exception !== undefined ? { exception: row.exception } : {}) };
|
|
229
|
+
});
|
|
230
|
+
const checks = [];
|
|
231
|
+
for (const command of verification?.commands ?? []) {
|
|
232
|
+
checks.push({
|
|
233
|
+
name: `${command.repositoryId}: ${[command.command, ...command.args].join(' ')}`,
|
|
234
|
+
status: command.exitCode === 0 ? 'passed' : 'failed', required: true,
|
|
235
|
+
evidence: [`Verification receipt ${verification?.receiptId ?? 'unknown'}: exit code ${String(command.exitCode)}`],
|
|
236
|
+
scope: `Core ran the repository's own verification command in ${command.repositoryId} after the developer finished.`,
|
|
237
|
+
limitations: 'Proves only what this command exercises; behavior outside its tests, other repositories and manual scenarios are not covered.',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const repositoryId of verification?.unverifiedRepositories ?? []) {
|
|
241
|
+
checks.push({
|
|
242
|
+
name: `automated verification for ${repositoryId}`, status: 'unavailable', required: false,
|
|
243
|
+
evidence: ['No verification command was configured by the host or proposed by the architect for this repository.'],
|
|
244
|
+
scope: 'No automated check ran for this repository.', limitations: 'Acceptance relies on reviewer inspection alone for this repository.',
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
// Reviewer inspections are evidence, never gates: only Core's own subprocess receipts are required checks.
|
|
248
|
+
for (const item of Array.isArray(input.checks) ? input.checks : [])
|
|
249
|
+
checks.push({ ...object(item), required: false });
|
|
250
|
+
return { criteria: certified, checks, findings: (Array.isArray(input.findings) ? input.findings : []) };
|
|
251
|
+
}
|
|
252
|
+
function reviewerNode(deps) {
|
|
253
|
+
const { context, change, invoke, note, policy, config } = deps;
|
|
254
|
+
const criteria = frozenAcceptanceCriteria(context);
|
|
255
|
+
return {
|
|
256
|
+
effect: 'write', ends: ['archive', 'developer'],
|
|
257
|
+
async run(state, step) {
|
|
258
|
+
transitionPipeline(context, 'reviewer', 'running');
|
|
259
|
+
const manifest = boundedReviewManifest(candidateManifest(journal(context)));
|
|
260
|
+
const delta = reviewChanges(state.review?.manifest, manifest);
|
|
261
|
+
const prompt = roleInstructions('reviewer', context, change, { definition: config.rolePrompts?.reviewer, feedback: feedbackFor(state), verification: state.plan, policy, criteria, developer: state.development });
|
|
262
|
+
const incremental = config.efficiency?.reviewMode !== 'full' && delta.mode === 'incremental' && state.review?.sessionId;
|
|
263
|
+
const followup = correctionInstructions('reviewer', feedbackFor(state)) + '\nChanges since YOUR previous reviewed candidate:\n' + JSON.stringify(delta.changes)
|
|
264
|
+
+ '\nRecertify EVERY current acceptance criterion; previous met results are not current evidence:\n' + JSON.stringify(criteria)
|
|
265
|
+
+ '\nCurrent developer handoff:\n' + JSON.stringify(state.development);
|
|
266
|
+
const outcome = await invoke('reviewer', step, { kind: state.review ? 'correction' : 'initial', prompt: incremental ? followup : prompt, ...(incremental ? { resumeSessionId: state.review.sessionId, fallbackPrompt: prompt } : {}), structured: true, outputSchema: REVIEW_OUTPUT_SCHEMA }, output => {
|
|
267
|
+
const raw = object(output);
|
|
268
|
+
const review = evaluateReview(raw, policy);
|
|
269
|
+
const report = buildAcceptanceReport(criteria, state.verifyResult, raw.acceptance);
|
|
270
|
+
try {
|
|
271
|
+
validateAcceptanceReport(context, report);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
throw new Error('Invalid acceptance report: ' + (error instanceof Error ? error.message : String(error)));
|
|
275
|
+
}
|
|
276
|
+
return { ...review, report };
|
|
277
|
+
});
|
|
278
|
+
if (!outcome.ok)
|
|
279
|
+
return failed(outcome.error);
|
|
280
|
+
const workflow = new OpenSpecTools(deps.openspec.reviewer, step.signal);
|
|
281
|
+
workflow.assertParticipation();
|
|
282
|
+
await workflow.assertReady();
|
|
283
|
+
const { record, approved, report } = outcome.value;
|
|
284
|
+
const reviewedRecord = { ...record, manifest, ...(outcome.result.sessionId ? { sessionId: outcome.result.sessionId } : {}) };
|
|
285
|
+
// Acceptance evidence is bound to the exact candidate before the reviewer verdict is recorded.
|
|
286
|
+
recordAcceptance(context, report);
|
|
287
|
+
if (!approved) {
|
|
288
|
+
transitionPipeline(context, 'reviewer', 'blocked', 'Review requests corrections');
|
|
289
|
+
note('reviewer', `Review requested corrections (score ${record.score}, ${plural(record.issues.length, 'issue')}); returning to the developer.`);
|
|
290
|
+
return { status: 'succeeded', next: 'developer', update: { review: reviewedRecord }, output: reviewedRecord };
|
|
291
|
+
}
|
|
292
|
+
write(child(context.artifactRoot, 'openspec/changes/' + change + '/confidence-score.json'), JSON.stringify({ change, overall: record.score, aspects: record.aspects, summary: record.summary }, null, 2) + '\n');
|
|
293
|
+
transitionPipeline(context, 'reviewer', 'done');
|
|
294
|
+
note('reviewer', `Review approved with score ${record.score}: ${record.summary}`);
|
|
295
|
+
const reviewed = { ...reviewedRecord, candidateHash: fingerprintCandidate(journal(context)) };
|
|
296
|
+
return { status: 'succeeded', next: 'archive', update: { review: reviewed }, output: reviewed };
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function archiveNode(deps) {
|
|
301
|
+
const { context, config, change } = deps;
|
|
302
|
+
return {
|
|
303
|
+
effect: 'write', ends: [],
|
|
304
|
+
async run(_state, step) {
|
|
305
|
+
if (journal(context).phases.archive.status !== 'done' && config.approvalBeforeArchive && !deps.archiveApproved?.()) {
|
|
306
|
+
// Pauses until the host grants the approval; a resumed node passes straight
|
|
307
|
+
// through. The pause itself spends nothing, and says so.
|
|
308
|
+
step.reportUsage(NO_SPEND);
|
|
309
|
+
step.interrupt({ kind: 'approval', reason: 'Approve archive after inspecting the verified implementation' });
|
|
310
|
+
}
|
|
311
|
+
await archive(context, change, step.signal);
|
|
312
|
+
const record = { archivePath: journal(context).archivePath, deliveryOwner: context.ownership.git };
|
|
313
|
+
return { status: 'succeeded', next: null, update: { archived: record }, output: record, usage: NO_SPEND };
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** The five Core phases as LangGraph nodes. Declaration order defines "downstream" for invalidation. */
|
|
318
|
+
export function coreNodes(deps) {
|
|
319
|
+
return { architect: architectNode(deps), developer: developerNode(deps), verify: verifyNode(deps), reviewer: reviewerNode(deps), archive: archiveNode(deps) };
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=nodes.js.map
|