specrails-core 6.4.0 → 6.6.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/dist/agent-runtime/capabilities.d.ts +2 -2
- package/dist/agent-runtime/capabilities.js +22 -5
- package/dist/agent-runtime/cli-executor.js +8 -3
- package/dist/agent-runtime/cli.d.ts +4 -4
- package/dist/agent-runtime/cli.js +50 -3
- package/dist/agent-runtime/compact/environment.d.ts +50 -4
- package/dist/agent-runtime/compact/environment.js +183 -6
- package/dist/agent-runtime/compact/step.d.ts +1 -1
- package/dist/agent-runtime/efficiency-summary.d.ts +1 -1
- package/dist/agent-runtime/efficiency-types.d.ts +1 -1
- package/dist/agent-runtime/engine/checkpoint/database.d.ts +15 -15
- package/dist/agent-runtime/engine/contracts.d.ts +2 -1
- package/dist/agent-runtime/engine/definition-types.d.ts +1 -1
- package/dist/agent-runtime/engine/efficiency-summary.d.ts +8 -8
- package/dist/agent-runtime/engine/fork.d.ts +1 -1
- package/dist/agent-runtime/engine/graph-description.d.ts +16 -16
- package/dist/agent-runtime/engine/implementation-compiler.d.ts +68 -68
- package/dist/agent-runtime/engine/piece-dependencies.js +4 -4
- package/dist/agent-runtime/engine/pieces/implementation-step.d.ts +1 -1
- package/dist/agent-runtime/engine/pieces/role-turn.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/role-turn.js +64 -22
- package/dist/agent-runtime/engine/pieces/shared.d.ts +2 -2
- package/dist/agent-runtime/engine/pieces/verify.js +46 -13
- package/dist/agent-runtime/engine/run-status.d.ts +165 -165
- package/dist/agent-runtime/engine/runs.d.ts +168 -168
- package/dist/agent-runtime/engine/state.d.ts +16 -16
- package/dist/agent-runtime/evaluation.d.ts +3 -3
- package/dist/agent-runtime/executor-types.d.ts +10 -10
- package/dist/agent-runtime/graph/convergence.d.ts +2 -2
- package/dist/agent-runtime/graph/convergence.js +11 -0
- package/dist/agent-runtime/graph/nodes.js +35 -4
- package/dist/agent-runtime/graph/review-policy.d.ts +1 -1
- package/dist/agent-runtime/graph/state.d.ts +6 -2
- package/dist/agent-runtime/guardrails.d.ts +1 -1
- package/dist/agent-runtime/guardrails.js +4 -1
- package/dist/agent-runtime/index.d.ts +3 -2
- package/dist/agent-runtime/index.js +1 -0
- package/dist/agent-runtime/openai-executor.d.ts +9 -9
- package/dist/agent-runtime/openspec.d.ts +4 -4
- package/dist/agent-runtime/prompts.d.ts +6 -0
- package/dist/agent-runtime/prompts.js +32 -8
- package/dist/agent-runtime/recovery.d.ts +12 -12
- package/dist/agent-runtime/session/application/active-session.d.ts +176 -0
- package/dist/agent-runtime/session/application/active-session.js +701 -0
- package/dist/agent-runtime/session/application/delegation.d.ts +73 -0
- package/dist/agent-runtime/session/application/delegation.js +171 -0
- package/dist/agent-runtime/session/application/output-buffer.d.ts +43 -0
- package/dist/agent-runtime/session/application/output-buffer.js +65 -0
- package/dist/agent-runtime/session/application/session-service.d.ts +121 -0
- package/dist/agent-runtime/session/application/session-service.js +256 -0
- package/dist/agent-runtime/session/application/timers.d.ts +15 -0
- package/dist/agent-runtime/session/application/timers.js +35 -0
- package/dist/agent-runtime/session/domain/descriptor.d.ts +6 -0
- package/dist/agent-runtime/session/domain/descriptor.js +32 -0
- package/dist/agent-runtime/session/domain/errors.d.ts +9 -0
- package/dist/agent-runtime/session/domain/errors.js +16 -0
- package/dist/agent-runtime/session/domain/events.d.ts +147 -0
- package/dist/agent-runtime/session/domain/events.js +24 -0
- package/dist/agent-runtime/session/domain/fingerprint.d.ts +2 -0
- package/dist/agent-runtime/session/domain/fingerprint.js +10 -0
- package/dist/agent-runtime/session/domain/interruption.d.ts +13 -0
- package/dist/agent-runtime/session/domain/interruption.js +39 -0
- package/dist/agent-runtime/session/domain/policy.d.ts +42 -0
- package/dist/agent-runtime/session/domain/policy.js +104 -0
- package/dist/agent-runtime/session/domain/snapshot.d.ts +100 -0
- package/dist/agent-runtime/session/domain/snapshot.js +234 -0
- package/dist/agent-runtime/session/domain/state-machines.d.ts +21 -0
- package/dist/agent-runtime/session/domain/state-machines.js +41 -0
- package/dist/agent-runtime/session/domain/types.d.ts +130 -0
- package/dist/agent-runtime/session/domain/types.js +15 -0
- package/dist/agent-runtime/session/domain/usage.d.ts +54 -0
- package/dist/agent-runtime/session/domain/usage.js +86 -0
- package/dist/agent-runtime/session/drivers/claude/argv.d.ts +19 -0
- package/dist/agent-runtime/session/drivers/claude/argv.js +67 -0
- package/dist/agent-runtime/session/drivers/claude/driver.d.ts +19 -0
- package/dist/agent-runtime/session/drivers/claude/driver.js +134 -0
- package/dist/agent-runtime/session/drivers/claude/translator.d.ts +40 -0
- package/dist/agent-runtime/session/drivers/claude/translator.js +247 -0
- package/dist/agent-runtime/session/drivers/codex/argv.d.ts +12 -0
- package/dist/agent-runtime/session/drivers/codex/argv.js +76 -0
- package/dist/agent-runtime/session/drivers/codex/driver.d.ts +19 -0
- package/dist/agent-runtime/session/drivers/codex/driver.js +193 -0
- package/dist/agent-runtime/session/drivers/codex/rpc.d.ts +28 -0
- package/dist/agent-runtime/session/drivers/codex/rpc.js +81 -0
- package/dist/agent-runtime/session/drivers/codex/translator.d.ts +53 -0
- package/dist/agent-runtime/session/drivers/codex/translator.js +267 -0
- package/dist/agent-runtime/session/drivers/common/process.d.ts +37 -0
- package/dist/agent-runtime/session/drivers/common/process.js +116 -0
- package/dist/agent-runtime/session/drivers/executor/driver.d.ts +22 -0
- package/dist/agent-runtime/session/drivers/executor/driver.js +163 -0
- package/dist/agent-runtime/session/drivers/registry.d.ts +32 -0
- package/dist/agent-runtime/session/drivers/registry.js +38 -0
- package/dist/agent-runtime/session/host/protocol.d.ts +18 -0
- package/dist/agent-runtime/session/host/protocol.js +88 -0
- package/dist/agent-runtime/session/host/server.d.ts +57 -0
- package/dist/agent-runtime/session/host/server.js +229 -0
- package/dist/agent-runtime/session/index.d.ts +45 -0
- package/dist/agent-runtime/session/index.js +74 -0
- package/dist/agent-runtime/session/journal/schema.d.ts +7 -0
- package/dist/agent-runtime/session/journal/schema.js +44 -0
- package/dist/agent-runtime/session/journal/sqlite-journal.d.ts +70 -0
- package/dist/agent-runtime/session/journal/sqlite-journal.js +279 -0
- package/dist/agent-runtime/session/ports.d.ts +192 -0
- package/dist/agent-runtime/session/ports.js +1 -0
- package/dist/agent-runtime/session/testing/driver-conformance.d.ts +21 -0
- package/dist/agent-runtime/session/testing/driver-conformance.js +97 -0
- package/dist/agent-runtime/session/testing/fake-clock.d.ts +20 -0
- package/dist/agent-runtime/session/testing/fake-clock.js +34 -0
- package/dist/agent-runtime/session/testing/fixture-replayer.d.ts +41 -0
- package/dist/agent-runtime/session/testing/fixture-replayer.js +172 -0
- package/dist/agent-runtime/session/testing/index.d.ts +9 -0
- package/dist/agent-runtime/session/testing/index.js +9 -0
- package/dist/agent-runtime/session/testing/memory-journal.d.ts +27 -0
- package/dist/agent-runtime/session/testing/memory-journal.js +70 -0
- package/dist/agent-runtime/session/testing/scripted-driver.d.ts +30 -0
- package/dist/agent-runtime/session/testing/scripted-driver.js +77 -0
- package/dist/agent-runtime/verification-proposals.d.ts +9 -0
- package/dist/agent-runtime/verification-proposals.js +69 -0
- package/dist/agent-runtime/verification-repair.d.ts +31 -2
- package/dist/agent-runtime/verification-repair.js +58 -3
- package/dist/installer/commands/init.d.ts +1 -1
- package/dist/installer/phases/install-config.js +10 -3
- package/dist/installer/util/paths.d.ts +25 -1
- package/dist/installer/util/registry.js +3 -3
- package/dist/pipeline/pipeline-state.d.ts +42 -11
- package/dist/pipeline/pipeline-state.js +47 -8
- package/dist/shared/specrails-home.d.ts +21 -0
- package/dist/shared/specrails-home.js +36 -0
- package/integration-contract.json +42 -1
- package/package.json +23 -11
- package/schemas/agent-runtime.schema.json +1 -1
|
@@ -106,7 +106,7 @@ export declare function buildOpenSpecInvocation(repoRoot: string, provider: Prov
|
|
|
106
106
|
bin: string;
|
|
107
107
|
args: string[];
|
|
108
108
|
};
|
|
109
|
-
export declare const KIMI_REQUIRED_OPENSPEC_SKILLS: readonly [
|
|
109
|
+
export declare const KIMI_REQUIRED_OPENSPEC_SKILLS: readonly ['openspec-propose', 'openspec-explore', 'openspec-new-change', 'openspec-continue-change', 'openspec-apply-change', 'openspec-ff-change', 'openspec-sync-specs', 'openspec-archive-change', 'openspec-bulk-archive-change', 'openspec-verify-change', 'openspec-onboard'];
|
|
110
110
|
/**
|
|
111
111
|
* Move only OpenSpec-owned Kimi workflow directories from either upstream
|
|
112
112
|
* location into the artifact workspace. Every destination is copied to a
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
2
|
+
import { dump, loadAll } from 'js-yaml';
|
|
3
3
|
import { FilesystemError, InstallerError } from '../util/errors.js';
|
|
4
4
|
import { pathExists, readTextFile, writeFileLf } from '../util/fs.js';
|
|
5
5
|
import { warn } from '../util/logger.js';
|
|
@@ -72,7 +72,14 @@ export function loadInstallConfig(configPath) {
|
|
|
72
72
|
const raw = readTextFile(configPath);
|
|
73
73
|
let parsed;
|
|
74
74
|
try {
|
|
75
|
-
|
|
75
|
+
// js-yaml 5 `load()` throws on a stream with no document (empty or
|
|
76
|
+
// comment-only file). Keep reporting that as a non-mapping config, and
|
|
77
|
+
// keep rejecting multi-document streams as `load()` did.
|
|
78
|
+
const documents = loadAll(raw);
|
|
79
|
+
if (documents.length > 1) {
|
|
80
|
+
throw new Error('expected a single document in the stream, but found more');
|
|
81
|
+
}
|
|
82
|
+
parsed = documents[0];
|
|
76
83
|
}
|
|
77
84
|
catch (err) {
|
|
78
85
|
throw new InvalidConfigError([`YAML parse error: ${err.message}`]);
|
|
@@ -258,7 +265,7 @@ function isExactModelIdentifier(value) {
|
|
|
258
265
|
*/
|
|
259
266
|
export function writeInstallConfig(configPath, config) {
|
|
260
267
|
try {
|
|
261
|
-
const text =
|
|
268
|
+
const text = dump(config, { lineWidth: 120, noRefs: true });
|
|
262
269
|
writeFileLf(configPath, text);
|
|
263
270
|
}
|
|
264
271
|
catch (err) {
|
|
@@ -6,7 +6,31 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Audited by vitest spec `reserved-paths.test.ts`.
|
|
8
8
|
*/
|
|
9
|
-
export declare const RESERVED_PATHS: readonly [
|
|
9
|
+
export declare const RESERVED_PATHS: readonly [
|
|
10
|
+
/**
|
|
11
|
+
* .specrails/profiles/** — project + desktop-app-authored profile JSON.
|
|
12
|
+
* specrails-desktop writes profile files here and expects them preserved
|
|
13
|
+
* across specrails-core updates.
|
|
14
|
+
*/
|
|
15
|
+
'.specrails/profiles/',
|
|
16
|
+
/**
|
|
17
|
+
* .claude/agents/custom-*.md — user-authored custom agents, kept
|
|
18
|
+
* distinct from the bundled sr-* agents so updates never overwrite
|
|
19
|
+
* them.
|
|
20
|
+
*/
|
|
21
|
+
'.claude/agents/custom-',
|
|
22
|
+
/**
|
|
23
|
+
* `.kimi-code/skills/custom-<id>/...` — user-authored Kimi role skills.
|
|
24
|
+
* Kimi discovers skills only one directory below `skills/`, so custom roles
|
|
25
|
+
* share the flat namespace with managed `sr-*` and workflow skills.
|
|
26
|
+
*/
|
|
27
|
+
'.kimi-code/skills/custom-',
|
|
28
|
+
/**
|
|
29
|
+
* Pre-release compatibility: preserve a legacy nested custom role until the
|
|
30
|
+
* installer can atomically migrate it to the flat path. A destination
|
|
31
|
+
* collision is never overwritten or deleted automatically.
|
|
32
|
+
*/
|
|
33
|
+
'.kimi-code/skills/rails/custom-'];
|
|
10
34
|
/**
|
|
11
35
|
* Returns true when a repo-relative path falls inside a reserved
|
|
12
36
|
* region. Accepts both POSIX (/) and Windows (\) separators; the
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Everything in this module is synchronous to fit the installer's sync flow.
|
|
18
18
|
*/
|
|
19
19
|
import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
20
|
-
import os from 'node:os';
|
|
21
20
|
import path from 'node:path';
|
|
21
|
+
import { userHome } from '../../shared/specrails-home.js';
|
|
22
22
|
/** Registry schema version. A reader that sees a higher value MUST treat all
|
|
23
23
|
* entries as absent (legacy fallback), never mis-parse. */
|
|
24
24
|
export const REGISTRY_SCHEMA_VERSION = 1;
|
|
@@ -190,9 +190,9 @@ function isValidProjectEntry(key, value, home) {
|
|
|
190
190
|
export function slugify(name) {
|
|
191
191
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
192
192
|
}
|
|
193
|
-
/** `$HOME` for the registry, overridable for tests. */
|
|
193
|
+
/** `$HOME` for the registry, overridable for tests (shared with session journals). */
|
|
194
194
|
function resolveHome(home) {
|
|
195
|
-
return home
|
|
195
|
+
return userHome(home);
|
|
196
196
|
}
|
|
197
197
|
/** Absolute path to `registry.json`. */
|
|
198
198
|
export function registryPath(home) {
|
|
@@ -107,6 +107,22 @@ export interface VerificationReceipt {
|
|
|
107
107
|
reason?: string;
|
|
108
108
|
/** Repositories in scope that this receipt ran no command for (explicitly admitted by the request). */
|
|
109
109
|
unverifiedRepositories?: string[];
|
|
110
|
+
/** Candidate files the verification commands themselves added, modified or removed (absent when the candidate did not move). */
|
|
111
|
+
selfMutation?: VerificationSelfMutation;
|
|
112
|
+
}
|
|
113
|
+
export interface CandidateFileChange {
|
|
114
|
+
repositoryId: string;
|
|
115
|
+
path: string;
|
|
116
|
+
change: 'added' | 'modified' | 'removed';
|
|
117
|
+
}
|
|
118
|
+
/** What verification wrote into its own candidate: the files (bounded, `omittedFiles` counts the rest) and the commands that executed in the run. */
|
|
119
|
+
export interface VerificationSelfMutation {
|
|
120
|
+
files: CandidateFileChange[];
|
|
121
|
+
omittedFiles?: number;
|
|
122
|
+
commands: Array<{
|
|
123
|
+
repositoryId: string;
|
|
124
|
+
label: string;
|
|
125
|
+
}>;
|
|
110
126
|
}
|
|
111
127
|
export interface AcceptanceCriterion {
|
|
112
128
|
specId: string;
|
|
@@ -232,7 +248,19 @@ export interface CandidateManifest {
|
|
|
232
248
|
}>;
|
|
233
249
|
}
|
|
234
250
|
export declare function candidateManifest(state: CandidateScope): CandidateManifest;
|
|
251
|
+
export declare function candidateManifestHash(manifest: CandidateManifest): string;
|
|
235
252
|
export declare function fingerprintCandidate(state: CandidateScope): string;
|
|
253
|
+
/** Files that differ between two manifests of the same scope, sorted by repository, then path. */
|
|
254
|
+
export declare function diffCandidateManifests(before: CandidateManifest, after: CandidateManifest): CandidateFileChange[];
|
|
255
|
+
/** `1 candidate file: a` / `4 candidate files: a, b, c and 1 more`, shared by receipts and failure summaries. */
|
|
256
|
+
export declare function describeModifiedFiles(mutation: Pick<VerificationSelfMutation, 'files' | 'omittedFiles'>): string;
|
|
257
|
+
/**
|
|
258
|
+
* A receipt whose candidate moved while it ran. During a verify step no role
|
|
259
|
+
* turn runs, so a manifest diff around the run attributes the change to the
|
|
260
|
+
* verification commands themselves (a lint pre-step regenerating a tracked
|
|
261
|
+
* file); without both manifests the generic reason stays.
|
|
262
|
+
*/
|
|
263
|
+
export declare function candidateChangedReceipt(receipt: VerificationReceipt, before: CandidateManifest | undefined, after: (() => CandidateManifest) | undefined): VerificationReceipt;
|
|
236
264
|
export declare function initializePipeline(contextInput: unknown, change: string): PipelineState;
|
|
237
265
|
/** Host-owned binding, including immutable harness bytes outside the candidate. */
|
|
238
266
|
export declare function bindVerificationPlan(contextInput: PipelineContext, hash: string, files: Array<{
|
|
@@ -309,6 +337,9 @@ export interface VerificationRunOptions {
|
|
|
309
337
|
/** Persistence and candidate ownership are bound by the host, never inferred from a journal path. */
|
|
310
338
|
export interface VerificationEvidencePort {
|
|
311
339
|
candidateHash: string;
|
|
340
|
+
/** The manifest `candidateHash` was computed from; with `currentManifest`, an invalidated receipt names the files verification modified. */
|
|
341
|
+
candidateManifest?: CandidateManifest;
|
|
342
|
+
currentManifest?(): CandidateManifest;
|
|
312
343
|
scopeHash: string;
|
|
313
344
|
planHash?: string;
|
|
314
345
|
previous?: VerificationReceipt;
|
|
@@ -335,21 +366,15 @@ export interface VerificationEvidenceQuery {
|
|
|
335
366
|
export declare function persistCheckEvidence(context: PipelineContext, result: CommandReceipt, planHash: string, candidateHash: string): void;
|
|
336
367
|
/** Historical read: no live repository validation, source execution or lifecycle mutation. */
|
|
337
368
|
export declare function readVerificationEvidence(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>, query?: VerificationEvidenceQuery): {
|
|
338
|
-
nextCursor?: string | undefined;
|
|
339
369
|
schemaVersion: number;
|
|
340
370
|
available: boolean;
|
|
341
371
|
items: Record<string, unknown>[];
|
|
342
372
|
truncated: boolean;
|
|
373
|
+
nextCursor?: string | undefined;
|
|
343
374
|
} | {
|
|
344
375
|
schemaVersion: number;
|
|
345
376
|
available: boolean;
|
|
346
377
|
items: {
|
|
347
|
-
sources: {
|
|
348
|
-
id: string;
|
|
349
|
-
displayPath: string;
|
|
350
|
-
byteCount: number;
|
|
351
|
-
hash: string;
|
|
352
|
-
}[];
|
|
353
378
|
schemaVersion: 1;
|
|
354
379
|
runId: string;
|
|
355
380
|
id: string;
|
|
@@ -358,9 +383,9 @@ export declare function readVerificationEvidence(context: Pick<PipelineContext,
|
|
|
358
383
|
label: string;
|
|
359
384
|
origin: string[];
|
|
360
385
|
required: true;
|
|
361
|
-
disposition:
|
|
386
|
+
disposition: 'executed' | 'reused' | 'not-run';
|
|
362
387
|
reusedFrom?: string;
|
|
363
|
-
status:
|
|
388
|
+
status: 'passed' | 'failed' | 'cancelled' | 'interrupted' | 'unavailable';
|
|
364
389
|
reuseReason?: string;
|
|
365
390
|
recordedAt: string;
|
|
366
391
|
execution: {
|
|
@@ -382,16 +407,22 @@ export declare function readVerificationEvidence(context: Pick<PipelineContext,
|
|
|
382
407
|
stderrBytes: number;
|
|
383
408
|
stdoutHash: string;
|
|
384
409
|
stderrHash: string;
|
|
410
|
+
sources: {
|
|
411
|
+
id: string;
|
|
412
|
+
displayPath: string;
|
|
413
|
+
byteCount: number;
|
|
414
|
+
hash: string;
|
|
415
|
+
}[];
|
|
385
416
|
}[];
|
|
386
417
|
truncated: boolean;
|
|
387
418
|
} | {
|
|
388
|
-
|
|
419
|
+
items?: undefined;
|
|
389
420
|
schemaVersion: number;
|
|
390
421
|
available: boolean;
|
|
391
422
|
text: string;
|
|
392
423
|
byteCount: number;
|
|
393
424
|
totalBytes: number;
|
|
394
425
|
truncated: boolean;
|
|
395
|
-
|
|
426
|
+
nextCursor?: string | undefined;
|
|
396
427
|
};
|
|
397
428
|
export {};
|
|
@@ -407,7 +407,45 @@ export function candidateManifest(state) {
|
|
|
407
407
|
}));
|
|
408
408
|
return { schemaVersion: 1, scopeHash: state.scopeHash, repositories: entries };
|
|
409
409
|
}
|
|
410
|
-
export function
|
|
410
|
+
export function candidateManifestHash(manifest) { return digest(canonical(manifest.repositories)); }
|
|
411
|
+
export function fingerprintCandidate(state) { return candidateManifestHash(candidateManifest(state)); }
|
|
412
|
+
/** Files that differ between two manifests of the same scope, sorted by repository, then path. */
|
|
413
|
+
export function diffCandidateManifests(before, after) {
|
|
414
|
+
const changes = [];
|
|
415
|
+
for (const repositoryId of [...new Set([...before.repositories, ...after.repositories].map(repo => repo.id))].sort()) {
|
|
416
|
+
const old = new Map(before.repositories.find(repo => repo.id === repositoryId)?.files ?? []);
|
|
417
|
+
const current = new Map(after.repositories.find(repo => repo.id === repositoryId)?.files ?? []);
|
|
418
|
+
for (const file of [...new Set([...old.keys(), ...current.keys()])].sort()) {
|
|
419
|
+
const change = !old.has(file) ? 'added' : !current.has(file) ? 'removed' : old.get(file) !== current.get(file) ? 'modified' : undefined;
|
|
420
|
+
if (change)
|
|
421
|
+
changes.push({ repositoryId, path: file, change });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return changes;
|
|
425
|
+
}
|
|
426
|
+
const SELF_MUTATION_FILE_LIMIT = 200;
|
|
427
|
+
/** `1 candidate file: a` / `4 candidate files: a, b, c and 1 more`, shared by receipts and failure summaries. */
|
|
428
|
+
export function describeModifiedFiles(mutation) {
|
|
429
|
+
const total = mutation.files.length + (mutation.omittedFiles ?? 0), shown = mutation.files.slice(0, 3).map(file => file.path);
|
|
430
|
+
return `${total} candidate file${total === 1 ? '' : 's'}: ${shown.join(', ')}${total > shown.length ? ` and ${total - shown.length} more` : ''}`;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* A receipt whose candidate moved while it ran. During a verify step no role
|
|
434
|
+
* turn runs, so a manifest diff around the run attributes the change to the
|
|
435
|
+
* verification commands themselves (a lint pre-step regenerating a tracked
|
|
436
|
+
* file); without both manifests the generic reason stays.
|
|
437
|
+
*/
|
|
438
|
+
export function candidateChangedReceipt(receipt, before, after) {
|
|
439
|
+
if (receipt.selfMutation)
|
|
440
|
+
return { ...receipt, valid: false };
|
|
441
|
+
const files = before && after ? diffCandidateManifests(before, after()) : [];
|
|
442
|
+
if (!files.length)
|
|
443
|
+
return { ...receipt, valid: false, reason: 'Candidate changed during verification' };
|
|
444
|
+
const selfMutation = { files: files.slice(0, SELF_MUTATION_FILE_LIMIT), ...(files.length > SELF_MUTATION_FILE_LIMIT ? { omittedFiles: files.length - SELF_MUTATION_FILE_LIMIT } : {}),
|
|
445
|
+
commands: receipt.commands.filter(command => command.disposition !== 'reused' && command.disposition !== 'not-run').map(command => ({ repositoryId: command.repositoryId, label: command.label ?? command.key ?? [command.command, ...command.args].join(' ').slice(0, 256) })) };
|
|
446
|
+
const failed = receipt.commands.some(command => command.exitCode !== 0);
|
|
447
|
+
return { ...receipt, valid: false, reason: `${failed ? 'A verification command failed. ' : ''}Verification commands modified ${describeModifiedFiles(selfMutation)}`, selfMutation };
|
|
448
|
+
}
|
|
411
449
|
function activeArtifactPath(state) { return state.archivePath ?? path.join(state.context.artifactRoot, 'openspec', 'changes', state.change); }
|
|
412
450
|
function artifactFingerprint(state) {
|
|
413
451
|
const root = activeArtifactPath(state);
|
|
@@ -1154,19 +1192,19 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1154
1192
|
const identityProblems = planReasons(state, typeof request.planHash === 'string' ? request.planHash : undefined);
|
|
1155
1193
|
if (identityProblems.length)
|
|
1156
1194
|
fail(identityProblems.join('; '));
|
|
1157
|
-
const
|
|
1195
|
+
const manifest = candidateManifest(state), candidateHash = candidateManifestHash(manifest);
|
|
1158
1196
|
const isCurrent = () => {
|
|
1159
1197
|
const current = readState(context);
|
|
1160
1198
|
return fingerprintCandidate(current) === candidateHash && current.revision === state.revision && planReasons(current, request.planHash).length === 0;
|
|
1161
1199
|
};
|
|
1162
1200
|
return executeVerification(context, raw, {
|
|
1163
|
-
candidateHash, scopeHash: state.scopeHash, planHash: state.verificationPlan?.hash, previous,
|
|
1201
|
+
candidateHash, candidateManifest: manifest, currentManifest: () => candidateManifest(readState(context)), scopeHash: state.scopeHash, planHash: state.verificationPlan?.hash, previous,
|
|
1164
1202
|
isCurrent: () => locked(context, isCurrent),
|
|
1165
1203
|
persistCheck: (result, planHash, candidate) => persistCheckEvidence(context, result, planHash, candidate),
|
|
1166
1204
|
commitReceipt: receipt => locked(context, () => {
|
|
1167
1205
|
const current = readState(context);
|
|
1168
1206
|
if (!isCurrent())
|
|
1169
|
-
receipt =
|
|
1207
|
+
receipt = candidateChangedReceipt(receipt, manifest, () => candidateManifest(current));
|
|
1170
1208
|
atomicJson(safeChild(pipelineStateDirectory(context), 'receipts/' + receipt.id + '.json'), receipt);
|
|
1171
1209
|
if (receipt.kind === 'full' || !receipt.valid || !current.verification)
|
|
1172
1210
|
current.verification = receipt;
|
|
@@ -1249,12 +1287,13 @@ export async function executeVerification(contextInput, raw, evidence, log = ()
|
|
|
1249
1287
|
notRunEvidenceIds.push(id);
|
|
1250
1288
|
}
|
|
1251
1289
|
const changed = !evidence.isCurrent();
|
|
1252
|
-
const
|
|
1290
|
+
const draft = {
|
|
1253
1291
|
id: randomUUID(), ...(evidence.planHash ? { planHash: evidence.planHash } : {}), kind: request.kind, scopeHash: evidence.scopeHash, candidateHash, commands: results, ...(notRunEvidenceIds.length ? { notRunEvidenceIds } : {}),
|
|
1254
|
-
completedAt: new Date().toISOString(), valid: !
|
|
1255
|
-
...(
|
|
1292
|
+
completedAt: new Date().toISOString(), valid: !signal?.aborted && (options.deadline === undefined || Date.now() <= options.deadline) && results.length === commands.length && results.every((result) => result.exitCode === 0),
|
|
1293
|
+
...(results.some((result) => result.exitCode !== 0) ? { reason: 'A verification command failed' } : {}),
|
|
1256
1294
|
...(unverifiedRepositories.length ? { unverifiedRepositories } : {}),
|
|
1257
|
-
}
|
|
1295
|
+
};
|
|
1296
|
+
const receipt = await evidence.commitReceipt(changed ? candidateChangedReceipt(draft, evidence.candidateManifest, evidence.currentManifest && (() => evidence.currentManifest())) : draft);
|
|
1258
1297
|
if (!receipt.valid)
|
|
1259
1298
|
for (const result of results) {
|
|
1260
1299
|
await options.onEvidence?.('check-invalidated', { executionId: result.evidenceId ?? '', repositoryId: result.repositoryId, checkId: result.key ?? '', label: result.label ?? result.command, reason: receipt.reason ?? 'Verification did not complete successfully' });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The user-level Specrails home. Registry, framework store, workspaces and
|
|
3
|
+
* agent session journals all live under `<home>/.specrails`, never inside a
|
|
4
|
+
* project repository.
|
|
5
|
+
*
|
|
6
|
+
* `SPECRAILS_REGISTRY_HOME` overrides the base directory so tests (and
|
|
7
|
+
* isolated installs) redirect every user-level artifact together.
|
|
8
|
+
*/
|
|
9
|
+
export declare function userHome(home?: string): string;
|
|
10
|
+
/** Absolute path to `<home>/.specrails`. */
|
|
11
|
+
export declare function specrailsHome(home?: string): string;
|
|
12
|
+
/** Scope used by session hosts that are not bound to a project. */
|
|
13
|
+
export declare const GLOBAL_SESSION_SCOPE = "global";
|
|
14
|
+
/** True when `scope` is a valid session scope key (a project slug or `global`). */
|
|
15
|
+
export declare function isSessionScopeKey(scope: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Directory that holds every agent session journal of one scope:
|
|
18
|
+
* `<home>/.specrails/sessions/<scope>/`. Sessions are grouped per project so a
|
|
19
|
+
* project's history can be inspected, backed up or removed as one unit.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sessionsRoot(scope: string, home?: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* The user-level Specrails home. Registry, framework store, workspaces and
|
|
5
|
+
* agent session journals all live under `<home>/.specrails`, never inside a
|
|
6
|
+
* project repository.
|
|
7
|
+
*
|
|
8
|
+
* `SPECRAILS_REGISTRY_HOME` overrides the base directory so tests (and
|
|
9
|
+
* isolated installs) redirect every user-level artifact together.
|
|
10
|
+
*/
|
|
11
|
+
export function userHome(home) {
|
|
12
|
+
return home ?? process.env.SPECRAILS_REGISTRY_HOME ?? os.homedir();
|
|
13
|
+
}
|
|
14
|
+
/** Absolute path to `<home>/.specrails`. */
|
|
15
|
+
export function specrailsHome(home) {
|
|
16
|
+
return path.join(userHome(home), '.specrails');
|
|
17
|
+
}
|
|
18
|
+
/** Scope used by session hosts that are not bound to a project. */
|
|
19
|
+
export const GLOBAL_SESSION_SCOPE = 'global';
|
|
20
|
+
/** Same alphabet the registry uses for project slugs, bounded for file names. */
|
|
21
|
+
const SCOPE_KEY = /^[a-z0-9](?:[a-z0-9-]{0,126}[a-z0-9])?$/;
|
|
22
|
+
/** True when `scope` is a valid session scope key (a project slug or `global`). */
|
|
23
|
+
export function isSessionScopeKey(scope) {
|
|
24
|
+
return SCOPE_KEY.test(scope);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Directory that holds every agent session journal of one scope:
|
|
28
|
+
* `<home>/.specrails/sessions/<scope>/`. Sessions are grouped per project so a
|
|
29
|
+
* project's history can be inspected, backed up or removed as one unit.
|
|
30
|
+
*/
|
|
31
|
+
export function sessionsRoot(scope, home) {
|
|
32
|
+
if (!isSessionScopeKey(scope)) {
|
|
33
|
+
throw new Error(`Invalid session scope "${scope}": use a project slug (a-z, 0-9, "-") or "${GLOBAL_SESSION_SCOPE}"`);
|
|
34
|
+
}
|
|
35
|
+
return path.join(specrailsHome(home), 'sessions', scope);
|
|
36
|
+
}
|
|
@@ -241,7 +241,8 @@
|
|
|
241
241
|
"workflows",
|
|
242
242
|
"fork",
|
|
243
243
|
"signal",
|
|
244
|
-
"cancel"
|
|
244
|
+
"cancel",
|
|
245
|
+
"host"
|
|
245
246
|
],
|
|
246
247
|
"cliPresentationOperations": [
|
|
247
248
|
"help"
|
|
@@ -373,6 +374,46 @@
|
|
|
373
374
|
"workflowAgentSteps": {
|
|
374
375
|
"version": 1,
|
|
375
376
|
"ownership": "Host-defined roles, schemas and gates; Core provides scoped artifact contracts and verified candidate guards"
|
|
377
|
+
},
|
|
378
|
+
"sessions": {
|
|
379
|
+
"version": 1,
|
|
380
|
+
"protocolVersions": [
|
|
381
|
+
1
|
|
382
|
+
],
|
|
383
|
+
"capability": "sessions",
|
|
384
|
+
"cliOperation": "host",
|
|
385
|
+
"cliCommand": "specrails-core runtime host --stdio --scope <projectKey|global>",
|
|
386
|
+
"transport": "JSON-RPC 2.0 over newline-delimited stdio; one host per scope",
|
|
387
|
+
"journalRoot": "~/.specrails/sessions/<scope>/sessions.sqlite",
|
|
388
|
+
"moduleExport": "./agent-runtime/session",
|
|
389
|
+
"documentation": "docs/agent-sessions/protocol.md",
|
|
390
|
+
"eventTypes": [
|
|
391
|
+
"session.opened",
|
|
392
|
+
"session.phase",
|
|
393
|
+
"session.process",
|
|
394
|
+
"session.provider-ref",
|
|
395
|
+
"session.updated",
|
|
396
|
+
"session.closed",
|
|
397
|
+
"input.accepted",
|
|
398
|
+
"input.state",
|
|
399
|
+
"turn.started",
|
|
400
|
+
"turn.output",
|
|
401
|
+
"turn.tool",
|
|
402
|
+
"turn.completed",
|
|
403
|
+
"subagent.started",
|
|
404
|
+
"subagent.phase",
|
|
405
|
+
"subagent.output",
|
|
406
|
+
"subagent.usage",
|
|
407
|
+
"subagent.result",
|
|
408
|
+
"subagents.settled",
|
|
409
|
+
"output.truncated",
|
|
410
|
+
"notice.interruption",
|
|
411
|
+
"provider.diagnostic"
|
|
412
|
+
],
|
|
413
|
+
"capabilities": [
|
|
414
|
+
"sessions",
|
|
415
|
+
"delegation"
|
|
416
|
+
]
|
|
376
417
|
}
|
|
377
418
|
},
|
|
378
419
|
"execution": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specrails-core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.6.0",
|
|
4
4
|
"description": "Agent workflow engine for specrails-desktop: provider-independent installer and programmatic agent runtime for Claude Code and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
"import": "./dist/agent-runtime/engine/index.js",
|
|
15
15
|
"default": "./dist/agent-runtime/engine/index.js"
|
|
16
16
|
},
|
|
17
|
+
"./agent-runtime/session": {
|
|
18
|
+
"types": "./dist/agent-runtime/session/index.d.ts",
|
|
19
|
+
"import": "./dist/agent-runtime/session/index.js",
|
|
20
|
+
"default": "./dist/agent-runtime/session/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./agent-runtime/session/testing": {
|
|
23
|
+
"types": "./dist/agent-runtime/session/testing/index.d.ts",
|
|
24
|
+
"import": "./dist/agent-runtime/session/testing/index.js",
|
|
25
|
+
"default": "./dist/agent-runtime/session/testing/index.js"
|
|
26
|
+
},
|
|
17
27
|
"./package.json": "./package.json",
|
|
18
28
|
"./integration-contract.json": "./integration-contract.json",
|
|
19
29
|
"./schemas/agent-runtime.schema.json": "./schemas/agent-runtime.schema.json",
|
|
@@ -79,23 +89,25 @@
|
|
|
79
89
|
},
|
|
80
90
|
"dependencies": {
|
|
81
91
|
"@fission-ai/openspec": "1.4.1",
|
|
82
|
-
"@langchain/core": "1.2.
|
|
83
|
-
"@langchain/langgraph": "1.4.
|
|
92
|
+
"@langchain/core": "1.2.14",
|
|
93
|
+
"@langchain/langgraph": "1.4.18",
|
|
84
94
|
"@langchain/langgraph-checkpoint": "1.1.5",
|
|
85
|
-
"@modelcontextprotocol/sdk": "1.
|
|
95
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
86
96
|
"ajv": "^8.20.0",
|
|
87
97
|
"cross-spawn": "7.0.6",
|
|
88
|
-
"js-yaml": "^4.
|
|
98
|
+
"js-yaml": "^5.4.3",
|
|
89
99
|
"picocolors": "^1.1.1",
|
|
90
|
-
"
|
|
100
|
+
"semver": "^7.8.5",
|
|
101
|
+
"zod": "4.6.5"
|
|
91
102
|
},
|
|
92
103
|
"devDependencies": {
|
|
93
104
|
"@types/cross-spawn": "6.0.6",
|
|
94
|
-
"@types/
|
|
95
|
-
"@types/
|
|
96
|
-
"@vitest/coverage-v8": "^
|
|
97
|
-
"
|
|
98
|
-
"
|
|
105
|
+
"@types/node": "^26.6.5",
|
|
106
|
+
"@types/semver": "^7.8.0",
|
|
107
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
108
|
+
"js-yaml-4": "npm:js-yaml@^4.3.2",
|
|
109
|
+
"typescript": "^7.0.2",
|
|
110
|
+
"vitest": "^5.0.3"
|
|
99
111
|
},
|
|
100
112
|
"license": "MIT",
|
|
101
113
|
"author": "fjpulidop"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"planning": { "enum": ["full", "proportional"] }, "acceptDeveloperChecks": { "type": "boolean" },
|
|
13
13
|
"verification": { "type": "object", "additionalProperties": false, "properties": { "maxConcurrency": { "type": "integer", "minimum": 1, "maximum": 4 } } }
|
|
14
14
|
} },
|
|
15
|
-
"guardrails": { "type": "object", "additionalProperties": false, "properties": { "plan-validation": { "type": "boolean" }, "genuine-blocking-question": { "type": "boolean" }, "plan-language-hint": { "type": "boolean" }, "frozen-plan-writes": { "type": "boolean" }, "primary-language": { "type": "boolean" }, "duplicate-sibling": { "type": "boolean" }, "one-test-per-module": { "type": "boolean" }, "empty-write": { "type": "boolean" }, "evidence-gated-ticking": { "type": "boolean" }, "inventory-retry": { "type": "boolean" }, "synthetic-corrections": { "type": "boolean" }, "silent-group-closure": { "type": "boolean" }, "verify-per-group": { "type": "boolean" }, "test-reachability": { "type": "boolean" }, "exit-code-honesty": { "type": "boolean" }, "environment-repair": { "type": "boolean" }, "lockfile-repair": { "type": "boolean" }, "verify-idle-timeout": { "type": "boolean" } } },
|
|
15
|
+
"guardrails": { "type": "object", "additionalProperties": false, "properties": { "plan-validation": { "type": "boolean" }, "genuine-blocking-question": { "type": "boolean" }, "plan-language-hint": { "type": "boolean" }, "frozen-plan-writes": { "type": "boolean" }, "primary-language": { "type": "boolean" }, "duplicate-sibling": { "type": "boolean" }, "one-test-per-module": { "type": "boolean" }, "empty-write": { "type": "boolean" }, "evidence-gated-ticking": { "type": "boolean" }, "inventory-retry": { "type": "boolean" }, "synthetic-corrections": { "type": "boolean" }, "silent-group-closure": { "type": "boolean" }, "verify-per-group": { "type": "boolean" }, "test-reachability": { "type": "boolean" }, "exit-code-honesty": { "type": "boolean" }, "environment-repair": { "type": "boolean" }, "lockfile-repair": { "type": "boolean" }, "verify-idle-timeout": { "type": "boolean" }, "verification-output-adoption": { "type": "boolean" } } },
|
|
16
16
|
"rolePrompts": { "type": "object", "propertyNames": { "pattern": "^[a-z][a-z0-9-]{0,63}$" }, "additionalProperties": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" }, "properties": {
|
|
17
17
|
"architect": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" },
|
|
18
18
|
"developer": { "type": "string", "minLength": 1, "maxLength": 20000, "pattern": "\\S" },
|