specrails-core 6.0.1 → 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/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/config.d.ts +4 -1
- package/dist/agent-runtime/config.js +41 -3
- package/dist/agent-runtime/core-host.d.ts +9 -3
- package/dist/agent-runtime/core-host.js +21 -18
- 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 +82 -9
- package/dist/agent-runtime/graph/nodes.d.ts +2 -0
- package/dist/agent-runtime/graph/nodes.js +2 -2
- package/dist/agent-runtime/graph/roles.d.ts +6 -0
- package/dist/agent-runtime/graph/roles.js +23 -17
- 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 +4 -3
- package/dist/agent-runtime/prompts.js +26 -5
- package/dist/agent-runtime/recovery.d.ts +1 -1
- package/dist/agent-runtime/repository-context.js +1 -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 +10 -0
- 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 +38 -4
- package/dist/pipeline/pipeline-state.js +94 -21
- package/integration-contract.json +83 -11
- 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
|
@@ -13,6 +13,11 @@ export interface RoleExecutionState {
|
|
|
13
13
|
attemptId: string;
|
|
14
14
|
}>>;
|
|
15
15
|
}
|
|
16
|
+
/** Bound by the engine to one run/scope/node; the legacy adapter remains file-backed. */
|
|
17
|
+
export interface RoleStatePort {
|
|
18
|
+
read(): RoleExecutionState;
|
|
19
|
+
write(state: RoleExecutionState): void;
|
|
20
|
+
}
|
|
16
21
|
/** Host-owned auxiliary state lives under the workflow's existing run lease.
|
|
17
22
|
* A corrupted packet fails explicitly; it is never used as missing context. */
|
|
18
23
|
export declare function readRoleState(directory: string): RoleExecutionState;
|
|
@@ -53,4 +53,6 @@ export declare function addDeveloperChecks(context: PipelineContext, proposals:
|
|
|
53
53
|
export declare function expandedPlanCommands(context: PipelineContext, plan: VerificationPlan): VerificationCommand[];
|
|
54
54
|
/** Bind the exact manifest and every source to lifecycle gates. */
|
|
55
55
|
export declare function bindPlan(context: PipelineContext, plan: VerificationPlan): void;
|
|
56
|
+
/** Rebind a checkpoint-owned plan to a new run, retaining the exact admitted checks. */
|
|
57
|
+
export declare function forkVerificationPlan(context: PipelineContext, source: VerificationPlan, sourceContext: PipelineContext): VerificationPlan;
|
|
56
58
|
export {};
|
|
@@ -208,3 +208,13 @@ export function bindPlan(context, plan) {
|
|
|
208
208
|
}
|
|
209
209
|
bindVerificationPlan(context, plan.planHash, files);
|
|
210
210
|
}
|
|
211
|
+
/** Rebind a checkpoint-owned plan to a new run, retaining the exact admitted checks. */
|
|
212
|
+
export function forkVerificationPlan(context, source, sourceContext) {
|
|
213
|
+
const { integrity, ...payload } = source;
|
|
214
|
+
if (integrity !== fingerprint(payload) || source.scopeHash !== fingerprint(sourceContext) || source.runId !== sourceContext.runId
|
|
215
|
+
|| source.planHash !== fingerprint({ policyVersion: 1, scopeHash: source.scopeHash, executionPolicy: source.executionPolicy, entries: source.entries }))
|
|
216
|
+
throw new Error('Fork verification plan failed its source integrity check');
|
|
217
|
+
const plan = savePlan(context, source.baseline, source.developer, null, source.executionPolicy);
|
|
218
|
+
bindPlan(context, plan);
|
|
219
|
+
return plan;
|
|
220
|
+
}
|
|
@@ -52,6 +52,8 @@ export interface WorkflowStepContext {
|
|
|
52
52
|
attemptId: string;
|
|
53
53
|
attempt: number;
|
|
54
54
|
input: JsonValue;
|
|
55
|
+
/** Engine-owned, durably claimed operator text; cannot replace the frozen scope. */
|
|
56
|
+
operatorSteering?: string;
|
|
55
57
|
signal: AbortSignal;
|
|
56
58
|
/** A detached snapshot; modifying it cannot modify the persisted run. */
|
|
57
59
|
checkpoint: WorkflowState;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { RunWorkflowOptions, WorkflowState } from './workflow-types.js';
|
|
1
|
+
import type { RunWorkflowOptions, WorkflowDefinition, WorkflowState } from './workflow-types.js';
|
|
2
2
|
export * from './workflow-types.js';
|
|
3
3
|
export { readWorkflowState, readWorkflowEnvelope, writeWorkflowEnvelope, WorkflowStoreError, type WorkflowEnvelope } from './durable-store.js';
|
|
4
4
|
export declare class WorkflowError extends Error {
|
|
5
5
|
readonly code: 'INVALID_WORKFLOW' | 'ALREADY_EXISTS' | 'NOT_FOUND' | 'INCOMPATIBLE_RESUME';
|
|
6
6
|
constructor(code: 'INVALID_WORKFLOW' | 'ALREADY_EXISTS' | 'NOT_FOUND' | 'INCOMPATIBLE_RESUME', message: string);
|
|
7
7
|
}
|
|
8
|
+
/** Pure legacy resume identity; preserve node order, declared exits and default metadata. */
|
|
9
|
+
export declare function definitionFingerprint<S extends Record<string, unknown>>(workflow: WorkflowDefinition<S>): string;
|
|
8
10
|
/**
|
|
9
11
|
* Execute a LangGraph state graph with a durable host ledger at every effect
|
|
10
12
|
* boundary. LangGraph owns traversal, state reducers, checkpoints, interrupts
|
|
@@ -63,9 +63,13 @@ function validateWorkflow(workflow) {
|
|
|
63
63
|
if (!Number.isInteger(maxTransitions) || maxTransitions < 1 || maxTransitions > 10_000) {
|
|
64
64
|
throw new WorkflowError('INVALID_WORKFLOW', 'maxTransitions must be an integer from 1 to 10000');
|
|
65
65
|
}
|
|
66
|
+
return definitionFingerprint(workflow);
|
|
67
|
+
}
|
|
68
|
+
/** Pure legacy resume identity; preserve node order, declared exits and default metadata. */
|
|
69
|
+
export function definitionFingerprint(workflow) {
|
|
66
70
|
return fingerprint({
|
|
67
|
-
id: workflow.id, version: workflow.version, maxTransitions, entry: workflow.entry,
|
|
68
|
-
nodes:
|
|
71
|
+
id: workflow.id, version: workflow.version, maxTransitions: workflow.maxTransitions ?? 100, entry: workflow.entry,
|
|
72
|
+
nodes: Object.keys(workflow.nodes).map(id => ({ id, effect: workflow.nodes[id].effect ?? 'read', maxAttempts: workflow.nodes[id].maxAttempts ?? 1, retrySafe: workflow.nodes[id].retrySafe ?? false, ends: workflow.nodes[id].ends })),
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
function validateUsage(usage) {
|
|
@@ -406,7 +410,7 @@ export async function runWorkflow(options) {
|
|
|
406
410
|
},
|
|
407
411
|
reportInvocation: async (invocation) => {
|
|
408
412
|
validateUsage(invocation.usage);
|
|
409
|
-
if (!Number.isFinite(invocation.durationMs) || invocation.durationMs < 0 || !Number.isSafeInteger(invocation.toolCalls) || invocation.toolCalls < 0)
|
|
413
|
+
if (typeof invocation.durationMs !== 'number' || !Number.isFinite(invocation.durationMs) || invocation.durationMs < 0 || typeof invocation.toolCalls !== 'number' || !Number.isSafeInteger(invocation.toolCalls) || invocation.toolCalls < 0)
|
|
410
414
|
throw new TypeError('Invalid invocation measurement');
|
|
411
415
|
if (invocation.invocationId && history.invocations?.some(item => item.invocationId === invocation.invocationId))
|
|
412
416
|
return;
|
|
@@ -13,11 +13,11 @@ export declare function canonicalWorkspace(cwd: string, allowedRoots: string[]):
|
|
|
13
13
|
};
|
|
14
14
|
/** Application tool boundary, not an OS sandbox for concurrent external processes. */
|
|
15
15
|
export declare class WorkspaceTools {
|
|
16
|
-
private readonly
|
|
16
|
+
private readonly access;
|
|
17
17
|
readonly cwd: string;
|
|
18
18
|
readonly roots: string[];
|
|
19
19
|
private readonly rootAliases;
|
|
20
|
-
constructor(cwd: string, roots: string[],
|
|
20
|
+
constructor(cwd: string, roots: string[], access: 'read' | 'write' | AgentRole);
|
|
21
21
|
definitions(): WorkspaceToolDefinition[];
|
|
22
22
|
private resolve;
|
|
23
23
|
execute(name: string, input: unknown): string;
|
|
@@ -33,12 +33,12 @@ export function canonicalWorkspace(cwd, allowedRoots) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Application tool boundary, not an OS sandbox for concurrent external processes. */
|
|
35
35
|
export class WorkspaceTools {
|
|
36
|
-
|
|
36
|
+
access;
|
|
37
37
|
cwd;
|
|
38
38
|
roots;
|
|
39
39
|
rootAliases;
|
|
40
|
-
constructor(cwd, roots,
|
|
41
|
-
this.
|
|
40
|
+
constructor(cwd, roots, access) {
|
|
41
|
+
this.access = access;
|
|
42
42
|
const scope = canonicalWorkspace(cwd, roots);
|
|
43
43
|
this.cwd = scope.cwd;
|
|
44
44
|
this.roots = scope.roots;
|
|
@@ -52,8 +52,8 @@ export class WorkspaceTools {
|
|
|
52
52
|
definition('read_lines', 'Read numbered lines, up to 500 lines / 48 KiB, from a UTF-8 file up to 1 MiB. Returns a full-file SHA-256 for guarded edits.', { ...file, startLine: { type: 'integer', minimum: 1 }, endLine: { type: 'integer', minimum: 1 } }, ['path']),
|
|
53
53
|
definition('search_text', 'Search literal text recursively in source files. Skips dependencies, build output, metadata, binaries and symlinks. Returns bounded numbered matches and a truncation flag.', { ...file, query: { type: 'string' }, maxResults: { type: 'integer', minimum: 1, maximum: 100 } }, ['path', 'query']),
|
|
54
54
|
definition('get_diff', 'Read the staged and unstaged Git diff against HEAD for one file, up to 48 KiB. Untracked files are identified; use read_lines for their content.', file, ['path']),
|
|
55
|
-
...(this.
|
|
56
|
-
...(this.
|
|
55
|
+
...((this.access === 'write' || this.access === 'developer') ? [definition('write_file', 'Create or replace a UTF-8 source file (max 256 KiB). Parent directories are created. Runtime metadata cannot be changed.', { ...file, content: { type: 'string' } }, ['path', 'content'])] : []),
|
|
56
|
+
...((this.access === 'write' || this.access === 'developer') ? [definition('apply_patch', 'Atomically replace one exact, unique fragment in a UTF-8 file. Rejects missing or ambiguous matches. Optionally guard against stale reads with expectedHash from read_lines.', { ...file, oldText: { type: 'string', minLength: 1 }, newText: { type: 'string' }, expectedHash: { type: 'string' } }, ['path', 'oldText', 'newText'])] : []),
|
|
57
57
|
];
|
|
58
58
|
}
|
|
59
59
|
resolve(raw, write) {
|
|
@@ -92,7 +92,7 @@ export class WorkspaceTools {
|
|
|
92
92
|
throw new Error('Tool input must be a JSON object');
|
|
93
93
|
const args = input;
|
|
94
94
|
if (!this.definitions().some(tool => tool.function.name === name))
|
|
95
|
-
throw new Error(`Tool '${name}' is unavailable for the ${this.
|
|
95
|
+
throw new Error(`Tool '${name}' is unavailable for the ${this.access} role`);
|
|
96
96
|
const allowed = name === 'write_file' ? ['path', 'content'] : name === 'apply_patch' ? ['path', 'oldText', 'newText', 'expectedHash'] : name === 'read_lines' ? ['path', 'startLine', 'endLine'] : name === 'search_text' ? ['path', 'query', 'maxResults'] : ['path'];
|
|
97
97
|
if (Object.keys(args).some(key => !allowed.includes(key)) || typeof args.path !== 'string')
|
|
98
98
|
throw new Error('Invalid tool arguments');
|
package/dist/installer/cli.js
CHANGED
|
@@ -99,6 +99,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
99
99
|
return await dispatch(subcommand, flags, positionals);
|
|
100
100
|
}
|
|
101
101
|
catch (err) {
|
|
102
|
+
if (subcommand === 'runtime') {
|
|
103
|
+
const { runtimeFailure } = await import('../agent-runtime/cli.js');
|
|
104
|
+
process.stdout.write(JSON.stringify(runtimeFailure(err)) + '\n');
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
102
107
|
if (isInstallerError(err)) {
|
|
103
108
|
fatal(err.message);
|
|
104
109
|
return err.exitCode;
|
|
@@ -4,8 +4,8 @@ import type { Provider } from './provider-detect.js';
|
|
|
4
4
|
* ships. The implement pipeline depends on all three. Any additional agent
|
|
5
5
|
* comes from a user-authored profile (`custom-*`), never the installer.
|
|
6
6
|
*
|
|
7
|
-
* Mirrors the
|
|
8
|
-
*
|
|
7
|
+
* Mirrors the required built-in roles in schemas/agent-runtime.schema.json
|
|
8
|
+
* (`agents`, with the sr- prefix in installed files); keep both in sync.
|
|
9
9
|
*/
|
|
10
10
|
export declare const CORE_AGENTS: Set<string>;
|
|
11
11
|
/**
|
|
@@ -11,8 +11,8 @@ import { buildManifest, writeManifestFiles } from './manifest.js';
|
|
|
11
11
|
* ships. The implement pipeline depends on all three. Any additional agent
|
|
12
12
|
* comes from a user-authored profile (`custom-*`), never the installer.
|
|
13
13
|
*
|
|
14
|
-
* Mirrors the
|
|
15
|
-
*
|
|
14
|
+
* Mirrors the required built-in roles in schemas/agent-runtime.schema.json
|
|
15
|
+
* (`agents`, with the sr- prefix in installed files); keep both in sync.
|
|
16
16
|
*/
|
|
17
17
|
export const CORE_AGENTS = new Set([
|
|
18
18
|
'sr-architect',
|
|
@@ -151,10 +151,6 @@ const SKILL_FROM_COMMAND = {
|
|
|
151
151
|
command: 'implement',
|
|
152
152
|
description: 'sr:implement — Full OpenSpec lifecycle with specialized agents: architect designs, developer implements, reviewer validates. Use for implementing frozen specs or feature descriptions.',
|
|
153
153
|
},
|
|
154
|
-
'sr-batch-implement': {
|
|
155
|
-
command: 'batch-implement',
|
|
156
|
-
description: 'sr:batch-implement — Run multiple frozen specs as one programmatic agent workflow.',
|
|
157
|
-
},
|
|
158
154
|
};
|
|
159
155
|
/**
|
|
160
156
|
* Provider-static subtrees inside a providerDir that are SHARED via symlink from
|
|
@@ -1557,7 +1553,7 @@ function renderInitialAgentsMd(repoRoot) {
|
|
|
1557
1553
|
'',
|
|
1558
1554
|
'Implementation is coordinated by the Specrails programmatic agent runtime.',
|
|
1559
1555
|
'Use the frozen scope and official OpenSpec workflow supplied for your role.',
|
|
1560
|
-
'Do not start another implement
|
|
1556
|
+
'Do not start another implement workflow inside a role.',
|
|
1561
1557
|
'',
|
|
1562
1558
|
'## Repository context',
|
|
1563
1559
|
'',
|
|
@@ -187,6 +187,9 @@ export interface PipelineState {
|
|
|
187
187
|
acceptanceHash: string;
|
|
188
188
|
};
|
|
189
189
|
artifactExclusions: string[];
|
|
190
|
+
/** Explicit engine-owned metadata; absent for all legacy journals. */
|
|
191
|
+
runtimeExclusions?: readonly string[];
|
|
192
|
+
repositoryExclusions?: Readonly<Record<string, readonly string[]>>;
|
|
190
193
|
preview?: {
|
|
191
194
|
baseHash: string;
|
|
192
195
|
files: PreviewFile[];
|
|
@@ -207,6 +210,18 @@ export declare function withinRepositoryScope(repository: Pick<PipelineRepositor
|
|
|
207
210
|
export declare function repositoryWorkingDirectory(repository: Pick<PipelineRepository, 'path' | 'scope'>): string;
|
|
208
211
|
export declare function validatePipelineContext(input: unknown): PipelineContext;
|
|
209
212
|
export declare function pipelineStateDirectory(context: PipelineContext): string;
|
|
213
|
+
export type CandidateScope = Pick<PipelineState, 'context' | 'scopeHash' | 'artifactExclusions'> & {
|
|
214
|
+
/** Engine-owned child journals and artifact paths, never user-defined exclusion patterns. */
|
|
215
|
+
runtimeExclusions?: readonly string[];
|
|
216
|
+
repositoryExclusions?: Readonly<Record<string, readonly string[]>>;
|
|
217
|
+
};
|
|
218
|
+
/** Bind only engine-owned metadata paths; callers supply declared run/branch ownership. */
|
|
219
|
+
export declare function bindImplementationExclusions(contextInput: PipelineContext, exclusions: {
|
|
220
|
+
runtimeExclusions: readonly string[];
|
|
221
|
+
repositoryExclusions: Readonly<Record<string, readonly string[]>>;
|
|
222
|
+
}): void;
|
|
223
|
+
/** Read the validated implementation journal's dynamic candidate exclusions. */
|
|
224
|
+
export declare function readCandidateScope(contextInput: unknown): CandidateScope;
|
|
210
225
|
export interface CandidateManifest {
|
|
211
226
|
schemaVersion: 1;
|
|
212
227
|
scopeHash: string;
|
|
@@ -216,8 +231,8 @@ export interface CandidateManifest {
|
|
|
216
231
|
files: Array<[string, string]>;
|
|
217
232
|
}>;
|
|
218
233
|
}
|
|
219
|
-
export declare function candidateManifest(state:
|
|
220
|
-
export declare function fingerprintCandidate(state:
|
|
234
|
+
export declare function candidateManifest(state: CandidateScope): CandidateManifest;
|
|
235
|
+
export declare function fingerprintCandidate(state: CandidateScope): string;
|
|
221
236
|
export declare function initializePipeline(contextInput: unknown, change: string): PipelineState;
|
|
222
237
|
/** Host-owned binding, including immutable harness bytes outside the candidate. */
|
|
223
238
|
export declare function bindVerificationPlan(contextInput: PipelineContext, hash: string, files: Array<{
|
|
@@ -270,6 +285,11 @@ export declare function verificationInvocation(command: string, args: string[],
|
|
|
270
285
|
};
|
|
271
286
|
/** Shared persisted-output/provider diagnostic redaction; preserves line structure. */
|
|
272
287
|
export declare function redactRuntimeText(text: string, env?: NodeJS.ProcessEnv): string;
|
|
288
|
+
/** Structured shell execution with the same scope, environment and tree-kill rules, without a receipt or journal. */
|
|
289
|
+
export declare function executeVerificationCommand(contextInput: unknown, raw: unknown, log?: (text: string) => void, signal?: AbortSignal, options?: {
|
|
290
|
+
deadline?: number;
|
|
291
|
+
idleTimeoutMs?: number;
|
|
292
|
+
}): Promise<CommandReceipt>;
|
|
273
293
|
export interface VerificationSnapshot {
|
|
274
294
|
eligible: boolean;
|
|
275
295
|
reason: string;
|
|
@@ -280,12 +300,25 @@ export interface VerificationSnapshot {
|
|
|
280
300
|
export declare function verificationSnapshot(context: PipelineContext, command: VerificationCommand): VerificationSnapshot;
|
|
281
301
|
/** Contiguous waves preserve baseline order; an uncertain check is a barrier. */
|
|
282
302
|
export declare function verificationWaves<T extends VerificationCommand>(commands: T[], maxConcurrency?: number): T[][];
|
|
283
|
-
export
|
|
303
|
+
export interface VerificationRunOptions {
|
|
284
304
|
deadline?: number;
|
|
285
305
|
maxConcurrency?: number;
|
|
286
306
|
idleTimeoutMs?: number;
|
|
287
307
|
onEvidence?: (kind: 'check-started' | 'check-finished' | 'check-reused' | 'check-invalidated', payload: Record<string, string | number | null>) => Promise<void>;
|
|
288
|
-
}
|
|
308
|
+
}
|
|
309
|
+
/** Persistence and candidate ownership are bound by the host, never inferred from a journal path. */
|
|
310
|
+
export interface VerificationEvidencePort {
|
|
311
|
+
candidateHash: string;
|
|
312
|
+
scopeHash: string;
|
|
313
|
+
planHash?: string;
|
|
314
|
+
previous?: VerificationReceipt;
|
|
315
|
+
isCurrent(): boolean;
|
|
316
|
+
persistCheck(result: CommandReceipt, planHash: string, candidateHash: string): void | Promise<void>;
|
|
317
|
+
commitReceipt(receipt: VerificationReceipt): VerificationReceipt | Promise<VerificationReceipt>;
|
|
318
|
+
}
|
|
319
|
+
export declare function verifyPipeline(contextInput: unknown, raw: unknown, log?: (text: string) => void, signal?: AbortSignal, options?: VerificationRunOptions): Promise<VerificationReceipt>;
|
|
320
|
+
/** Same deterministic command/evidence runner for legacy journals and ledger-only engine runs. */
|
|
321
|
+
export declare function executeVerification(contextInput: unknown, raw: unknown, evidence: VerificationEvidencePort, log?: (text: string) => void, signal?: AbortSignal, options?: VerificationRunOptions): Promise<VerificationReceipt>;
|
|
289
322
|
/** Validate a check plan without creating state or executing commands. */
|
|
290
323
|
export declare function validateVerificationRequest(contextInput: unknown, raw: unknown): void;
|
|
291
324
|
export declare function preparePreview(contextInput: unknown, raw: unknown): PipelineState;
|
|
@@ -299,6 +332,7 @@ export interface VerificationEvidenceQuery {
|
|
|
299
332
|
cursor?: string;
|
|
300
333
|
limit?: number;
|
|
301
334
|
}
|
|
335
|
+
export declare function persistCheckEvidence(context: PipelineContext, result: CommandReceipt, planHash: string, candidateHash: string): void;
|
|
302
336
|
/** Historical read: no live repository validation, source execution or lifecycle mutation. */
|
|
303
337
|
export declare function readVerificationEvidence(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>, query?: VerificationEvidenceQuery): {
|
|
304
338
|
nextCursor?: string | undefined;
|
|
@@ -273,10 +273,56 @@ function saveState(state) {
|
|
|
273
273
|
atomicJson(stateFile(state.context), state);
|
|
274
274
|
}
|
|
275
275
|
function relativeUnix(value) { return value.split(path.sep).join('/'); }
|
|
276
|
+
/** Bind only engine-owned metadata paths; callers supply declared run/branch ownership. */
|
|
277
|
+
export function bindImplementationExclusions(contextInput, exclusions) {
|
|
278
|
+
const context = validatePipelineContext(contextInput);
|
|
279
|
+
const root = path.join(context.backlogRoot, '.specrails/pipeline');
|
|
280
|
+
const runtimeExclusions = [...new Set(exclusions.runtimeExclusions)].sort();
|
|
281
|
+
for (const item of runtimeExclusions) {
|
|
282
|
+
const relative = path.relative(root, item);
|
|
283
|
+
const memory = ['', '-wal', '-shm'].some(suffix => item === path.join(context.backlogRoot, '.specrails/engine-store.sqlite' + suffix));
|
|
284
|
+
if (memory) {
|
|
285
|
+
safeChild(context.backlogRoot, path.relative(context.backlogRoot, item));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!path.isAbsolute(item) || path.resolve(item) !== item || !ID.test(relative) || relative.includes(path.sep))
|
|
289
|
+
fail('Implementation runtime exclusion is not an admitted pipeline journal');
|
|
290
|
+
safeChild(root, relative);
|
|
291
|
+
}
|
|
292
|
+
const repositoryExclusions = {};
|
|
293
|
+
for (const [id, paths] of Object.entries(exclusions.repositoryExclusions)) {
|
|
294
|
+
const repository = context.repositories.find(repository => repository.id === id);
|
|
295
|
+
if (!repository)
|
|
296
|
+
continue; // A scoped child receives the parent union, filtered to its frozen repositories.
|
|
297
|
+
repositoryExclusions[id] = [...new Set(paths)].sort();
|
|
298
|
+
for (const item of repositoryExclusions[id]) {
|
|
299
|
+
if (!/^(?:[a-zA-Z0-9][a-zA-Z0-9._-]*\/)*openspec\/(?:changes|specs)\/[a-z0-9][a-z0-9./-]*$/.test(item))
|
|
300
|
+
fail('Implementation artifact exclusion is outside declared OpenSpec artifacts');
|
|
301
|
+
safeChild(repository.path, item);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
locked(context, () => {
|
|
305
|
+
const state = readState(context);
|
|
306
|
+
if (canonical(state.runtimeExclusions ?? []) === canonical(runtimeExclusions) && canonical(state.repositoryExclusions ?? {}) === canonical(repositoryExclusions))
|
|
307
|
+
return;
|
|
308
|
+
state.runtimeExclusions = runtimeExclusions;
|
|
309
|
+
state.repositoryExclusions = repositoryExclusions;
|
|
310
|
+
saveState(state);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** Read the validated implementation journal's dynamic candidate exclusions. */
|
|
314
|
+
export function readCandidateScope(contextInput) {
|
|
315
|
+
const state = readState(validatePipelineContext(contextInput));
|
|
316
|
+
return { context: state.context, scopeHash: state.scopeHash, artifactExclusions: [...state.artifactExclusions], ...(state.runtimeExclusions ? { runtimeExclusions: [...state.runtimeExclusions] } : {}), ...(state.repositoryExclusions ? { repositoryExclusions: structuredClone(state.repositoryExclusions) } : {}) };
|
|
317
|
+
}
|
|
276
318
|
function excluded(state, repo, relative) {
|
|
277
319
|
const absolute = path.join(repo.path, relative);
|
|
278
320
|
if (within(pipelineStateDirectory(state.context), absolute))
|
|
279
321
|
return true;
|
|
322
|
+
if (state.runtimeExclusions?.some(directory => within(directory, absolute)))
|
|
323
|
+
return true;
|
|
324
|
+
if (state.repositoryExclusions?.[repo.id]?.some(item => relative === item || relative.startsWith(item + '/')))
|
|
325
|
+
return true;
|
|
280
326
|
if (relative === '.specrails/runtime' || relative.startsWith('.specrails/runtime/'))
|
|
281
327
|
return true;
|
|
282
328
|
if (repo.id !== state.context.artifactRepositoryId)
|
|
@@ -805,6 +851,12 @@ function validateCommand(context, raw) {
|
|
|
805
851
|
export function verificationInvocation(command, args, cwd, platform = process.platform, env = process.env) {
|
|
806
852
|
if (platform !== 'win32')
|
|
807
853
|
return { command, args };
|
|
854
|
+
// An explicit shell line (`cmd /d /s /c <line>`) is already cmd syntax. Node's
|
|
855
|
+
// default quoting would escape its inner quotes with backslashes, which cmd does
|
|
856
|
+
// not understand. Pass it verbatim: /s strips exactly the outer quotes we add.
|
|
857
|
+
if (/(^|[\\/])cmd(\.exe)?$/i.test(command) && args.length === 4 && args[0].toLowerCase() === '/d' && args[1].toLowerCase() === '/s' && args[2].toLowerCase() === '/c') {
|
|
858
|
+
return { command, args: ['/d', '/s', '/c', '"' + args[3] + '"'], windowsVerbatimArguments: true };
|
|
859
|
+
}
|
|
808
860
|
let resolved = command;
|
|
809
861
|
if (!/\.(cmd|bat|exe|com)$/i.test(command)) {
|
|
810
862
|
const pathValue = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] ?? '';
|
|
@@ -845,6 +897,10 @@ export function redactRuntimeText(text, env = process.env) {
|
|
|
845
897
|
}
|
|
846
898
|
/** Silence a verification command may keep before it counts as hung (env `SPECRAILS_VERIFY_IDLE_TIMEOUT_MS`, default 5 min). */
|
|
847
899
|
function verificationIdleTimeoutMs() { const raw = Number(process.env.SPECRAILS_VERIFY_IDLE_TIMEOUT_MS); return Number.isFinite(raw) && raw >= 100 ? raw : 5 * 60_000; }
|
|
900
|
+
/** Structured shell execution with the same scope, environment and tree-kill rules, without a receipt or journal. */
|
|
901
|
+
export async function executeVerificationCommand(contextInput, raw, log = () => { }, signal, options = {}) {
|
|
902
|
+
return executeCheck(validateCommand(validatePipelineContext(contextInput), raw), log, signal, options.deadline, undefined, options.idleTimeoutMs);
|
|
903
|
+
}
|
|
848
904
|
async function executeCheck(command, log, signal, deadline, evidenceId = digest(randomUUID()), idleTimeoutOverrideMs) {
|
|
849
905
|
const started = Date.now();
|
|
850
906
|
const overrides = normalizeVerificationEnvironment(command.env ?? {});
|
|
@@ -1078,7 +1134,7 @@ export function verificationWaves(commands, maxConcurrency = 1) {
|
|
|
1078
1134
|
}
|
|
1079
1135
|
export async function verifyPipeline(contextInput, raw, log = () => { }, signal, options = {}) {
|
|
1080
1136
|
const context = validatePipelineContext(contextInput);
|
|
1081
|
-
const { request
|
|
1137
|
+
const { request } = verificationPlan(context, raw);
|
|
1082
1138
|
let previous;
|
|
1083
1139
|
const state = locked(context, () => {
|
|
1084
1140
|
const current = readState(context);
|
|
@@ -1097,6 +1153,33 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1097
1153
|
if (identityProblems.length)
|
|
1098
1154
|
fail(identityProblems.join('; '));
|
|
1099
1155
|
const candidateHash = fingerprintCandidate(state);
|
|
1156
|
+
const isCurrent = () => {
|
|
1157
|
+
const current = readState(context);
|
|
1158
|
+
return fingerprintCandidate(current) === candidateHash && current.revision === state.revision && planReasons(current, request.planHash).length === 0;
|
|
1159
|
+
};
|
|
1160
|
+
return executeVerification(context, raw, {
|
|
1161
|
+
candidateHash, scopeHash: state.scopeHash, planHash: state.verificationPlan?.hash, previous,
|
|
1162
|
+
isCurrent: () => locked(context, isCurrent),
|
|
1163
|
+
persistCheck: (result, planHash, candidate) => persistCheckEvidence(context, result, planHash, candidate),
|
|
1164
|
+
commitReceipt: receipt => locked(context, () => {
|
|
1165
|
+
const current = readState(context);
|
|
1166
|
+
if (!isCurrent())
|
|
1167
|
+
receipt = { ...receipt, valid: false, reason: 'Candidate changed during verification' };
|
|
1168
|
+
atomicJson(safeChild(pipelineStateDirectory(context), 'receipts/' + receipt.id + '.json'), receipt);
|
|
1169
|
+
if (receipt.kind === 'full' || !receipt.valid || !current.verification)
|
|
1170
|
+
current.verification = receipt;
|
|
1171
|
+
else if (previous?.valid && previous.candidateHash === candidateHash)
|
|
1172
|
+
current.verification = previous;
|
|
1173
|
+
saveState(current);
|
|
1174
|
+
return receipt;
|
|
1175
|
+
}),
|
|
1176
|
+
}, log, signal, options);
|
|
1177
|
+
}
|
|
1178
|
+
/** Same deterministic command/evidence runner for legacy journals and ledger-only engine runs. */
|
|
1179
|
+
export async function executeVerification(contextInput, raw, evidence, log = () => { }, signal, options = {}) {
|
|
1180
|
+
const context = validatePipelineContext(contextInput);
|
|
1181
|
+
const { request, commands, unverifiedRepositories } = verificationPlan(context, raw);
|
|
1182
|
+
const { candidateHash, previous } = evidence;
|
|
1100
1183
|
const results = [];
|
|
1101
1184
|
const controller = new AbortController();
|
|
1102
1185
|
const abort = () => controller.abort();
|
|
@@ -1116,7 +1199,7 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1116
1199
|
const evidenceId = digest(randomUUID());
|
|
1117
1200
|
if (!reuse) {
|
|
1118
1201
|
const pending = { repositoryId: command.repositoryId, key: command.key, label: command.label, command: command.command, args: command.args, cwd: command.cwd, environmentHash: '', environmentKeys: [], environmentOverrideKeys: [], environmentOverridesHash: '', evidenceId, pending: true, disposition: 'executed', outcome: 'interrupted', exitCode: -1, durationMs: 0, output: '' };
|
|
1119
|
-
|
|
1202
|
+
await evidence.persistCheck(pending, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1120
1203
|
await options.onEvidence?.('check-started', { executionId: evidenceId, repositoryId: command.repositoryId, checkId: command.key ?? '', label: command.label ?? command.command });
|
|
1121
1204
|
}
|
|
1122
1205
|
const result = reuse ? { ...old, evidenceId, disposition: 'reused', reusedFrom: old.evidenceId, durationMs: 0, snapshot, reuseReason: 'snapshot-local-identities-match' } : { ...await executeCheck(command, text => log(prefix + text), controller.signal, options.deadline, evidenceId, options.idleTimeoutMs), disposition: 'executed', snapshot, reuseReason: snapshot.eligible ? 'snapshot-local-no-current-match' : snapshot.reason };
|
|
@@ -1131,7 +1214,7 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1131
1214
|
}
|
|
1132
1215
|
if (result.exitCode !== 0)
|
|
1133
1216
|
controller.abort();
|
|
1134
|
-
|
|
1217
|
+
await evidence.persistCheck(result, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1135
1218
|
await options.onEvidence?.(reuse ? 'check-reused' : 'check-finished', { executionId: evidenceId, repositoryId: command.repositoryId, checkId: command.key ?? '', label: command.label ?? command.command, exitCode: result.exitCode, durationMs: result.durationMs, reason: result.reuseReason ?? null });
|
|
1136
1219
|
return result;
|
|
1137
1220
|
}
|
|
@@ -1160,25 +1243,15 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1160
1243
|
for (const command of commands.slice(results.length)) {
|
|
1161
1244
|
const id = digest(randomUUID());
|
|
1162
1245
|
const skipped = { repositoryId: command.repositoryId, key: command.key, label: command.label, command: command.command, args: command.args, cwd: command.cwd, environmentHash: '', environmentKeys: [], environmentOverrideKeys: [], environmentOverridesHash: '', evidenceId: id, disposition: 'not-run', outcome: 'cancelled', exitCode: -1, durationMs: 0, output: 'Not run after cancellation or an earlier failed check' };
|
|
1163
|
-
|
|
1246
|
+
await evidence.persistCheck(skipped, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1164
1247
|
notRunEvidenceIds.push(id);
|
|
1165
1248
|
}
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
...(changed ? { reason: 'Candidate changed during verification' } : results.some((result) => result.exitCode !== 0) ? { reason: 'A verification command failed' } : {}),
|
|
1173
|
-
...(unverifiedRepositories.length ? { unverifiedRepositories } : {}),
|
|
1174
|
-
};
|
|
1175
|
-
atomicJson(safeChild(pipelineStateDirectory(context), 'receipts/' + receipt.id + '.json'), receipt);
|
|
1176
|
-
if (receipt.kind === 'full' || !receipt.valid || !current.verification)
|
|
1177
|
-
current.verification = receipt;
|
|
1178
|
-
else if (previous?.valid && previous.candidateHash === candidateHash)
|
|
1179
|
-
current.verification = previous;
|
|
1180
|
-
saveState(current);
|
|
1181
|
-
return receipt;
|
|
1249
|
+
const changed = !evidence.isCurrent();
|
|
1250
|
+
const receipt = await evidence.commitReceipt({
|
|
1251
|
+
id: randomUUID(), ...(evidence.planHash ? { planHash: evidence.planHash } : {}), kind: request.kind, scopeHash: evidence.scopeHash, candidateHash, commands: results, ...(notRunEvidenceIds.length ? { notRunEvidenceIds } : {}),
|
|
1252
|
+
completedAt: new Date().toISOString(), valid: !changed && !signal?.aborted && (options.deadline === undefined || Date.now() <= options.deadline) && results.length === commands.length && results.every((result) => result.exitCode === 0),
|
|
1253
|
+
...(changed ? { reason: 'Candidate changed during verification' } : results.some((result) => result.exitCode !== 0) ? { reason: 'A verification command failed' } : {}),
|
|
1254
|
+
...(unverifiedRepositories.length ? { unverifiedRepositories } : {}),
|
|
1182
1255
|
});
|
|
1183
1256
|
if (!receipt.valid)
|
|
1184
1257
|
for (const result of results) {
|
|
@@ -1406,7 +1479,7 @@ function evidenceSummary(document) {
|
|
|
1406
1479
|
const { stdout: _out, stderr: _err, sources, ...summary } = document;
|
|
1407
1480
|
return { ...summary, sources: sources.map(({ text: _text, ...source }) => source) };
|
|
1408
1481
|
}
|
|
1409
|
-
function persistCheckEvidence(context, result, planHash, candidateHash) {
|
|
1482
|
+
export function persistCheckEvidence(context, result, planHash, candidateHash) {
|
|
1410
1483
|
const id = result.evidenceId;
|
|
1411
1484
|
const planPath = safeChild(pipelineStateDirectory(context), 'verification/plan.json');
|
|
1412
1485
|
const plan = existsSync(planPath) ? object(readJson(planPath)) : undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion": "5.
|
|
2
|
+
"schemaVersion": "5.1",
|
|
3
3
|
"providers": {
|
|
4
4
|
"claude": {
|
|
5
5
|
"cli": {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"initCommand": "init",
|
|
14
14
|
"workflows": {
|
|
15
15
|
"implement": "/specrails:implement",
|
|
16
|
-
"batch-implement": "/specrails:batch-implement",
|
|
17
16
|
"retry": "/specrails:retry"
|
|
18
17
|
}
|
|
19
18
|
},
|
|
@@ -29,7 +28,6 @@
|
|
|
29
28
|
"initCommand": "init",
|
|
30
29
|
"workflows": {
|
|
31
30
|
"implement": "$implement",
|
|
32
|
-
"batch-implement": "$batch-implement",
|
|
33
31
|
"retry": "$retry"
|
|
34
32
|
}
|
|
35
33
|
},
|
|
@@ -45,7 +43,6 @@
|
|
|
45
43
|
"initCommand": "init",
|
|
46
44
|
"workflows": {
|
|
47
45
|
"implement": "/specrails:implement",
|
|
48
|
-
"batch-implement": "/specrails:batch-implement",
|
|
49
46
|
"retry": "/specrails:retry"
|
|
50
47
|
}
|
|
51
48
|
},
|
|
@@ -85,12 +82,21 @@
|
|
|
85
82
|
"<model-id>",
|
|
86
83
|
"--args",
|
|
87
84
|
"<arguments>"
|
|
85
|
+
],
|
|
86
|
+
"renderOnlyArgs": [
|
|
87
|
+
".kimi-code/specrails/run-skill.mjs",
|
|
88
|
+
"--skill",
|
|
89
|
+
"<skill-id>",
|
|
90
|
+
"--model",
|
|
91
|
+
"<model-id>",
|
|
92
|
+
"--args",
|
|
93
|
+
"<arguments>",
|
|
94
|
+
"--render-only"
|
|
88
95
|
]
|
|
89
96
|
},
|
|
90
97
|
"initCommand": "init",
|
|
91
98
|
"workflows": {
|
|
92
99
|
"implement": "/skill:specrails-implement",
|
|
93
|
-
"batch-implement": "/skill:specrails-batch-implement",
|
|
94
100
|
"retry": "/skill:specrails-retry"
|
|
95
101
|
}
|
|
96
102
|
}
|
|
@@ -229,9 +235,19 @@
|
|
|
229
235
|
"validate",
|
|
230
236
|
"run",
|
|
231
237
|
"status",
|
|
232
|
-
"resume"
|
|
238
|
+
"resume",
|
|
239
|
+
"prompts",
|
|
240
|
+
"capabilities",
|
|
241
|
+
"evidence",
|
|
242
|
+
"recovery",
|
|
243
|
+
"evaluate",
|
|
244
|
+
"workflows",
|
|
245
|
+
"fork",
|
|
246
|
+
"signal",
|
|
247
|
+
"cancel"
|
|
233
248
|
],
|
|
234
|
-
"
|
|
249
|
+
"cliPresentationOperations": ["help"],
|
|
250
|
+
"apiOperation": "runtime api emits {type: runtime-api, apiVersion, coreVersion, runtimeIdentity, workflowVersions, engineVersion, engines, nodeKindsVersion, nodeKinds, capabilities, guardrails}; does not invoke providers",
|
|
235
251
|
"validationTransport": "runtime validate --stdin accepts UTF-8 configuration JSON up to 2 MiB, mutually exclusive with --config",
|
|
236
252
|
"compactStatus": "runtime status --compact omits workflow history, events and outputs, frozen pipeline context and verification command output; retains identities (runId, traceId), phase statuses and visits, pendingApproval, pendingQuestion, usage, completion, acceptance summary and verification metadata",
|
|
237
253
|
"interrupts": "A paused run (exit 2) carries pendingApproval (resume --approve <step>) or pendingQuestion (resume --answer <text>, nonempty, at most 20000 characters); the architect asks only after one autonomous investigation pass unless architect.onLowConfidence is proceed",
|
|
@@ -241,7 +257,7 @@
|
|
|
241
257
|
"configSchemaPath": "schemas/agent-runtime.schema.json",
|
|
242
258
|
"configSchemaExport": "specrails-core/schemas/agent-runtime.schema.json",
|
|
243
259
|
"projectConfigPath": ".specrails/agent-runtime.json",
|
|
244
|
-
"minimumNode": "
|
|
260
|
+
"minimumNode": "22.22.3",
|
|
245
261
|
"platforms": [
|
|
246
262
|
"darwin",
|
|
247
263
|
"win32"
|
|
@@ -260,6 +276,7 @@
|
|
|
260
276
|
"phases": [
|
|
261
277
|
"architect",
|
|
262
278
|
"developer",
|
|
279
|
+
"fixer",
|
|
263
280
|
"verify",
|
|
264
281
|
"reviewer",
|
|
265
282
|
"archive"
|
|
@@ -277,14 +294,69 @@
|
|
|
277
294
|
"specificationFormat": "Architect specs contain complete main-spec replacements, including unchanged requirements; archive does not merge partial delta snippets.",
|
|
278
295
|
"compatibility": "Additive to integration schema 4 and execution context schema 1. Legacy workflows and profile v1 remain supported. Hosts must check RUNTIME_API_VERSION, not infer runtime support from Core major version.",
|
|
279
296
|
"documentation": "docs/agent-runtime.md",
|
|
280
|
-
"instructionsVersion": "
|
|
281
|
-
"workflowVersion": "
|
|
297
|
+
"instructionsVersion": "10",
|
|
298
|
+
"workflowVersion": "7",
|
|
299
|
+
"engines": [1, 2],
|
|
300
|
+
"engine": {
|
|
301
|
+
"version": 2,
|
|
302
|
+
"definitionSchema": "schemas/workflow-definition.schema.json",
|
|
303
|
+
"nodeKindsVersion": 5
|
|
304
|
+
},
|
|
305
|
+
"nodeKinds": [
|
|
306
|
+
"prompt",
|
|
307
|
+
"role-turn",
|
|
308
|
+
"decider",
|
|
309
|
+
"verify",
|
|
310
|
+
"shell",
|
|
311
|
+
"openspec-validate",
|
|
312
|
+
"openspec-archive",
|
|
313
|
+
"condition",
|
|
314
|
+
"end",
|
|
315
|
+
"approval",
|
|
316
|
+
"question",
|
|
317
|
+
"gate",
|
|
318
|
+
"assign",
|
|
319
|
+
"component",
|
|
320
|
+
"map",
|
|
321
|
+
"implementation",
|
|
322
|
+
"join"
|
|
323
|
+
],
|
|
324
|
+
"engineStatePath": "<backlogRoot>/.specrails/pipeline/<runId>/agent-workflow/run.sqlite",
|
|
325
|
+
"frozenRequest": "agent-runtime-request.json carries workflow {id, version, source: builtin|definition, definitionHash, engine: 1|2}; resume and status select the engine from workflow.engine and a request without the block is a legacy (engine 1) run",
|
|
326
|
+
"builtins": [
|
|
327
|
+
{ "id": "specrails-implementation", "version": "7", "deprecated": false }
|
|
328
|
+
],
|
|
282
329
|
"developerTools": "The developer role runs with each CLI's autonomous edit+shell mode (claude --tools default --dangerously-skip-permissions with Agent/Task/Skill disallowed; codex workspace-write; gemini --yolo; kimi print mode). Architect and reviewer stay read-only. Claude roles load project settings only (--setting-sources project,local).",
|
|
283
330
|
"verificationPolicy": "Configured commands are optional. The architect proposes commands for repositories without one; repositories with no automated check are admitted and recorded in the receipt as unverifiedRepositories. Unchecked tasks return to the developer as feedback instead of failing the run.",
|
|
284
331
|
"sessionReuse": "Developer correction passes resume the previous provider session (claude --resume, codex exec resume, gemini --resume, kimi --session) with a short correction prompt; an unavailable session falls back to a fresh full turn. Structured roles get one repair turn in-session when their JSON is unusable.",
|
|
285
332
|
"structuredOutput": "Claude receives --json-schema and Codex --output-schema for architect/reviewer replies; other providers are parsed leniently.",
|
|
286
333
|
"resumeBudget": "An explicit resume resets the transition ceiling and grants the developer a fresh maxAttempts budget.",
|
|
287
|
-
"defaultImplementationEngine": true
|
|
334
|
+
"defaultImplementationEngine": true,
|
|
335
|
+
"openRoles": {
|
|
336
|
+
"version": 1,
|
|
337
|
+
"builtinRoles": [
|
|
338
|
+
"architect",
|
|
339
|
+
"developer",
|
|
340
|
+
"reviewer"
|
|
341
|
+
],
|
|
342
|
+
"configField": "roles",
|
|
343
|
+
"roleIdPattern": "^[a-z][a-z0-9-]{0,63}$",
|
|
344
|
+
"access": [
|
|
345
|
+
"read",
|
|
346
|
+
"write"
|
|
347
|
+
],
|
|
348
|
+
"artifacts": [
|
|
349
|
+
"none",
|
|
350
|
+
"tasks-checkboxes",
|
|
351
|
+
"all"
|
|
352
|
+
],
|
|
353
|
+
"instructions": [
|
|
354
|
+
"role",
|
|
355
|
+
"none"
|
|
356
|
+
],
|
|
357
|
+
"customAgentLoop": "free",
|
|
358
|
+
"nativeCommandUnsupported": "native_command_unsupported"
|
|
359
|
+
}
|
|
288
360
|
},
|
|
289
361
|
"execution": {
|
|
290
362
|
"schemaVersion": 1,
|