specrails-core 6.0.0 → 6.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/README.md +1 -1
- package/dist/agent-runtime/capabilities.js +3 -1
- package/dist/agent-runtime/change-scope.d.ts +63 -0
- package/dist/agent-runtime/change-scope.js +297 -0
- package/dist/agent-runtime/cli-executor.d.ts +3 -0
- package/dist/agent-runtime/cli-executor.js +50 -13
- package/dist/agent-runtime/cli-process.js +6 -2
- package/dist/agent-runtime/cli.d.ts +15 -0
- package/dist/agent-runtime/cli.js +43 -9
- package/dist/agent-runtime/compact/chat-client.js +2 -1
- package/dist/agent-runtime/compact/environment.d.ts +12 -0
- package/dist/agent-runtime/compact/environment.js +31 -1
- package/dist/agent-runtime/compact/test-reachability.js +6 -2
- package/dist/agent-runtime/config.d.ts +4 -1
- package/dist/agent-runtime/config.js +41 -3
- package/dist/agent-runtime/core-host.d.ts +10 -4
- package/dist/agent-runtime/core-host.js +22 -19
- package/dist/agent-runtime/efficiency-summary.d.ts +4 -1
- package/dist/agent-runtime/efficiency-summary.js +30 -27
- package/dist/agent-runtime/efficiency-types.d.ts +4 -4
- package/dist/agent-runtime/efficiency.d.ts +1 -1
- package/dist/agent-runtime/efficiency.js +7 -6
- package/dist/agent-runtime/engine/__fixtures__/implementation-component.json +90 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.d.ts +11 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.js +60 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation.json +43 -0
- package/dist/agent-runtime/engine/budget.d.ts +9 -0
- package/dist/agent-runtime/engine/budget.js +33 -0
- package/dist/agent-runtime/engine/canonical-json.d.ts +9 -0
- package/dist/agent-runtime/engine/canonical-json.js +162 -0
- package/dist/agent-runtime/engine/checkpoint/database.d.ts +67 -0
- package/dist/agent-runtime/engine/checkpoint/database.js +304 -0
- package/dist/agent-runtime/engine/checkpoint/lease.d.ts +17 -0
- package/dist/agent-runtime/engine/checkpoint/lease.js +82 -0
- package/dist/agent-runtime/engine/checkpoint/ledger.d.ts +133 -0
- package/dist/agent-runtime/engine/checkpoint/ledger.js +510 -0
- package/dist/agent-runtime/engine/checkpoint/saver.d.ts +25 -0
- package/dist/agent-runtime/engine/checkpoint/saver.js +146 -0
- package/dist/agent-runtime/engine/cli.d.ts +10 -0
- package/dist/agent-runtime/engine/cli.js +172 -0
- package/dist/agent-runtime/engine/compiler.d.ts +23 -0
- package/dist/agent-runtime/engine/compiler.js +261 -0
- package/dist/agent-runtime/engine/composition.d.ts +16 -0
- package/dist/agent-runtime/engine/composition.js +28 -0
- package/dist/agent-runtime/engine/concurrency.d.ts +14 -0
- package/dist/agent-runtime/engine/concurrency.js +55 -0
- package/dist/agent-runtime/engine/contracts.d.ts +273 -0
- package/dist/agent-runtime/engine/contracts.js +11 -0
- package/dist/agent-runtime/engine/definition-schema.d.ts +250 -0
- package/dist/agent-runtime/engine/definition-schema.js +313 -0
- package/dist/agent-runtime/engine/definition-types.d.ts +84 -0
- package/dist/agent-runtime/engine/definition-types.js +3 -0
- package/dist/agent-runtime/engine/definition-validator.d.ts +7 -0
- package/dist/agent-runtime/engine/definition-validator.js +187 -0
- package/dist/agent-runtime/engine/efficiency-summary.d.ts +62 -0
- package/dist/agent-runtime/engine/efficiency-summary.js +31 -0
- package/dist/agent-runtime/engine/events.d.ts +19 -0
- package/dist/agent-runtime/engine/events.js +88 -0
- package/dist/agent-runtime/engine/execution.d.ts +29 -0
- package/dist/agent-runtime/engine/execution.js +121 -0
- package/dist/agent-runtime/engine/expressions.d.ts +11 -0
- package/dist/agent-runtime/engine/expressions.js +172 -0
- package/dist/agent-runtime/engine/fork.d.ts +28 -0
- package/dist/agent-runtime/engine/fork.js +211 -0
- package/dist/agent-runtime/engine/graph-description.d.ts +48 -0
- package/dist/agent-runtime/engine/graph-description.js +16 -0
- package/dist/agent-runtime/engine/implementation-compiler.d.ts +214 -0
- package/dist/agent-runtime/engine/implementation-compiler.js +91 -0
- package/dist/agent-runtime/engine/index.d.ts +14 -0
- package/dist/agent-runtime/engine/index.js +14 -0
- package/dist/agent-runtime/engine/invocation-context.d.ts +11 -0
- package/dist/agent-runtime/engine/invocation-context.js +128 -0
- package/dist/agent-runtime/engine/otel.d.ts +15 -0
- package/dist/agent-runtime/engine/otel.js +132 -0
- package/dist/agent-runtime/engine/output.d.ts +4 -0
- package/dist/agent-runtime/engine/output.js +73 -0
- package/dist/agent-runtime/engine/piece-dependencies.d.ts +50 -0
- package/dist/agent-runtime/engine/piece-dependencies.js +193 -0
- package/dist/agent-runtime/engine/piece-registry.d.ts +19 -0
- package/dist/agent-runtime/engine/piece-registry.js +62 -0
- package/dist/agent-runtime/engine/pieces/assign.d.ts +4 -0
- package/dist/agent-runtime/engine/pieces/assign.js +35 -0
- package/dist/agent-runtime/engine/pieces/control.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/control.js +49 -0
- package/dist/agent-runtime/engine/pieces/decider.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/decider.js +70 -0
- package/dist/agent-runtime/engine/pieces/implementation-binding.d.ts +12 -0
- package/dist/agent-runtime/engine/pieces/implementation-binding.js +40 -0
- package/dist/agent-runtime/engine/pieces/implementation-journal.d.ts +25 -0
- package/dist/agent-runtime/engine/pieces/implementation-journal.js +192 -0
- package/dist/agent-runtime/engine/pieces/implementation.d.ts +23 -0
- package/dist/agent-runtime/engine/pieces/implementation.js +176 -0
- package/dist/agent-runtime/engine/pieces/index.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/index.js +22 -0
- package/dist/agent-runtime/engine/pieces/openspec.d.ts +3 -0
- package/dist/agent-runtime/engine/pieces/openspec.js +69 -0
- package/dist/agent-runtime/engine/pieces/ports.d.ts +48 -0
- package/dist/agent-runtime/engine/pieces/ports.js +1 -0
- package/dist/agent-runtime/engine/pieces/project-memory.d.ts +3 -0
- package/dist/agent-runtime/engine/pieces/project-memory.js +13 -0
- package/dist/agent-runtime/engine/pieces/prompt.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/prompt.js +130 -0
- package/dist/agent-runtime/engine/pieces/role-turn.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/role-turn.js +73 -0
- package/dist/agent-runtime/engine/pieces/shared.d.ts +67 -0
- package/dist/agent-runtime/engine/pieces/shared.js +40 -0
- package/dist/agent-runtime/engine/pieces/shell.d.ts +6 -0
- package/dist/agent-runtime/engine/pieces/shell.js +47 -0
- package/dist/agent-runtime/engine/pieces/verify.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/verify.js +52 -0
- package/dist/agent-runtime/engine/preflight.d.ts +27 -0
- package/dist/agent-runtime/engine/preflight.js +54 -0
- package/dist/agent-runtime/engine/regex.d.ts +3 -0
- package/dist/agent-runtime/engine/regex.js +19 -0
- package/dist/agent-runtime/engine/run-status.d.ts +563 -0
- package/dist/agent-runtime/engine/run-status.js +61 -0
- package/dist/agent-runtime/engine/runs.d.ts +605 -0
- package/dist/agent-runtime/engine/runs.js +247 -0
- package/dist/agent-runtime/engine/state.d.ts +64 -0
- package/dist/agent-runtime/engine/state.js +98 -0
- package/dist/agent-runtime/engine/steering/inbox.d.ts +54 -0
- package/dist/agent-runtime/engine/steering/inbox.js +113 -0
- package/dist/agent-runtime/engine/storage/fork-allocation.d.ts +21 -0
- package/dist/agent-runtime/engine/storage/fork-allocation.js +143 -0
- package/dist/agent-runtime/engine/storage/private-path.d.ts +11 -0
- package/dist/agent-runtime/engine/storage/private-path.js +74 -0
- package/dist/agent-runtime/engine/store/sqlite-store.d.ts +26 -0
- package/dist/agent-runtime/engine/store/sqlite-store.js +227 -0
- package/dist/agent-runtime/evaluation-corpus.d.ts +125 -0
- package/dist/agent-runtime/evaluation-corpus.js +4 -0
- package/dist/agent-runtime/evaluation.d.ts +9 -3
- package/dist/agent-runtime/evaluation.js +50 -8
- package/dist/agent-runtime/executor-types.d.ts +53 -3
- package/dist/agent-runtime/executor-types.js +35 -2
- package/dist/agent-runtime/executors.js +2 -2
- package/dist/agent-runtime/graph/artifacts.d.ts +7 -0
- package/dist/agent-runtime/graph/artifacts.js +86 -10
- package/dist/agent-runtime/graph/convergence.d.ts +26 -0
- package/dist/agent-runtime/graph/convergence.js +62 -0
- package/dist/agent-runtime/graph/nodes.d.ts +2 -0
- package/dist/agent-runtime/graph/nodes.js +171 -24
- package/dist/agent-runtime/graph/roles.d.ts +6 -0
- package/dist/agent-runtime/graph/roles.js +23 -17
- package/dist/agent-runtime/graph/state.d.ts +5 -0
- package/dist/agent-runtime/graph/state.js +2 -0
- package/dist/agent-runtime/kimi-acp.js +6 -6
- package/dist/agent-runtime/openai-executor.js +17 -12
- package/dist/agent-runtime/openspec.d.ts +17 -2
- package/dist/agent-runtime/openspec.js +54 -20
- package/dist/agent-runtime/prompts.d.ts +11 -5
- package/dist/agent-runtime/prompts.js +71 -20
- package/dist/agent-runtime/recovery.d.ts +1 -1
- package/dist/agent-runtime/repository-context.js +20 -1
- package/dist/agent-runtime/role-routing.js +4 -2
- package/dist/agent-runtime/role-state.d.ts +5 -0
- package/dist/agent-runtime/verification-plan.d.ts +2 -0
- package/dist/agent-runtime/verification-plan.js +18 -3
- package/dist/agent-runtime/workflow-types.d.ts +2 -0
- package/dist/agent-runtime/workflow.d.ts +3 -1
- package/dist/agent-runtime/workflow.js +7 -3
- package/dist/agent-runtime/workspace-tools.d.ts +2 -2
- package/dist/agent-runtime/workspace-tools.js +6 -6
- package/dist/installer/cli.js +5 -0
- package/dist/installer/phases/scaffold.d.ts +2 -2
- package/dist/installer/phases/scaffold.js +3 -7
- package/dist/pipeline/pipeline-state.d.ts +49 -4
- package/dist/pipeline/pipeline-state.js +150 -23
- package/integration-contract.json +84 -12
- package/package.json +12 -5
- package/schemas/agent-runtime.schema.json +93 -1
- package/schemas/workflow-definition.schema.json +312 -0
- package/templates/kimi/specrails/run-skill.mjs +15 -1
- package/templates/commands/specrails/batch-implement.md +0 -32
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isDefinitionCommand, NODE_KINDS_VERSION, runDefinitionCommand } from './engine/cli.js';
|
|
2
|
+
import { validationPieceRegistry } from './engine/pieces/index.js';
|
|
1
3
|
import { GUARDRAIL_CATALOG } from './guardrails.js';
|
|
2
4
|
import { runEvaluation } from './evaluation.js';
|
|
3
5
|
import { efficiencySummary } from './efficiency-summary.js';
|
|
@@ -15,6 +17,15 @@ import { runtimeEfficiency } from './efficiency.js';
|
|
|
15
17
|
import { configuredCapabilities } from './capabilities.js';
|
|
16
18
|
import { createExecutorRegistry } from './executors.js';
|
|
17
19
|
import { runRecovery } from './recovery.js';
|
|
20
|
+
import { EngineError } from './engine/contracts.js';
|
|
21
|
+
/** Both runtime entry points emit the same machine-readable fatal result. */
|
|
22
|
+
export function runtimeFailure(error) {
|
|
23
|
+
return { type: 'runtime-result', status: 'failed', error: error instanceof EngineError
|
|
24
|
+
? { code: error.code, message: error.message, ...(error.details === undefined ? {} : { details: error.details }) }
|
|
25
|
+
: error instanceof Error ? error.message : String(error) };
|
|
26
|
+
}
|
|
27
|
+
/** Machine operations are mirrored in integration-contract.json; help is presentation-only. */
|
|
28
|
+
export const RUNTIME_CLI_OPERATIONS = ['api', 'validate', 'run', 'status', 'resume', 'prompts', 'capabilities', 'evidence', 'recovery', 'evaluate', 'workflows', 'fork', 'signal', 'cancel', 'help'];
|
|
18
29
|
function read(file) { return JSON.parse(readFileSync(file, 'utf8')); }
|
|
19
30
|
async function readStdin() {
|
|
20
31
|
const chunks = [];
|
|
@@ -87,8 +98,15 @@ function invocationUsage(state, priorCount) {
|
|
|
87
98
|
? null : attempts.reduce((total, a) => total + (a.usage?.[key] ?? 0), 0);
|
|
88
99
|
return { costUsd: sum('costUsd'), inputTokens: sum('inputTokens'), outputTokens: sum('outputTokens') };
|
|
89
100
|
}
|
|
101
|
+
/** Engines this package can launch: the legacy built-in pipeline (1) and workflow definitions (2).
|
|
102
|
+
* Core 7 advertises [2] so hosts refuse unconverted legacy loops before admission. */
|
|
103
|
+
export const RUNTIME_ENGINES = [1, 2];
|
|
90
104
|
export async function runRuntimeCommand(flags, positionals, emit = value => process.stdout.write(JSON.stringify(value) + '\n')) {
|
|
91
105
|
const command = flags.help === true ? 'help' : positionals[0] ?? 'help';
|
|
106
|
+
if (isDefinitionCommand(flags, command))
|
|
107
|
+
return runDefinitionCommand(flags, positionals, emit);
|
|
108
|
+
if (!RUNTIME_CLI_OPERATIONS.some(operation => operation === command))
|
|
109
|
+
throw new Error('Unknown runtime operation: ' + command);
|
|
92
110
|
if (command === 'prompts') {
|
|
93
111
|
emit({ type: 'runtime-role-prompts', defaults: rolePromptDefaults() });
|
|
94
112
|
return 0;
|
|
@@ -105,28 +123,42 @@ export async function runRuntimeCommand(flags, positionals, emit = value => proc
|
|
|
105
123
|
if (command === 'evaluate') {
|
|
106
124
|
if (flags.real !== undefined && flags.real !== true)
|
|
107
125
|
throw new Error('--real is a boolean flag');
|
|
108
|
-
const report = await runEvaluation({ output: stringFlag(flags, 'output'), real: flags.real === true,
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
const report = await runEvaluation({ output: stringFlag(flags, 'output'), real: flags.real === true,
|
|
127
|
+
...(flags.definition === undefined ? {} : { definition: readFileSync(stringFlag(flags, 'definition')) }),
|
|
128
|
+
...(flags.config === undefined ? {} : { config: validateRuntimeConfig(read(stringFlag(flags, 'config'))) }), ...(flags['max-cost-usd'] === undefined ? {} : { maxCostUsd: Number(stringFlag(flags, 'max-cost-usd')) }), ...(flags.repetitions === undefined ? {} : { repetitions: Number(stringFlag(flags, 'repetitions')) }) });
|
|
129
|
+
emit({ type: 'runtime-evaluation', mode: report.mode, observations: report.observations.length, output: stringFlag(flags, 'output'),
|
|
130
|
+
isDefinitionEvaluation: report.isDefinitionEvaluation, allCasesAccepted: report.allCasesAccepted, acceptedCases: report.acceptedCases,
|
|
131
|
+
monetaryConclusion: report.monetaryConclusion, correctionPromptTargetMet: report.correctionPromptTargetMet, noObservedQualityDrop: report.noObservedQualityDrop, noExtraInvocations: report.noExtraInvocations, stopReason: report.stopReason });
|
|
132
|
+
return report.stopReason || !report.allCasesAccepted || (!flags.real && (!report.noObservedQualityDrop || !report.noExtraInvocations || !report.correctionPromptTargetMet)) ? 1 : 0;
|
|
111
133
|
}
|
|
112
134
|
if (command === 'help') {
|
|
113
135
|
emit({ usage: [
|
|
114
136
|
'specrails-core runtime api',
|
|
115
|
-
'specrails-core runtime evaluate --output <directory> [--repetitions <1..20>] [--real --config <json> --max-cost-usd <limit>]',
|
|
137
|
+
'specrails-core runtime evaluate --output <directory> [--definition <json>] [--repetitions <1..20>] [--real --config <json> --max-cost-usd <limit>]',
|
|
116
138
|
'specrails-core runtime capabilities --config <json>',
|
|
117
139
|
'specrails-core runtime evidence --context <json> [--id <opaqueId>] [--section summary|stdout|stderr|source] [--source-id <opaqueId>] [--cursor <cursor>] [--limit <1..100>]',
|
|
118
140
|
'specrails-core runtime prompts',
|
|
119
141
|
'specrails-core runtime validate --config <json>',
|
|
120
142
|
'specrails-core runtime validate --stdin',
|
|
121
143
|
'specrails-core runtime run --context <json> --config <json> --change <kebab-case>',
|
|
144
|
+
'specrails-core runtime run --context <json> --config <json> --definition <json> [--change <kebab-case>]',
|
|
145
|
+
'specrails-core runtime workflows list',
|
|
146
|
+
'specrails-core runtime workflows validate --stdin [--config <json> | --structural]',
|
|
122
147
|
'specrails-core runtime status --context <json> [--compact]',
|
|
123
148
|
'specrails-core runtime resume --context <json> [--approve archive] [--answer <text>] [--recover developer] [--invalidate verify]',
|
|
124
149
|
'specrails-core runtime recovery --context <json> --stdin',
|
|
150
|
+
'specrails-core runtime fork --context <json> --from <nodePath> --run-id <newRunId> [--scope-id <scope>] [--visit <number>] [--state <json>]',
|
|
151
|
+
'specrails-core runtime signal --context <json> --stdin [--request-id <id>]',
|
|
152
|
+
'specrails-core runtime cancel --context <json> [--request-id <id>]',
|
|
125
153
|
] });
|
|
126
154
|
return 0;
|
|
127
155
|
}
|
|
128
156
|
if (command === 'api') {
|
|
129
|
-
|
|
157
|
+
// Engine v2 fields come from the same validation registry that admits definitions; test-only pieces are never registered there.
|
|
158
|
+
emit({ type: 'runtime-api', apiVersion: RUNTIME_API_VERSION, coreVersion: CORE_PACKAGE_VERSION, runtimeIdentity: coreRuntimeIdentity(), workflowVersions: [CORE_WORKFLOW_VERSION],
|
|
159
|
+
engineVersion: 2, engines: RUNTIME_ENGINES, nodeKindsVersion: NODE_KINDS_VERSION, nodeKinds: validationPieceRegistry().kinds(),
|
|
160
|
+
capabilities: { openRoles: 1, scopedRecovery: 1, efficientRoleExecution: 1, reproducibleVerification: 1, implementationEfficiencyMetrics: 1, compactAgentLoop: 1, configurableGuardrails: 1, compactOutputBudget: 1, roleThinkingControl: 1, repositoryScope: 1,
|
|
161
|
+
engineV2: 1, workflowDefinitions: 1, fanOut: 1, fork: 1, forkIdempotency: 1, steeringInbox: 1 }, guardrails: GUARDRAIL_CATALOG });
|
|
130
162
|
return 0;
|
|
131
163
|
}
|
|
132
164
|
if (command === 'validate') {
|
|
@@ -163,8 +195,6 @@ export async function runRuntimeCommand(flags, positionals, emit = value => proc
|
|
|
163
195
|
process.off('SIGTERM', abort);
|
|
164
196
|
}
|
|
165
197
|
}
|
|
166
|
-
if (!['run', 'status', 'resume'].includes(command))
|
|
167
|
-
throw new Error('Unknown runtime operation: ' + command);
|
|
168
198
|
const context = validatePipelineContext(read(stringFlag(flags, 'context')));
|
|
169
199
|
const directory = path.join(pipelineStateDirectory(context), 'agent-workflow');
|
|
170
200
|
const previous = await readWorkflowState(directory, context.runId);
|
|
@@ -178,6 +208,8 @@ export async function runRuntimeCommand(flags, positionals, emit = value => proc
|
|
|
178
208
|
const requestFile = path.join(pipelineStateDirectory(context), 'agent-runtime-request.json');
|
|
179
209
|
let request;
|
|
180
210
|
if (command === 'resume') {
|
|
211
|
+
if (flags.definition !== undefined)
|
|
212
|
+
throw new EngineError('invalid_arguments', 'Resume uses the frozen definition; start a new run to change it');
|
|
181
213
|
if (!previous)
|
|
182
214
|
throw new Error('No programmatic run exists for this context');
|
|
183
215
|
request = read(requestFile);
|
|
@@ -189,7 +221,9 @@ export async function runRuntimeCommand(flags, positionals, emit = value => proc
|
|
|
189
221
|
else {
|
|
190
222
|
if (flags.answer !== undefined || flags.approve !== undefined)
|
|
191
223
|
throw new Error('Answers and approvals apply to runtime resume');
|
|
192
|
-
|
|
224
|
+
// The built-in workflow has no published definition hash; its resume identity is the checkpoint's workflowFingerprint.
|
|
225
|
+
request = { change: stringFlag(flags, 'change'), config: normalizeRuntimeConfig(read(stringFlag(flags, 'config'))), runtimeIdentity: coreRuntimeIdentity(),
|
|
226
|
+
workflow: { id: 'specrails-implementation', version: CORE_WORKFLOW_VERSION, source: 'builtin', definitionHash: null, engine: 1 } };
|
|
193
227
|
await preflightCoreWorkflow({ context, change: request.change, config: request.config });
|
|
194
228
|
const serialized = JSON.stringify(request, null, 2) + '\n';
|
|
195
229
|
mkdirSync(path.dirname(requestFile), { recursive: true, mode: 0o700 });
|
|
@@ -237,7 +271,7 @@ if (process.argv[1] && existsSync(process.argv[1]) && import.meta.url === pathTo
|
|
|
237
271
|
process.exitCode = await runRuntimeCommand(flags, [subcommand ?? 'help', ...positionals]);
|
|
238
272
|
}
|
|
239
273
|
catch (error) {
|
|
240
|
-
process.stdout.write(JSON.stringify(
|
|
274
|
+
process.stdout.write(JSON.stringify(runtimeFailure(error)) + '\n');
|
|
241
275
|
process.exitCode = 1;
|
|
242
276
|
}
|
|
243
277
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
1
2
|
import { AgentExecutionError } from '../executor-types.js';
|
|
2
3
|
import { sumCacheUsage } from '../efficiency-types.js';
|
|
3
4
|
/**
|
|
@@ -49,7 +50,7 @@ export function patientFetchInit() {
|
|
|
49
50
|
}
|
|
50
51
|
const RETRY_DELAY_MS = 3000;
|
|
51
52
|
function sleep(ms, signal) {
|
|
52
|
-
return
|
|
53
|
+
return delay(ms, undefined, { signal });
|
|
53
54
|
}
|
|
54
55
|
export function record(value) {
|
|
55
56
|
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
@@ -5,12 +5,24 @@ interface EnvironmentInstall {
|
|
|
5
5
|
command: string;
|
|
6
6
|
args: string[];
|
|
7
7
|
}
|
|
8
|
+
/** `precondition`: the install failed for a reason only the host can repair (see hostPreconditionFailure). */
|
|
8
9
|
interface InstallOutcome extends EnvironmentInstall {
|
|
9
10
|
ok: boolean;
|
|
10
11
|
detail: string;
|
|
12
|
+
precondition?: string;
|
|
11
13
|
}
|
|
12
14
|
/** True when a verification failure looks like a missing toolchain/dependency rather than a code defect. */
|
|
13
15
|
export declare function isEnvironmentFailure(exitCode: number | null | undefined, output: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Failures that no edit to the change can repair, because the verification
|
|
18
|
+
* process itself lacks something only the host can grant: an environment
|
|
19
|
+
* variable a configuration file references, registry credentials, registry
|
|
20
|
+
* network access, git credentials for a dependency. Feeding them to a fixer
|
|
21
|
+
* makes it edit package-manager configuration to route around the missing
|
|
22
|
+
* secret (observed: `.yarnrc.yml` rewritten, reverted by review, rewritten
|
|
23
|
+
* again). Returns a short host-facing reason, or undefined.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hostPreconditionFailure(output: string): string | undefined;
|
|
14
26
|
/** Packages a failure output names explicitly (`Try \`npm i --save-dev @types/jest\``); the host installs exactly those. */
|
|
15
27
|
export declare function suggestedPackages(output: string): string[];
|
|
16
28
|
/** Dependencies package.json declares that node_modules does not hold (bounded). */
|
|
@@ -19,6 +19,8 @@ const ENVIRONMENT_SIGNATURES = [
|
|
|
19
19
|
/jest: not found|vitest: not found|mocha: not found|tsc: not found|pytest: not found/i,
|
|
20
20
|
/TS2582|Do you need to install type definitions|Try `npm i --save-dev @types\//i,
|
|
21
21
|
/Module (?:[\w@./-]+) in the \w+ option was not found|Preset [\w@./-]+ not found|Cannot find module '(?:ts-jest|babel-jest|@swc\/jest|ts-node|tsx)'/i,
|
|
22
|
+
// Yarn Berry refuses to run scripts in a checkout that was never installed.
|
|
23
|
+
/doesn't seem to have been installed - running an install there might help|Couldn't find the node_modules state file - running an install might help/i,
|
|
22
24
|
];
|
|
23
25
|
/** True when a verification failure looks like a missing toolchain/dependency rather than a code defect. */
|
|
24
26
|
export function isEnvironmentFailure(exitCode, output) {
|
|
@@ -27,6 +29,33 @@ export function isEnvironmentFailure(exitCode, output) {
|
|
|
27
29
|
const tail = output.slice(-6000);
|
|
28
30
|
return ENVIRONMENT_SIGNATURES.some(pattern => pattern.test(tail));
|
|
29
31
|
}
|
|
32
|
+
const REGISTRY = String.raw `(?:registry|npmjs\.org|yarnpkg\.com|npm\.pkg\.github\.com|pkgs\.dev\.azure\.com|jfrog|artifactory|nexus|pypi\.org|crates\.io|proxy\.golang\.org)`;
|
|
33
|
+
/**
|
|
34
|
+
* Failures that no edit to the change can repair, because the verification
|
|
35
|
+
* process itself lacks something only the host can grant: an environment
|
|
36
|
+
* variable a configuration file references, registry credentials, registry
|
|
37
|
+
* network access, git credentials for a dependency. Feeding them to a fixer
|
|
38
|
+
* makes it edit package-manager configuration to route around the missing
|
|
39
|
+
* secret (observed: `.yarnrc.yml` rewritten, reverted by review, rewritten
|
|
40
|
+
* again). Returns a short host-facing reason, or undefined.
|
|
41
|
+
*/
|
|
42
|
+
export function hostPreconditionFailure(output) {
|
|
43
|
+
const text = output.replace(/\u001b\[[0-9;?]*[A-Za-z]/g, '').slice(-16_000);
|
|
44
|
+
const variable = /Environment variable not found \(([A-Za-z_][A-Za-z0-9_]*)\)/.exec(text);
|
|
45
|
+
if (variable)
|
|
46
|
+
return `the environment variable ${variable[1]} is not available to verification commands, and a package-manager configuration file requires it`;
|
|
47
|
+
if (/\bYN0041\b/.test(text) || /YN0035[\s\S]{0,400}?Response Code: 40[13]\b/.test(text) || /Response code 40[13] \((?:Unauthorized|Forbidden)\)/i.test(text)
|
|
48
|
+
|| /npm (?:ERR!|error) code (?:E401|E403|ENEEDAUTH)\b/.test(text) || /ERR_PNPM_FETCH_40[13]\b/.test(text)
|
|
49
|
+
|| new RegExp(String.raw `\b40[13] (?:Unauthorized|Forbidden)\b[^\n]{0,80}https?://[^\s]*${REGISTRY}`, 'i').test(text)
|
|
50
|
+
|| /\b(?:401|403) Client Error: (?:Unauthorized|Forbidden) for url/.test(text))
|
|
51
|
+
return 'the package registry rejected the credentials available to verification commands (HTTP 401/403)';
|
|
52
|
+
if (/npm (?:ERR!|error) code (?:ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ETIMEDOUT|ECONNRESET)\b/.test(text) || /ERR_PNPM_META_FETCH_FAIL\b/.test(text)
|
|
53
|
+
|| new RegExp(String.raw `\b(?:ENOTFOUND|EAI_AGAIN)\b[^\n]{0,200}${REGISTRY}|${REGISTRY}[^\n]{0,200}\b(?:ENOTFOUND|EAI_AGAIN)\b`, 'i').test(text))
|
|
54
|
+
return 'the package registry cannot be reached from the verification environment';
|
|
55
|
+
if (/fatal: could not read (?:Username|Password) for/.test(text) || /Permission denied \(publickey\)/.test(text))
|
|
56
|
+
return 'git credentials needed to fetch a dependency are not available to verification commands';
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
30
59
|
/** Packages a failure output names explicitly (`Try \`npm i --save-dev @types/jest\``); the host installs exactly those. */
|
|
31
60
|
export function suggestedPackages(output) {
|
|
32
61
|
const packages = new Set();
|
|
@@ -155,7 +184,8 @@ export function installEnvironment(roots, io = {}) {
|
|
|
155
184
|
const ok = result.status === 0;
|
|
156
185
|
const detail = ok ? `installed ${plan.ecosystem} dependencies in ${path.basename(root)}` : `${plan.command} ${plan.args.join(' ')} failed in ${path.basename(root)}: ${String(result.stderr || result.error?.message || `exit ${result.status}`).trim().slice(0, 400)}`;
|
|
157
186
|
io.onEvent?.({ kind: 'text', text: `Environment: ${detail}` });
|
|
158
|
-
|
|
187
|
+
const precondition = ok ? undefined : hostPreconditionFailure(`${result.stdout ?? ''}\n${result.stderr ?? ''}`);
|
|
188
|
+
outcomes.push({ ...plan, ok, detail, ...(precondition ? { precondition } : {}) });
|
|
159
189
|
}
|
|
160
190
|
}
|
|
161
191
|
return outcomes;
|
|
@@ -98,7 +98,11 @@ function tokenReaches(token, file, root, discovery) {
|
|
|
98
98
|
* file. Unknown ⇒ true: only an enumerating command can prove absence.
|
|
99
99
|
*/
|
|
100
100
|
export function commandReachesTest(root, command, file) {
|
|
101
|
-
|
|
101
|
+
// Path arguments are relative to the directory the command runs in (a
|
|
102
|
+
// package inside a larger checkout), while reported files are relative to
|
|
103
|
+
// the checkout: compare both from the command's directory.
|
|
104
|
+
const base = path.resolve(root, command.cwd ?? '.');
|
|
105
|
+
const target = posix(path.relative(base, path.resolve(root, file)));
|
|
102
106
|
let judged = false;
|
|
103
107
|
for (const tokens of segments(expandCommandText(root, command))) {
|
|
104
108
|
if (!tokens.length)
|
|
@@ -117,7 +121,7 @@ export function commandReachesTest(root, command, file) {
|
|
|
117
121
|
if (!paths.some(token => TEST_FILE_PATTERN.test(posix(token)) || token.includes('*') || token.endsWith('...')))
|
|
118
122
|
continue;
|
|
119
123
|
judged = true;
|
|
120
|
-
if (paths.some(token => tokenReaches(token, target,
|
|
124
|
+
if (paths.some(token => tokenReaches(token, target, base, discovery)))
|
|
121
125
|
return true;
|
|
122
126
|
}
|
|
123
127
|
return !judged;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type RoleDescriptor, type RuntimeConfig } from './executor-types.js';
|
|
2
2
|
export declare function validateRuntimeConfig(input: unknown, options?: {
|
|
3
3
|
registeredProviderIds?: string[];
|
|
4
4
|
}): RuntimeConfig;
|
|
@@ -9,3 +9,6 @@ export declare function loadRuntimeConfig(file: string, options?: {
|
|
|
9
9
|
export declare function normalizeRuntimeConfig(input: unknown, options?: {
|
|
10
10
|
registeredProviderIds?: string[];
|
|
11
11
|
}): RuntimeConfig;
|
|
12
|
+
/** Built-ins stay ordered and custom roles are declared data, never inferred from a prompt. */
|
|
13
|
+
export declare function roleIds(config: RuntimeConfig): string[];
|
|
14
|
+
export declare function resolveRoleDescriptor(config: RuntimeConfig, id: string): RoleDescriptor;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
2
|
import { validateGuardrailSettings } from './guardrails.js';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { BUILTIN_ROLES, BUILTIN_ROLE_POLICY, ROLE_ID, isBuiltinRole } from './executor-types.js';
|
|
3
5
|
import { DEFAULT_REVIEW_POLICY, REVIEW_ASPECTS } from './graph/review-policy.js';
|
|
4
|
-
const ROLES = [
|
|
6
|
+
const ROLES = [...BUILTIN_ROLES];
|
|
5
7
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
6
8
|
function fail(field, why) { throw new Error(`Invalid runtime config ${field}: ${why}`); }
|
|
7
9
|
function object(value, field) {
|
|
@@ -60,7 +62,7 @@ function score(value, field, floor) {
|
|
|
60
62
|
}
|
|
61
63
|
export function validateRuntimeConfig(input, options = {}) {
|
|
62
64
|
const config = object(input, '$');
|
|
63
|
-
keys(config, ['schemaVersion', 'enabled', 'providers', 'agents', 'fixer', 'limits', 'verification', 'approvalBeforeArchive', 'review', 'architect', 'rolePrompts', 'efficiency', 'guardrails'], '$');
|
|
65
|
+
keys(config, ['schemaVersion', 'enabled', 'providers', 'agents', 'roles', 'fixer', 'limits', 'verification', 'approvalBeforeArchive', 'review', 'architect', 'rolePrompts', 'efficiency', 'guardrails'], '$');
|
|
64
66
|
if (config.schemaVersion !== 1)
|
|
65
67
|
fail('schemaVersion', 'expected 1');
|
|
66
68
|
if (config.enabled !== undefined && typeof config.enabled !== 'boolean')
|
|
@@ -141,6 +143,30 @@ export function validateRuntimeConfig(input, options = {}) {
|
|
|
141
143
|
return structuredClone(agent);
|
|
142
144
|
};
|
|
143
145
|
const agents = Object.fromEntries(ROLES.map(role => [role, validateAgent(rawAgents[role], `agents.${role}`)]));
|
|
146
|
+
let roles;
|
|
147
|
+
if (config.roles !== undefined) {
|
|
148
|
+
const raw = object(config.roles, 'roles');
|
|
149
|
+
roles = {};
|
|
150
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
151
|
+
if (!ROLE_ID.test(id) || id === 'fixer')
|
|
152
|
+
fail(`roles.${id}`, 'expected a role id; fixer is reserved');
|
|
153
|
+
const descriptor = object(value, `roles.${id}`);
|
|
154
|
+
const { access, artifacts, prompt, openspecSkill, ...assignment } = descriptor;
|
|
155
|
+
const selected = validateAgent(assignment, `roles.${id}`);
|
|
156
|
+
choices(access, ['read', 'write'], `roles.${id}.access`);
|
|
157
|
+
choices(artifacts, ['none', 'tasks-checkboxes', 'all'], `roles.${id}.artifacts`);
|
|
158
|
+
if (prompt !== undefined && string(prompt, `roles.${id}.prompt`).length > 20000)
|
|
159
|
+
fail(`roles.${id}.prompt`, 'maximum 20000 characters');
|
|
160
|
+
if (openspecSkill !== undefined)
|
|
161
|
+
choices(openspecSkill, ['openspec-ff-change', 'openspec-apply-change', 'openspec-verify-change'], `roles.${id}.openspecSkill`);
|
|
162
|
+
if (isBuiltinRole(id)) {
|
|
163
|
+
const policy = BUILTIN_ROLE_POLICY[id];
|
|
164
|
+
if (access !== policy.access || artifacts !== policy.artifacts || openspecSkill !== policy.openspecSkill || prompt !== undefined || !isDeepStrictEqual(selected, agents[id]))
|
|
165
|
+
fail(`roles.${id}`, 'built-in descriptors must match their implicit assignment and policy');
|
|
166
|
+
}
|
|
167
|
+
roles[id] = { ...selected, access, artifacts, ...(prompt === undefined ? {} : { prompt }), ...(openspecSkill === undefined ? {} : { openspecSkill }) };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
144
170
|
// The optional fixer: the engine correction rounds run on (verify failure or
|
|
145
171
|
// review rejection); absent ⇒ the developer corrects its own work.
|
|
146
172
|
const fixer = config.fixer === undefined ? undefined : validateAgent(config.fixer, 'fixer');
|
|
@@ -259,7 +285,7 @@ export function validateRuntimeConfig(input, options = {}) {
|
|
|
259
285
|
let rolePrompts;
|
|
260
286
|
if (config.rolePrompts !== undefined) {
|
|
261
287
|
const raw = object(config.rolePrompts, 'rolePrompts');
|
|
262
|
-
keys(raw, [...ROLES, 'fixer'], 'rolePrompts');
|
|
288
|
+
keys(raw, [...ROLES, 'fixer', ...Object.keys(roles ?? {})], 'rolePrompts');
|
|
263
289
|
rolePrompts = {};
|
|
264
290
|
for (const [role, value] of Object.entries(raw)) {
|
|
265
291
|
const text = string(value, `rolePrompts.${role}`);
|
|
@@ -269,6 +295,7 @@ export function validateRuntimeConfig(input, options = {}) {
|
|
|
269
295
|
}
|
|
270
296
|
}
|
|
271
297
|
return { schemaVersion: 1, enabled: true, providers, agents, verification,
|
|
298
|
+
...(roles === undefined ? {} : { roles }),
|
|
272
299
|
...(config.efficiency === undefined ? {} : { efficiency: structuredClone(config.efficiency) }),
|
|
273
300
|
...(rolePrompts === undefined ? {} : { rolePrompts }),
|
|
274
301
|
...(config.limits === undefined ? {} : { limits: { ...config.limits } }),
|
|
@@ -284,9 +311,20 @@ export function loadRuntimeConfig(file, options) {
|
|
|
284
311
|
/** New admissions normalize once; validation of a saved document is lossless. */
|
|
285
312
|
export function normalizeRuntimeConfig(input, options) {
|
|
286
313
|
const config = JSON.parse(JSON.stringify(validateRuntimeConfig(input, options)));
|
|
314
|
+
config.roles = Object.fromEntries(roleIds(config).map(id => { const { id: _id, ...descriptor } = resolveRoleDescriptor(config, id); return [id, descriptor]; }));
|
|
287
315
|
config.efficiency = {
|
|
288
316
|
schemaVersion: 1, contextMode: 'incremental', reviewMode: 'incremental', planning: 'proportional', acceptDeveloperChecks: true,
|
|
289
317
|
...config.efficiency, verification: { maxConcurrency: 1, ...config.efficiency?.verification },
|
|
290
318
|
};
|
|
291
319
|
return config;
|
|
292
320
|
}
|
|
321
|
+
/** Built-ins stay ordered and custom roles are declared data, never inferred from a prompt. */
|
|
322
|
+
export function roleIds(config) { return [...new Set([...BUILTIN_ROLES, ...Object.keys(config.roles ?? {})])]; }
|
|
323
|
+
export function resolveRoleDescriptor(config, id) {
|
|
324
|
+
if (isBuiltinRole(id))
|
|
325
|
+
return { ...config.agents[id], ...BUILTIN_ROLE_POLICY[id], id };
|
|
326
|
+
const role = Object.hasOwn(config.roles ?? {}, id) ? config.roles[id] : undefined;
|
|
327
|
+
if (!role)
|
|
328
|
+
throw new Error(`Unknown runtime role '${id}'`);
|
|
329
|
+
return { ...role, ...(config.rolePrompts?.[id] === undefined ? {} : { prompt: config.rolePrompts[id] }), id };
|
|
330
|
+
}
|
|
@@ -2,10 +2,11 @@ import { type PipelineContext } from '../pipeline/pipeline-state.js';
|
|
|
2
2
|
import { type ExecutorRegistry } from './executors.js';
|
|
3
3
|
import type { AgentEvent, RuntimeConfig, AgentEventRole } from './executor-types.js';
|
|
4
4
|
import { parseAgentObject } from './graph/artifacts.js';
|
|
5
|
+
import { type CoreNodeId, type CoreStateType } from './graph/state.js';
|
|
5
6
|
import { type RuntimeIdentity } from './runtime-identity.js';
|
|
6
|
-
import type { WorkflowEvent, WorkflowSpan, WorkflowState } from './workflow-types.js';
|
|
7
|
+
import type { WorkflowDefinition, WorkflowEvent, WorkflowNode, WorkflowSpan, WorkflowState } from './workflow-types.js';
|
|
7
8
|
export declare const RUNTIME_API_VERSION = 1;
|
|
8
|
-
export declare const CORE_WORKFLOW_VERSION = "
|
|
9
|
+
export declare const CORE_WORKFLOW_VERSION = "7";
|
|
9
10
|
export declare const CORE_PACKAGE_VERSION: string;
|
|
10
11
|
export declare function coreRuntimeIdentity(): RuntimeIdentity;
|
|
11
12
|
export { parseAgentObject };
|
|
@@ -32,9 +33,14 @@ export declare function preflightCoreWorkflow(options: Pick<CoreWorkflowOptions,
|
|
|
32
33
|
config: RuntimeConfig;
|
|
33
34
|
registry: ExecutorRegistry;
|
|
34
35
|
}>;
|
|
36
|
+
/** The legacy graph's resume identity; extracting it must never change its fingerprint. */
|
|
37
|
+
export declare function implementationWorkflowDefinition(nodes: Record<CoreNodeId, WorkflowNode<CoreStateType>>, { attempts, compactDeveloper }: {
|
|
38
|
+
attempts: number;
|
|
39
|
+
compactDeveloper: boolean;
|
|
40
|
+
}): WorkflowDefinition<CoreStateType>;
|
|
35
41
|
/**
|
|
36
|
-
* Runs the Core implementation graph: architect → developer → verify →
|
|
37
|
-
* with bounded corrections
|
|
42
|
+
* Runs the Core implementation graph: architect → developer → verify → reviewer → archive,
|
|
43
|
+
* with bounded corrections through fixer → verify → reviewer, an autonomous investigation pass
|
|
38
44
|
* before a low-confidence design asks the requester, real verification receipts, acceptance
|
|
39
45
|
* evidence bound to the exact candidate, and an optional approval before archive.
|
|
40
46
|
*/
|
|
@@ -3,7 +3,7 @@ import { prepareOpenSpec, roleOpenSpecContext } from './openspec.js';
|
|
|
3
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fingerprintCandidate, initializePipeline, inspectPipeline, pipelineStateDirectory, validatePipelineContext, validateVerificationRequest, } from '../pipeline/pipeline-state.js';
|
|
6
|
-
import { normalizeRuntimeConfig } from './config.js';
|
|
6
|
+
import { normalizeRuntimeConfig, resolveRoleDescriptor, roleIds } from './config.js';
|
|
7
7
|
import { createExecutorRegistry } from './executors.js';
|
|
8
8
|
import { archive, child, journal, parseAgentObject, SLUG } from './graph/artifacts.js';
|
|
9
9
|
import { coreNodes } from './graph/nodes.js';
|
|
@@ -15,10 +15,10 @@ import { assertEffortSupported } from './capabilities.js';
|
|
|
15
15
|
import { runtimePackageIntegrity } from './runtime-identity.js';
|
|
16
16
|
import { readWorkflowState, runWorkflow } from './workflow.js';
|
|
17
17
|
export const RUNTIME_API_VERSION = 1;
|
|
18
|
-
export const CORE_WORKFLOW_VERSION = '
|
|
18
|
+
export const CORE_WORKFLOW_VERSION = '7';
|
|
19
19
|
export const CORE_PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
|
|
20
20
|
export function coreRuntimeIdentity() {
|
|
21
|
-
return { packageVersion: CORE_PACKAGE_VERSION, workflowVersion: CORE_WORKFLOW_VERSION, instructionsVersion: String(ROLE_INSTRUCTIONS_VERSION), packageIntegrity: runtimePackageIntegrity(), apiVersion:
|
|
21
|
+
return { packageVersion: CORE_PACKAGE_VERSION, workflowVersion: CORE_WORKFLOW_VERSION, instructionsVersion: String(ROLE_INSTRUCTIONS_VERSION), packageIntegrity: runtimePackageIntegrity(), apiVersion: RUNTIME_API_VERSION };
|
|
22
22
|
}
|
|
23
23
|
export { parseAgentObject };
|
|
24
24
|
/** Read-only admission: validate scope, limits and actual effort support before freezing a request. */
|
|
@@ -36,7 +36,7 @@ export async function preflightCoreWorkflow(options) {
|
|
|
36
36
|
if (config.verification.length)
|
|
37
37
|
validateVerificationRequest(context, { kind: 'scoped', commands: config.verification });
|
|
38
38
|
const registry = options.registry ?? createExecutorRegistry(config);
|
|
39
|
-
for (const selected of
|
|
39
|
+
for (const selected of roleIds(config).map(id => resolveRoleDescriptor(config, id))) {
|
|
40
40
|
registry.validateLimits(selected.provider, config.limits ?? {});
|
|
41
41
|
for (const tier of [selected, ...(selected.escalation ? [selected.escalation] : [])]) {
|
|
42
42
|
if (tier.effort !== undefined)
|
|
@@ -45,9 +45,19 @@ export async function preflightCoreWorkflow(options) {
|
|
|
45
45
|
}
|
|
46
46
|
return { context, config, registry };
|
|
47
47
|
}
|
|
48
|
+
/** The legacy graph's resume identity; extracting it must never change its fingerprint. */
|
|
49
|
+
export function implementationWorkflowDefinition(nodes, { attempts, compactDeveloper }) {
|
|
50
|
+
return {
|
|
51
|
+
id: 'specrails-implementation', version: CORE_WORKFLOW_VERSION, schema: CoreState, entry: 'architect',
|
|
52
|
+
// The compact developer finishes task groups over developer→verify passes
|
|
53
|
+
// that do not count as corrections (bounded: +2 passes each).
|
|
54
|
+
maxTransitions: attempts * 3 + 3 + (compactDeveloper ? attempts * 2 * 2 : 0),
|
|
55
|
+
nodes: Object.fromEntries(CORE_NODE_ORDER.map(id => [id, nodes[id]])),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
48
58
|
/**
|
|
49
|
-
* Runs the Core implementation graph: architect → developer → verify →
|
|
50
|
-
* with bounded corrections
|
|
59
|
+
* Runs the Core implementation graph: architect → developer → verify → reviewer → archive,
|
|
60
|
+
* with bounded corrections through fixer → verify → reviewer, an autonomous investigation pass
|
|
51
61
|
* before a low-confidence design asks the requester, real verification receipts, acceptance
|
|
52
62
|
* evidence bound to the exact candidate, and an optional approval before archive.
|
|
53
63
|
*/
|
|
@@ -68,12 +78,12 @@ export async function runCoreWorkflow(options) {
|
|
|
68
78
|
}
|
|
69
79
|
}
|
|
70
80
|
const prepared = prepareOpenSpec(context.artifactRoot, options.change, directory);
|
|
71
|
-
const openspec = Object.fromEntries(
|
|
72
|
-
const provider = config.providers.find(item => item.id === config
|
|
73
|
-
return [role, roleOpenSpecContext(prepared, context.artifactRoot, options.change, directory, role, provider?.kind === 'cli' ? provider.cli : 'claude')];
|
|
81
|
+
const openspec = Object.fromEntries(roleIds(config).filter(role => resolveRoleDescriptor(config, role).openspecSkill).map(role => {
|
|
82
|
+
const provider = config.providers.find(item => item.id === resolveRoleDescriptor(config, role).provider);
|
|
83
|
+
return [role, roleOpenSpecContext(prepared, context.artifactRoot, options.change, directory, resolveRoleDescriptor(config, role), provider?.kind === 'cli' ? provider.cli : 'claude')];
|
|
74
84
|
}));
|
|
75
|
-
for (const role of
|
|
76
|
-
await registry.get(config
|
|
85
|
+
for (const role of Object.keys(openspec))
|
|
86
|
+
await registry.get(resolveRoleDescriptor(config, role).provider).validateOpenSpec?.(openspec[role]);
|
|
77
87
|
const attempts = config.limits?.maxAttempts ?? 3;
|
|
78
88
|
const developerProvider = config.providers.find(item => item.id === config.agents.developer.provider);
|
|
79
89
|
const compactDeveloper = developerProvider?.kind === 'openai-compatible' && (developerProvider.agentLoop ?? 'compact') === 'compact';
|
|
@@ -127,13 +137,6 @@ export async function runCoreWorkflow(options) {
|
|
|
127
137
|
return inspection.phases.archive.status === 'done';
|
|
128
138
|
return true;
|
|
129
139
|
},
|
|
130
|
-
workflow: {
|
|
131
|
-
id: 'specrails-implementation', version: CORE_WORKFLOW_VERSION, schema: CoreState, entry: 'architect',
|
|
132
|
-
// The compact (small-model) developer finishes its task groups over
|
|
133
|
-
// several developer→verify passes that do not count as corrections, so
|
|
134
|
-
// the transition budget must cover them too (bounded: +2 passes each).
|
|
135
|
-
maxTransitions: attempts * 3 + 3 + (compactDeveloper ? attempts * 2 * 2 : 0),
|
|
136
|
-
nodes: Object.fromEntries(CORE_NODE_ORDER.map(id => [id, nodes[id]])),
|
|
137
|
-
},
|
|
140
|
+
workflow: implementationWorkflowDefinition(nodes, { attempts, compactDeveloper }),
|
|
138
141
|
});
|
|
139
142
|
}
|
|
@@ -13,6 +13,9 @@ export declare function efficiencySummary(state: WorkflowState, context: Pipelin
|
|
|
13
13
|
notRunEvidenceIds?: string[];
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
+
}, durableEvidence?: {
|
|
17
|
+
items: Array<Record<string, unknown>>;
|
|
18
|
+
available: boolean;
|
|
16
19
|
}): {
|
|
17
20
|
schemaVersion: 1;
|
|
18
21
|
currentEvidenceIds: string[];
|
|
@@ -24,7 +27,7 @@ export declare function efficiencySummary(state: WorkflowState, context: Pipelin
|
|
|
24
27
|
archive: import("../pipeline/pipeline-state.js").PhaseStatus;
|
|
25
28
|
delivery: "pending" | "complete" | "pending-host";
|
|
26
29
|
roles: {
|
|
27
|
-
role:
|
|
30
|
+
role: string;
|
|
28
31
|
provider: string | null;
|
|
29
32
|
model: string | null;
|
|
30
33
|
effort: string | null;
|
|
@@ -1,36 +1,39 @@
|
|
|
1
|
+
import { resolveRoleDescriptor, roleIds } from './config.js';
|
|
2
|
+
import { BUILTIN_ROLES } from './executor-types.js';
|
|
1
3
|
import { readVerificationEvidence } from '../pipeline/pipeline-state.js';
|
|
2
4
|
import { runtimeEfficiency } from './efficiency.js';
|
|
3
5
|
/** Compact derived view. Durable invocation/evidence IDs remain the accounting authority. */
|
|
4
|
-
export function efficiencySummary(state, context, config, completion, identity) {
|
|
6
|
+
export function efficiencySummary(state, context, config, completion, identity, durableEvidence) {
|
|
5
7
|
const invocations = state.history.flatMap(attempt => [...(attempt.invocations ?? []), ...(attempt.pendingInvocations ?? [])].map(call => ({ ...call, role: attempt.stepId, attemptId: attempt.id })));
|
|
6
8
|
const kinds = ['initial', 'correction', 'repair', 'deepen', 'session-fallback'];
|
|
7
9
|
const byKind = Object.fromEntries(kinds.map(kind => [kind, invocations.filter(call => call.kind === kind).length]));
|
|
8
|
-
const measured = state.history.every(attempt => !attempt.pendingInvocations?.length) && state.history.every(attempt => !
|
|
10
|
+
const measured = state.history.every(attempt => !attempt.pendingInvocations?.length) && state.history.every(attempt => !(config ? roleIds(config) : [...BUILTIN_ROLES]).includes(attempt.stepId) || attempt.invocations !== undefined) && invocations.every(call => call.kind !== undefined && call.promptBytes !== undefined);
|
|
9
11
|
const bytes = (key) => invocations.every(call => call[key] !== undefined) ? invocations.reduce((sum, call) => sum + call[key], 0) : null;
|
|
10
12
|
const checkIds = new Set();
|
|
11
|
-
let checks = [], evidenceAvailable = true;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
for (const row of page.items ?? [])
|
|
21
|
-
if (typeof row.id === 'string' && !checkIds.has(row.id)) {
|
|
22
|
-
checkIds.add(row.id);
|
|
23
|
-
checks.push(row);
|
|
13
|
+
let checks = durableEvidence?.items ?? [], evidenceAvailable = durableEvidence?.available ?? true;
|
|
14
|
+
if (!durableEvidence)
|
|
15
|
+
try {
|
|
16
|
+
let cursor;
|
|
17
|
+
do {
|
|
18
|
+
const page = readVerificationEvidence(context, { limit: 100, ...(cursor ? { cursor } : {}) });
|
|
19
|
+
if (!page.available) {
|
|
20
|
+
evidenceAvailable = false;
|
|
21
|
+
break;
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
23
|
+
for (const row of page.items ?? [])
|
|
24
|
+
if (typeof row.id === 'string' && !checkIds.has(row.id)) {
|
|
25
|
+
checkIds.add(row.id);
|
|
26
|
+
checks.push(row);
|
|
27
|
+
}
|
|
28
|
+
if (checkIds.size > 10000)
|
|
29
|
+
throw new Error('Evidence summary limit');
|
|
30
|
+
cursor = 'nextCursor' in page ? page.nextCursor : undefined;
|
|
31
|
+
} while (cursor);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
checks = [];
|
|
35
|
+
evidenceAvailable = false;
|
|
36
|
+
}
|
|
34
37
|
const checksComplete = evidenceAvailable && checks.every(check => check.status !== 'interrupted' && typeof check.durationMs === 'number');
|
|
35
38
|
const escalatedRoles = new Set();
|
|
36
39
|
const escalations = invocations.filter(call => { if (call.tier !== 'escalation' || escalatedRoles.has(call.role))
|
|
@@ -39,13 +42,13 @@ export function efficiencySummary(state, context, config, completion, identity)
|
|
|
39
42
|
schemaVersion: 1, currentEvidenceIds: [...(identity?.verification?.receipt?.commands.flatMap(command => command.evidenceId ? [command.evidenceId] : []) ?? []), ...(identity?.verification?.receipt?.notRunEvidenceIds ?? [])].slice(0, 100), planHash: identity?.planHash ?? null, candidateHash: identity?.candidateHash ?? null, runId: state.runId, workflowVersion: state.workflowVersion,
|
|
40
43
|
technicalAcceptance: completion?.validation === 'verified' ? 'validated' : completion?.validation === 'with-exceptions' ? 'with-exceptions' : completion?.validation === 'blocked' ? 'blocked' : 'pending',
|
|
41
44
|
archive: completion?.archive ?? 'pending', delivery: completion?.delivery ?? 'pending',
|
|
42
|
-
roles:
|
|
43
|
-
const selected = config
|
|
45
|
+
roles: (config ? roleIds(config) : [...BUILTIN_ROLES]).map(role => {
|
|
46
|
+
const selected = (config ? resolveRoleDescriptor(config, role) : undefined), latest = invocations.filter(call => call.role === role).at(-1);
|
|
44
47
|
return { role, provider: latest?.provider ?? selected?.provider ?? null, model: latest?.model ?? selected?.model ?? null, effort: latest ? latest.requestedEffort ?? null : selected?.effort ?? null, observedModel: null, observedEffort: null, origin: 'frozen-runtime-config', tier: latest?.tier ?? 'base' };
|
|
45
48
|
}),
|
|
46
49
|
invocations: { byKind, total: invocations.length, complete: measured, promptBytes: bytes('promptBytes'), contextBytes: bytes('contextBytes'), handoffBytes: bytes('handoffBytes'), fullContexts: invocations.filter(call => call.contextMode === 'full').length, incrementalContexts: invocations.filter(call => call.contextMode === 'incremental').length },
|
|
47
50
|
escalations: escalations.slice(-16), escalationsTruncated: escalations.length > 16,
|
|
48
51
|
checks: { invalidated: identity && evidenceAvailable ? checks.filter(check => check.planHash !== identity.planHash || check.candidateHash !== identity.candidateHash || check.reuseReason === 'snapshot-inputs-changed-during-verification').length : null, available: evidenceAvailable, complete: checksComplete, executed: checksComplete ? checks.filter(check => check.disposition === 'executed').length : null, reused: evidenceAvailable ? checks.filter(check => check.disposition === 'reused').length : null, notRun: evidenceAvailable ? checks.filter(check => check.disposition === 'not-run').length : null, durationMs: checksComplete ? checks.reduce((sum, check) => sum + (typeof check.durationMs === 'number' ? check.durationMs : 0), 0) : null },
|
|
49
|
-
metrics: runtimeEfficiency(state),
|
|
52
|
+
metrics: runtimeEfficiency(state, config ? roleIds(config) : undefined),
|
|
50
53
|
};
|
|
51
54
|
}
|
|
@@ -18,10 +18,10 @@ export interface ProviderInvocation {
|
|
|
18
18
|
provider: string;
|
|
19
19
|
/** Requested model; absent means the provider selected its default. */
|
|
20
20
|
model?: string;
|
|
21
|
-
status: 'succeeded' | 'failed';
|
|
22
|
-
/** Entire executor wall time
|
|
23
|
-
durationMs: number;
|
|
24
|
-
toolCalls: number;
|
|
21
|
+
status: 'succeeded' | 'failed' | 'interrupted';
|
|
22
|
+
/** Entire executor wall time; unknown after losing an executor process. */
|
|
23
|
+
durationMs: number | null;
|
|
24
|
+
toolCalls: number | null;
|
|
25
25
|
usage: CacheTokenUsage & {
|
|
26
26
|
inputTokens: number | null;
|
|
27
27
|
outputTokens: number | null;
|
|
@@ -2,4 +2,4 @@ import type { WorkflowState } from './workflow-types.js';
|
|
|
2
2
|
import { type RuntimeEfficiency } from './efficiency-types.js';
|
|
3
3
|
export * from './efficiency-types.js';
|
|
4
4
|
/** Read-only projection: failed/superseded attempts remain spend, polling never changes it. */
|
|
5
|
-
export declare function runtimeEfficiency(state: WorkflowState): RuntimeEfficiency;
|
|
5
|
+
export declare function runtimeEfficiency(state: WorkflowState, roleIds?: readonly string[]): RuntimeEfficiency;
|