specrails-core 6.6.5 → 6.7.1

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.
Files changed (118) hide show
  1. package/dist/agent-runtime/capabilities.d.ts +5 -2
  2. package/dist/agent-runtime/capabilities.js +8 -2
  3. package/dist/agent-runtime/change-scope.d.ts +2 -0
  4. package/dist/agent-runtime/change-scope.js +5 -0
  5. package/dist/agent-runtime/cli-executor.js +8 -1
  6. package/dist/agent-runtime/cli.d.ts +6 -1
  7. package/dist/agent-runtime/cli.js +15 -4
  8. package/dist/agent-runtime/compact/reviewer.js +3 -1
  9. package/dist/agent-runtime/config.js +16 -5
  10. package/dist/agent-runtime/efficiency-summary.d.ts +7 -0
  11. package/dist/agent-runtime/efficiency-summary.js +7 -1
  12. package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.js +1 -1
  13. package/dist/agent-runtime/engine/checkpoint/database.d.ts +9 -0
  14. package/dist/agent-runtime/engine/checkpoint/database.js +40 -3
  15. package/dist/agent-runtime/engine/checkpoint/ledger.d.ts +1 -0
  16. package/dist/agent-runtime/engine/checkpoint/ledger.js +7 -2
  17. package/dist/agent-runtime/engine/cli.d.ts +12 -0
  18. package/dist/agent-runtime/engine/cli.js +15 -4
  19. package/dist/agent-runtime/engine/definition-schema.d.ts +1 -1
  20. package/dist/agent-runtime/engine/definition-schema.js +1 -0
  21. package/dist/agent-runtime/engine/definition-types.d.ts +1 -1
  22. package/dist/agent-runtime/engine/definition-types.js +1 -1
  23. package/dist/agent-runtime/engine/definition-validator.js +8 -0
  24. package/dist/agent-runtime/engine/efficiency-summary.d.ts +7 -0
  25. package/dist/agent-runtime/engine/graph-description.d.ts +3 -3
  26. package/dist/agent-runtime/engine/invocation-context.js +2 -0
  27. package/dist/agent-runtime/engine/piece-registry.d.ts +1 -1
  28. package/dist/agent-runtime/engine/piece-registry.js +1 -1
  29. package/dist/agent-runtime/engine/pieces/artifact-contract.d.ts +5 -0
  30. package/dist/agent-runtime/engine/pieces/artifact-contract.js +6 -2
  31. package/dist/agent-runtime/engine/pieces/derivation-coverage.d.ts +30 -0
  32. package/dist/agent-runtime/engine/pieces/derivation-coverage.js +95 -0
  33. package/dist/agent-runtime/engine/pieces/derive-change.d.ts +50 -0
  34. package/dist/agent-runtime/engine/pieces/derive-change.js +209 -0
  35. package/dist/agent-runtime/engine/pieces/index.js +2 -1
  36. package/dist/agent-runtime/engine/pieces/openspec.d.ts +16 -1
  37. package/dist/agent-runtime/engine/pieces/openspec.js +39 -30
  38. package/dist/agent-runtime/engine/pieces/role-turn.js +26 -5
  39. package/dist/agent-runtime/engine/preflight.d.ts +2 -1
  40. package/dist/agent-runtime/engine/preflight.js +3 -1
  41. package/dist/agent-runtime/engine/regex.d.ts +12 -2
  42. package/dist/agent-runtime/engine/regex.js +25 -11
  43. package/dist/agent-runtime/engine/run-status.d.ts +34 -0
  44. package/dist/agent-runtime/engine/run-status.js +7 -1
  45. package/dist/agent-runtime/engine/runs.d.ts +56 -0
  46. package/dist/agent-runtime/engine/runs.js +88 -5
  47. package/dist/agent-runtime/engine/steering/inbox.d.ts +15 -0
  48. package/dist/agent-runtime/engine/steering/inbox.js +21 -1
  49. package/dist/agent-runtime/engine/storage/private-path.js +7 -1
  50. package/dist/agent-runtime/evaluation.js +1 -1
  51. package/dist/agent-runtime/executor-types.d.ts +19 -0
  52. package/dist/agent-runtime/executor-types.js +10 -0
  53. package/dist/agent-runtime/failure-classification.d.ts +51 -0
  54. package/dist/agent-runtime/failure-classification.js +110 -0
  55. package/dist/agent-runtime/graph/artifacts.d.ts +3 -1
  56. package/dist/agent-runtime/graph/artifacts.js +5 -1
  57. package/dist/agent-runtime/graph/baseline.d.ts +50 -0
  58. package/dist/agent-runtime/graph/baseline.js +145 -0
  59. package/dist/agent-runtime/graph/capability-map.d.ts +29 -0
  60. package/dist/agent-runtime/graph/capability-map.js +65 -0
  61. package/dist/agent-runtime/graph/clarifications.d.ts +75 -0
  62. package/dist/agent-runtime/graph/clarifications.js +158 -0
  63. package/dist/agent-runtime/graph/nodes.js +178 -19
  64. package/dist/agent-runtime/graph/review-brief-record.d.ts +25 -0
  65. package/dist/agent-runtime/graph/review-brief-record.js +26 -0
  66. package/dist/agent-runtime/graph/review-brief.d.ts +73 -0
  67. package/dist/agent-runtime/graph/review-brief.js +127 -0
  68. package/dist/agent-runtime/graph/roles.js +5 -3
  69. package/dist/agent-runtime/graph/spec-trace-record.d.ts +33 -0
  70. package/dist/agent-runtime/graph/spec-trace-record.js +65 -0
  71. package/dist/agent-runtime/graph/state.d.ts +8 -0
  72. package/dist/agent-runtime/index.d.ts +7 -0
  73. package/dist/agent-runtime/index.js +7 -0
  74. package/dist/agent-runtime/openspec.d.ts +1 -0
  75. package/dist/agent-runtime/openspec.js +1 -1
  76. package/dist/agent-runtime/project-rules.d.ts +139 -0
  77. package/dist/agent-runtime/project-rules.js +683 -0
  78. package/dist/agent-runtime/prompts.d.ts +32 -1
  79. package/dist/agent-runtime/prompts.js +182 -7
  80. package/dist/agent-runtime/role-routing.d.ts +2 -1
  81. package/dist/agent-runtime/session/application/active-session.d.ts +43 -2
  82. package/dist/agent-runtime/session/application/active-session.js +163 -7
  83. package/dist/agent-runtime/session/application/session-service.d.ts +2 -2
  84. package/dist/agent-runtime/session/domain/descriptor.js +5 -0
  85. package/dist/agent-runtime/session/domain/interruption.js +1 -0
  86. package/dist/agent-runtime/session/domain/policy.js +2 -0
  87. package/dist/agent-runtime/session/domain/redaction.d.ts +19 -0
  88. package/dist/agent-runtime/session/domain/redaction.js +71 -0
  89. package/dist/agent-runtime/session/domain/types.d.ts +17 -1
  90. package/dist/agent-runtime/session/drivers/claude/argv.d.ts +14 -0
  91. package/dist/agent-runtime/session/drivers/claude/argv.js +19 -2
  92. package/dist/agent-runtime/session/drivers/claude/driver.js +11 -1
  93. package/dist/agent-runtime/session/drivers/claude/translator.d.ts +4 -0
  94. package/dist/agent-runtime/session/drivers/claude/translator.js +16 -3
  95. package/dist/agent-runtime/session/drivers/codex/driver.js +2 -0
  96. package/dist/agent-runtime/session/drivers/executor/driver.js +2 -0
  97. package/dist/agent-runtime/session/host/protocol.d.ts +12 -0
  98. package/dist/agent-runtime/session/host/protocol.js +10 -2
  99. package/dist/agent-runtime/session/host/server.js +6 -5
  100. package/dist/agent-runtime/session/index.d.ts +1 -1
  101. package/dist/agent-runtime/session/journal/sqlite-journal.js +6 -2
  102. package/dist/agent-runtime/session/ports.d.ts +6 -2
  103. package/dist/agent-runtime/session/testing/driver-conformance.js +16 -2
  104. package/dist/agent-runtime/session/testing/memory-journal.js +4 -2
  105. package/dist/installer/cli.js +10 -1
  106. package/dist/installer/commands/init.js +14 -3
  107. package/dist/installer/phases/scaffold.js +56 -5
  108. package/dist/pipeline/pipeline-state.d.ts +22 -0
  109. package/dist/pipeline/pipeline-state.js +55 -4
  110. package/dist/pipeline/spec-trace.d.ts +134 -0
  111. package/dist/pipeline/spec-trace.js +392 -0
  112. package/dist/shared/secret-redaction.d.ts +42 -0
  113. package/dist/shared/secret-redaction.js +184 -0
  114. package/integration-contract.json +177 -7
  115. package/package.json +7 -7
  116. package/schemas/agent-runtime.schema.json +3 -1
  117. package/schemas/workflow-definition.schema.json +1 -0
  118. package/templates/settings/codex-config.toml +3 -2
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The reviewer's risk brief: an optional, advisory companion to the verdict that
3
+ * tells a human what changed, what the change leans on, what no test exercises
4
+ * and where to look first. It never feeds the approval gate (review-policy.ts).
5
+ */
6
+ export const REVIEW_BRIEF_SCHEMA_VERSION = 1;
7
+ /** At most this many risks reach the brief; the rest become acceptance findings. */
8
+ export const REVIEW_BRIEF_RISK_LIMIT = 5;
9
+ const LIST_LIMIT = 20;
10
+ const TEXT_LIMIT = 2000;
11
+ export const REVIEW_RISK_SEVERITIES = ['high', 'medium', 'low'];
12
+ export const REVIEW_RISK_AREAS = ['data', 'security', 'api', 'ux', 'performance', 'ops', 'other'];
13
+ export const REVIEW_INVARIANT_STATUSES = ['kept', 'changed', 'at-risk'];
14
+ const text = { type: 'string', minLength: 1 };
15
+ const items = (properties) => ({ type: 'array', items: { type: 'object', additionalProperties: false, required: Object.keys(properties), properties } });
16
+ /** The optional `brief` property of REVIEW_OUTPUT_SCHEMA. Every list is optional and may be empty. */
17
+ export const REVIEW_BRIEF_SCHEMA = {
18
+ type: 'object', additionalProperties: false,
19
+ description: 'Optional risk brief for the human who merges the change. Advisory only: it never changes the verdict.',
20
+ properties: {
21
+ behaviourChanges: items({ what: text, where: { ...text, description: 'file:line' }, userVisible: { type: 'boolean' } }),
22
+ invariantsTouched: items({ invariant: text, evidence: { ...text, description: 'file:line or file:start-end' }, status: { type: 'string', enum: [...REVIEW_INVARIANT_STATUSES] } }),
23
+ untested: items({ path: text, why: text }),
24
+ risks: items({ severity: { type: 'string', enum: [...REVIEW_RISK_SEVERITIES] }, area: { type: 'string', enum: [...REVIEW_RISK_AREAS] }, whatToLookAt: text, ref: { ...text, description: 'file:line' } }),
25
+ humanChecks: { type: 'array', items: text },
26
+ divergences: items({ capability: text, requirement: text, observed: text }),
27
+ },
28
+ };
29
+ class BriefError extends Error {
30
+ constructor(detail) { super('Invalid review brief: ' + detail); }
31
+ }
32
+ function field(value, where) {
33
+ if (typeof value !== 'string' || !value.trim())
34
+ throw new BriefError(`${where} must be a non-empty string`);
35
+ return value.trim().slice(0, TEXT_LIMIT);
36
+ }
37
+ function member(value, allowed, where) {
38
+ if (typeof value !== 'string' || !allowed.includes(value))
39
+ throw new BriefError(`${where} must be one of ${allowed.join(', ')}`);
40
+ return value;
41
+ }
42
+ function list(value, name, parse) {
43
+ if (value === undefined || value === null)
44
+ return undefined;
45
+ if (!Array.isArray(value))
46
+ throw new BriefError(`${name} must be an array`);
47
+ return value.map((item, index) => {
48
+ const where = `${name}[${index}]`;
49
+ if (!item || typeof item !== 'object' || Array.isArray(item))
50
+ throw new BriefError(`${where} must be an object`);
51
+ return parse(item, where);
52
+ });
53
+ }
54
+ const SEVERITY_RANK = { high: 0, medium: 1, low: 2 };
55
+ /**
56
+ * Validates and normalizes a reviewer brief. Absent (`undefined`/`null`) means
57
+ * no brief; an empty object is a valid, empty brief. Risks are ordered by
58
+ * severity (stable within a severity) and capped at REVIEW_BRIEF_RISK_LIMIT;
59
+ * the rest are returned as `overflow`. Throws `Invalid review brief: …` when
60
+ * malformed, which the role invoker treats as repairable.
61
+ */
62
+ export function parseReviewBrief(value) {
63
+ if (value === undefined || value === null)
64
+ return { overflow: [] };
65
+ if (typeof value !== 'object' || Array.isArray(value))
66
+ throw new BriefError('brief must be an object');
67
+ const raw = value;
68
+ const brief = {};
69
+ const behaviourChanges = list(raw.behaviourChanges, 'behaviourChanges', (item, where) => {
70
+ if (typeof item.userVisible !== 'boolean')
71
+ throw new BriefError(`${where}.userVisible must be a boolean`);
72
+ return { what: field(item.what, where + '.what'), where: field(item.where, where + '.where'), userVisible: item.userVisible };
73
+ });
74
+ const invariantsTouched = list(raw.invariantsTouched, 'invariantsTouched', (item, where) => ({
75
+ invariant: field(item.invariant, where + '.invariant'), evidence: field(item.evidence, where + '.evidence'), status: member(item.status, REVIEW_INVARIANT_STATUSES, where + '.status'),
76
+ }));
77
+ const untested = list(raw.untested, 'untested', (item, where) => ({ path: field(item.path, where + '.path'), why: field(item.why, where + '.why') }));
78
+ const risks = list(raw.risks, 'risks', (item, where) => ({
79
+ severity: member(item.severity, REVIEW_RISK_SEVERITIES, where + '.severity'), area: member(item.area, REVIEW_RISK_AREAS, where + '.area'),
80
+ whatToLookAt: field(item.whatToLookAt, where + '.whatToLookAt'), ref: field(item.ref, where + '.ref'),
81
+ }));
82
+ let humanChecks;
83
+ if (raw.humanChecks !== undefined && raw.humanChecks !== null) {
84
+ if (!Array.isArray(raw.humanChecks))
85
+ throw new BriefError('humanChecks must be an array');
86
+ humanChecks = raw.humanChecks.map((item, index) => field(item, `humanChecks[${index}]`));
87
+ }
88
+ const divergences = list(raw.divergences, 'divergences', (item, where) => ({
89
+ capability: field(item.capability, where + '.capability'), requirement: field(item.requirement, where + '.requirement'), observed: field(item.observed, where + '.observed'),
90
+ }));
91
+ let overflow = [];
92
+ if (behaviourChanges)
93
+ brief.behaviourChanges = behaviourChanges.slice(0, LIST_LIMIT);
94
+ if (invariantsTouched)
95
+ brief.invariantsTouched = invariantsTouched.slice(0, LIST_LIMIT);
96
+ if (untested)
97
+ brief.untested = untested.slice(0, LIST_LIMIT);
98
+ if (risks) {
99
+ const ordered = risks.map((risk, index) => ({ risk, index })).sort((a, b) => SEVERITY_RANK[a.risk.severity] - SEVERITY_RANK[b.risk.severity] || a.index - b.index).map(entry => entry.risk);
100
+ brief.risks = ordered.slice(0, REVIEW_BRIEF_RISK_LIMIT);
101
+ overflow = ordered.slice(REVIEW_BRIEF_RISK_LIMIT);
102
+ }
103
+ if (humanChecks)
104
+ brief.humanChecks = humanChecks.slice(0, LIST_LIMIT);
105
+ if (divergences)
106
+ brief.divergences = divergences.slice(0, LIST_LIMIT);
107
+ return { brief, overflow };
108
+ }
109
+ /** One acceptance finding line for a risk that did not fit in the brief. */
110
+ export function riskFinding(risk) {
111
+ return `Additional ${risk.severity} ${risk.area} risk (beyond the brief's top ${REVIEW_BRIEF_RISK_LIMIT}): ${risk.whatToLookAt} (${risk.ref})`;
112
+ }
113
+ /**
114
+ * Applies the repair policy: on the first reply a malformed brief throws so the
115
+ * role invoker spends its single repair turn; on the repaired reply it is
116
+ * dropped with a diagnostic instead of failing the review.
117
+ */
118
+ export function settleReviewBrief(value, options) {
119
+ try {
120
+ return parseReviewBrief(value);
121
+ }
122
+ catch (error) {
123
+ if (options.repairable)
124
+ throw error;
125
+ return { overflow: [], diagnostic: `Review brief dropped after the repair turn: ${error instanceof Error ? error.message : String(error)}` };
126
+ }
127
+ }
@@ -10,6 +10,7 @@ import { performance } from 'node:perf_hooks';
10
10
  import { frozenAcceptanceCriteria, pipelineStateDirectory } from '../../pipeline/pipeline-state.js';
11
11
  import { AgentExecutionError, unknownUsage } from '../executor-types.js';
12
12
  import { ROLE_INSTRUCTIONS_VERSION, repairInstructions } from '../prompts.js';
13
+ import { asksBeforePlanning } from './clarifications.js';
13
14
  import { parseAgentObject } from './artifacts.js';
14
15
  /** Executor failures that mean "this session cannot be continued", not "the role failed". */
15
16
  const SESSION_FALLBACK_CODES = new Set(['session_not_found', 'session_expired', 'session_unsupported']);
@@ -138,9 +139,10 @@ export function createRoleInvoker(deps) {
138
139
  parseError = error;
139
140
  }
140
141
  }
141
- // A genuine architect question can precede artifact creation. Completed
142
- // role results must prove this invocation used the workflow, not an old visit.
143
- if (!(role === 'architect' && output?.confidence === 'low'))
142
+ // A genuine architect question (low confidence, or a high-impact
143
+ // clarification without a safe default) can precede artifact creation.
144
+ // Completed role results must prove this invocation used the workflow, not an old visit.
145
+ if (!(role === 'architect' && (output?.confidence === 'low' || asksBeforePlanning(output?.clarifications))))
144
146
  workflow?.assertParticipation(cursor);
145
147
  if (structured && !output && !options.lenient)
146
148
  throw new Error('Invalid structured role response: ' + (parseError instanceof Error ? parseError.message : 'missing object'));
@@ -0,0 +1,33 @@
1
+ import { type PipelineContext, type VerificationReceipt } from '../../pipeline/pipeline-state.js';
2
+ import { SPEC_TRACE_SCHEMA_VERSION, type SpecTraceReport } from '../../pipeline/spec-trace.js';
3
+ /** What the verification record, the reviewer and the acceptance findings carry; the full report stays in spec-trace.json. */
4
+ export interface SpecTraceSummary {
5
+ schemaVersion: typeof SPEC_TRACE_SCHEMA_VERSION;
6
+ receiptId: string | null;
7
+ summary: string;
8
+ requirements: number;
9
+ demonstrated: number;
10
+ touched: string[];
11
+ touchedUndemonstrated: Array<{
12
+ id: string;
13
+ reason: string;
14
+ }>;
15
+ untagged: Array<{
16
+ tag: string;
17
+ file: string;
18
+ line: number;
19
+ }>;
20
+ /** Absolute path of the full report. */
21
+ path: string;
22
+ }
23
+ export declare function specTracePath(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>): string;
24
+ /**
25
+ * Scans the specs and tests after a passing verification, binds the report to
26
+ * the receipt and writes `spec-trace.json`. Advisory and fail-open: any error
27
+ * yields `undefined` and never changes the verification outcome.
28
+ */
29
+ export declare function recordSpecTrace(context: PipelineContext, change: string, receipt: Pick<VerificationReceipt, 'id' | 'commands'>, reportedTests?: readonly string[]): SpecTraceSummary | undefined;
30
+ /** The latest report of a run, or null when verification has not passed yet (or the file is unreadable). */
31
+ export declare function readSpecTraceReport(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>): SpecTraceReport | null;
32
+ /** The acceptance finding that records the advisory trace next to the receipt. */
33
+ export declare function specTraceFinding(trace: SpecTraceSummary): string;
@@ -0,0 +1,65 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pipelineStateDirectory } from '../../pipeline/pipeline-state.js';
4
+ import { SPEC_TRACE_FILE, SPEC_TRACE_SCHEMA_VERSION, traceSpecs, traceSummary } from '../../pipeline/spec-trace.js';
5
+ import { commandReachesTest } from '../compact/test-reachability.js';
6
+ import { write } from './artifacts.js';
7
+ const LIST_LIMIT = 50;
8
+ export function specTracePath(context) {
9
+ return path.join(pipelineStateDirectory(context), SPEC_TRACE_FILE);
10
+ }
11
+ /**
12
+ * Scans the specs and tests after a passing verification, binds the report to
13
+ * the receipt and writes `spec-trace.json`. Advisory and fail-open: any error
14
+ * yields `undefined` and never changes the verification outcome.
15
+ */
16
+ export function recordSpecTrace(context, change, receipt, reportedTests = []) {
17
+ try {
18
+ const single = context.repositories.length === 1 ? context.repositories[0] : undefined;
19
+ const testFiles = single ? reportedTests.map(file => path.isAbsolute(file) ? path.relative(single.path, file) : file).filter(file => file && !file.startsWith('..')).map(file => ({ repositoryId: single.id, file })) : [];
20
+ const report = traceSpecs({
21
+ specsRoot: context.artifactRoot, changes: [change],
22
+ repositories: context.repositories.map(repository => ({ id: repository.id, root: repository.path })),
23
+ testFiles, receipt: { id: receipt.id, commands: receipt.commands.map(command => {
24
+ // Receipts record the absolute working directory; reachability wants it relative to the repository.
25
+ const root = context.repositories.find(item => item.id === command.repositoryId)?.path;
26
+ const cwd = root && command.cwd ? path.relative(root, path.resolve(root, command.cwd)) : '';
27
+ return { repositoryId: command.repositoryId, command: command.command, args: command.args, ...(cwd && !cwd.startsWith('..') ? { cwd } : {}), exitCode: command.exitCode };
28
+ }) },
29
+ reaches: commandReachesTest,
30
+ });
31
+ const file = specTracePath(context);
32
+ write(file, JSON.stringify(report, null, 2) + '\n');
33
+ return summarize(report, file, context.repositories.length > 1);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ }
39
+ function summarize(report, file, multiple) {
40
+ const reasons = new Map(report.undemonstrated.map(item => [item.id, item.reason]));
41
+ return {
42
+ schemaVersion: report.schemaVersion, receiptId: report.receiptId, summary: traceSummary(report), requirements: report.requirements, demonstrated: report.demonstrated.length,
43
+ touched: report.touched.slice(0, LIST_LIMIT), touchedUndemonstrated: report.touchedUndemonstrated.slice(0, LIST_LIMIT).map(id => ({ id, reason: reasons.get(id) ?? 'no-test' })),
44
+ untagged: report.untagged.slice(0, LIST_LIMIT).map(tag => ({ tag: tag.tag, file: (multiple ? tag.repositoryId + ':' : '') + tag.file, line: tag.line })),
45
+ path: file,
46
+ };
47
+ }
48
+ /** The latest report of a run, or null when verification has not passed yet (or the file is unreadable). */
49
+ export function readSpecTraceReport(context) {
50
+ try {
51
+ const file = specTracePath(context);
52
+ if (!existsSync(file))
53
+ return null;
54
+ const value = JSON.parse(readFileSync(file, 'utf8'));
55
+ return value && typeof value === 'object' && value.schemaVersion === SPEC_TRACE_SCHEMA_VERSION ? value : null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ /** The acceptance finding that records the advisory trace next to the receipt. */
62
+ export function specTraceFinding(trace) {
63
+ const touched = trace.touched.length ? `; touched by this change ${trace.touched.length - trace.touchedUndemonstrated.length}/${trace.touched.length}${trace.touchedUndemonstrated.length ? ' (not demonstrated: ' + trace.touchedUndemonstrated.map(item => item.id).join(', ') + ')' : ''}` : '';
64
+ return `Spec trace (advisory, receipt ${trace.receiptId ?? 'unknown'}): ${trace.summary}${touched}.`;
65
+ }
@@ -14,6 +14,8 @@ export interface ArchitectureRecord {
14
14
  question?: string;
15
15
  /** True when a low-confidence design proceeded on stated assumptions instead of asking. */
16
16
  assumed?: boolean;
17
+ /** Ambiguities the architect reported before planning (see clarifications.ts). */
18
+ clarifications?: import('./clarifications.js').Clarification[];
17
19
  }
18
20
  export interface DeveloperRecord {
19
21
  summary: string;
@@ -52,6 +54,8 @@ export interface VerificationRecord {
52
54
  selfMutation?: import('../../pipeline/pipeline-state.js').VerificationSelfMutation;
53
55
  /** Files verification regenerated that the host kept in the candidate; roles must not revert them. Sticky for the run. */
54
56
  adoptedOutputs?: import('../../pipeline/pipeline-state.js').CandidateFileChange[];
57
+ /** Advisory requirement ↔ test trace of a passing verification (spec-trace.json), bound to `receiptId`. */
58
+ specTrace?: import('./spec-trace-record.js').SpecTraceSummary;
55
59
  }
56
60
  export interface ReviewRecord {
57
61
  sessionId?: string;
@@ -62,6 +66,10 @@ export interface ReviewRecord {
62
66
  score: number;
63
67
  aspects: Record<string, number>;
64
68
  candidateHash?: string;
69
+ /** Advisory risk brief (see review-brief.ts); never part of the approval gate. */
70
+ brief?: import('./review-brief.js').ReviewBrief;
71
+ /** Why a malformed brief was dropped after the repair turn. */
72
+ briefDiagnostic?: string;
65
73
  }
66
74
  export interface ArchiveRecord {
67
75
  archivePath: string;
@@ -11,6 +11,13 @@ export * from './core-host.js';
11
11
  export * from './graph/state.js';
12
12
  export { coreNodes, developerRecord, buildAcceptanceReport, MAX_DEEPEN_PASSES, type CoreNodeDeps } from './graph/nodes.js';
13
13
  export { DEFAULT_REVIEW_POLICY, REVIEW_ASPECTS, evaluateReview, resolveReviewPolicy, type ReviewAspect, type ReviewPolicy } from './graph/review-policy.js';
14
+ export { REVIEW_BRIEF_RISK_LIMIT, REVIEW_BRIEF_SCHEMA, REVIEW_BRIEF_SCHEMA_VERSION, parseReviewBrief, type ReviewBrief, type ReviewRisk } from './graph/review-brief.js';
15
+ export { REVIEW_BRIEF_FILE, readReviewBriefRecord, reviewBriefPath, type ReviewBriefRecord } from './graph/review-brief-record.js';
16
+ export { BASELINE_BLOCKER, BASELINE_FILE, BASELINE_SCHEMA_VERSION, baselinePath, blastRadiusFiles, detectBaseline, readBaselineRecord, type BaselineDetection, type BaselineRecord } from './graph/baseline.js';
17
+ export { CAPABILITY_MAP_PATH, CAPABILITY_MAP_SCHEMA_VERSION, readCapabilityMap, type CapabilityMap } from './graph/capability-map.js';
18
+ export { SPEC_TRACE_FILE, SPEC_TRACE_SCHEMA_VERSION, requirementSlug, requirementId, traceSpecs, touchedRequirements, type SpecTraceReport, type SpecTraceInput } from '../pipeline/spec-trace.js';
19
+ export { readSpecTraceReport, specTracePath, type SpecTraceSummary } from './graph/spec-trace-record.js';
20
+ export { CLARIFICATIONS_FILE, CLARIFICATIONS_SCHEMA, CLARIFICATIONS_SCHEMA_VERSION, CLARIFICATION_POLICIES, blockingClarifications, designSection, parseClarifications, readClarificationsRecord, clarificationsPath, type Clarification, type ClarificationPolicy, type ClarificationsRecord } from './graph/clarifications.js';
14
21
  export { createRoleInvoker, type Accept, type InvokeOptions, type InvokeOutcome, type RoleInvoker, type RoleInvokerDeps } from './graph/roles.js';
15
22
  export { parseAgentObject } from './graph/artifacts.js';
16
23
  export { FileCheckpointSaver, type GraphStoreIO, type SerializedGraphStore } from './graph-checkpointer.js';
@@ -11,6 +11,13 @@ export * from './core-host.js';
11
11
  export * from './graph/state.js';
12
12
  export { coreNodes, developerRecord, buildAcceptanceReport, MAX_DEEPEN_PASSES } from './graph/nodes.js';
13
13
  export { DEFAULT_REVIEW_POLICY, REVIEW_ASPECTS, evaluateReview, resolveReviewPolicy } from './graph/review-policy.js';
14
+ export { REVIEW_BRIEF_RISK_LIMIT, REVIEW_BRIEF_SCHEMA, REVIEW_BRIEF_SCHEMA_VERSION, parseReviewBrief } from './graph/review-brief.js';
15
+ export { REVIEW_BRIEF_FILE, readReviewBriefRecord, reviewBriefPath } from './graph/review-brief-record.js';
16
+ export { BASELINE_BLOCKER, BASELINE_FILE, BASELINE_SCHEMA_VERSION, baselinePath, blastRadiusFiles, detectBaseline, readBaselineRecord } from './graph/baseline.js';
17
+ export { CAPABILITY_MAP_PATH, CAPABILITY_MAP_SCHEMA_VERSION, readCapabilityMap } from './graph/capability-map.js';
18
+ export { SPEC_TRACE_FILE, SPEC_TRACE_SCHEMA_VERSION, requirementSlug, requirementId, traceSpecs, touchedRequirements } from '../pipeline/spec-trace.js';
19
+ export { readSpecTraceReport, specTracePath } from './graph/spec-trace-record.js';
20
+ export { CLARIFICATIONS_FILE, CLARIFICATIONS_SCHEMA, CLARIFICATIONS_SCHEMA_VERSION, CLARIFICATION_POLICIES, blockingClarifications, designSection, parseClarifications, readClarificationsRecord, clarificationsPath } from './graph/clarifications.js';
14
21
  export { createRoleInvoker } from './graph/roles.js';
15
22
  export { parseAgentObject } from './graph/artifacts.js';
16
23
  export { FileCheckpointSaver } from './graph-checkpointer.js';
@@ -6,6 +6,7 @@ export declare const ROLE_SKILLS: {
6
6
  readonly developer: 'openspec-apply-change';
7
7
  readonly reviewer: 'openspec-verify-change';
8
8
  };
9
+ export declare const DEFAULT_PROJECT_CONFIG = "schema: spec-driven\n";
9
10
  export interface OpenSpecRoleContext {
10
11
  evidenceScope?: {
11
12
  backlogRoot: string;
@@ -11,7 +11,7 @@ export const OPENSPEC_VERSION = '1.14.3';
11
11
  export const ROLE_SKILLS = { architect: 'openspec-ff-change', developer: 'openspec-apply-change', reviewer: 'openspec-verify-change' };
12
12
  /** Where `openspec init --tools` writes each provider's skills (Codex and Kimi moved in 1.14). */
13
13
  const OPENSPEC_SKILL_DIRECTORIES = { claude: '.claude', codex: '.agents', gemini: '.gemini', kimi: '.kimi-code' };
14
- const DEFAULT_PROJECT_CONFIG = 'schema: spec-driven\n';
14
+ export const DEFAULT_PROJECT_CONFIG = 'schema: spec-driven\n';
15
15
  /** Saved built-in contexts retain their original shape; new roles carry explicit policy. */
16
16
  export function openSpecPolicy(context) {
17
17
  const legacy = isBuiltinRole(context.role) ? BUILTIN_ROLE_POLICY[context.role] : undefined;
@@ -0,0 +1,139 @@
1
+ import type { PipelineContext } from '../pipeline/pipeline-state.js';
2
+ import type { ExecutorRegistry } from './executors.js';
3
+ import type { AgentExecutor, AgentUsage, RuntimeConfig } from './executor-types.js';
4
+ export declare const RULES_INVENTORY_VERSION = 1;
5
+ export declare const RULES_FILE = ".specrails/rules.json";
6
+ export declare const RULE_SOURCE_FILES: readonly ['CLAUDE.md', 'AGENTS.md', 'CONTRIBUTING.md', '.github/CONTRIBUTING.md', 'docs/CONTRIBUTING.md'];
7
+ export declare const GATE_KINDS: readonly ['lint', 'format', 'typecheck', 'test', 'architecture', 'hook', 'ci', 'spec'];
8
+ export declare const RULE_STATUSES: readonly ['enforcedBy', 'automatable', 'humanOnly', 'unclassified'];
9
+ export type GateKind = typeof GATE_KINDS[number];
10
+ export type RuleStatus = typeof RULE_STATUSES[number];
11
+ export interface RulesGate {
12
+ id: string;
13
+ kind: GateKind;
14
+ /** Shell-free command line a host can run from the repository root. */
15
+ command: string;
16
+ /** Repository-relative file that revealed the gate. */
17
+ source: string;
18
+ }
19
+ export interface ProjectRule {
20
+ /** Stable across line moves: hash of the source file and the normalized statement. */
21
+ id: string;
22
+ text: string;
23
+ /** `<repository-relative file>:<line>` of the statement's first line. */
24
+ source: string;
25
+ status: RuleStatus;
26
+ /** With `enforcedBy`: the gate id (or the named test/check) that already enforces the rule. */
27
+ enforcedBy?: string;
28
+ /** With `automatable`: the check that would enforce it. */
29
+ proposedCheck?: string;
30
+ }
31
+ export interface RulesClassification {
32
+ status: 'classified' | 'cached' | 'offline' | 'unavailable' | 'failed' | 'empty';
33
+ provider?: string;
34
+ model?: string;
35
+ reason?: string;
36
+ classifiedAt?: string;
37
+ usage?: AgentUsage;
38
+ }
39
+ export interface RulesInventory {
40
+ schemaVersion: 1;
41
+ generatedAt: string;
42
+ gates: RulesGate[];
43
+ rules: ProjectRule[];
44
+ classification: RulesClassification;
45
+ }
46
+ /** JSON with comments and trailing commas (tsconfig, biome.jsonc). */
47
+ export declare function parseJsonc(text: string): unknown;
48
+ /** `run:` commands of a GitHub workflow (single line, literal `|` and folded `>` block scalars). */
49
+ export declare function workflowRunCommands(text: string): string[];
50
+ /** Deterministic gate discovery for one repository root. */
51
+ export declare function discoverGates(root: string): RulesGate[];
52
+ export declare function ruleId(file: string, text: string): string;
53
+ /**
54
+ * Rule statements of one instruction file: list items (with their wrapped
55
+ * continuation lines) and strongly normative paragraph sentences, outside code
56
+ * fences, tables and Specrails-managed blocks.
57
+ */
58
+ export declare function parseRuleStatements(text: string, file: string): Array<{
59
+ text: string;
60
+ source: string;
61
+ }>;
62
+ /** Every rule statement of the instruction files present in `root`, deduplicated, with stable ids. */
63
+ export declare function collectRuleStatements(root: string): ProjectRule[];
64
+ export declare const RULES_CLASSIFICATION_SCHEMA: {
65
+ readonly type: 'object';
66
+ readonly additionalProperties: false;
67
+ readonly required: readonly ['rules'];
68
+ readonly properties: {
69
+ readonly rules: {
70
+ readonly type: 'array';
71
+ readonly items: {
72
+ readonly type: 'object';
73
+ readonly additionalProperties: false;
74
+ readonly required: readonly ['id', 'status'];
75
+ readonly properties: {
76
+ readonly id: {
77
+ readonly type: 'string';
78
+ };
79
+ readonly status: {
80
+ readonly enum: readonly ['enforcedBy', 'automatable', 'humanOnly'];
81
+ };
82
+ readonly enforcedBy: {
83
+ readonly type: 'string';
84
+ };
85
+ readonly proposedCheck: {
86
+ readonly type: 'string';
87
+ };
88
+ };
89
+ };
90
+ };
91
+ };
92
+ };
93
+ export declare function classificationPrompt(gates: readonly RulesGate[], rules: readonly ProjectRule[]): string;
94
+ /** Applies a model reply to the parsed rules: only known ids and statuses; text and source never change. */
95
+ export declare function applyClassification(rules: readonly ProjectRule[], reply: Record<string, unknown>): {
96
+ rules: ProjectRule[];
97
+ classified: number;
98
+ };
99
+ export declare function readRulesInventory(file: string): RulesInventory | undefined;
100
+ export interface RulesInventoryOptions {
101
+ /** Repository whose gates and instruction files are inventoried. */
102
+ root: string;
103
+ /** Directory holding `.specrails/` (rules.json and agent-runtime.json); defaults to `root`. */
104
+ artifactRoot?: string;
105
+ /** false: deterministic only (gates + rules, unclassified unless cached). */
106
+ classify?: boolean;
107
+ /** Ignore cached classifications and classify every rule again. */
108
+ reclassify?: boolean;
109
+ /** Runtime configuration; defaults to `<artifactRoot>/.specrails/agent-runtime.json`. */
110
+ config?: RuntimeConfig;
111
+ /** Programmatic executors (tests, hosts) replacing configured ones. */
112
+ executors?: Record<string, AgentExecutor>;
113
+ registry?: ExecutorRegistry;
114
+ timeoutMs?: number;
115
+ signal?: AbortSignal;
116
+ now?: () => Date;
117
+ }
118
+ /**
119
+ * Builds and writes `.specrails/rules.json`. Rules whose statement did not
120
+ * change keep their previous classification, so a re-run (and `init`) only
121
+ * classifies what is new; the classification never blocks the inventory.
122
+ */
123
+ export declare function runRulesInventory(options: RulesInventoryOptions): Promise<{
124
+ file: string;
125
+ inventory: RulesInventory;
126
+ }>;
127
+ /** What the runtime hands the roles: the human-only rules and the discovered gates. */
128
+ export interface ProjectRulesContext {
129
+ humanOnly: Array<Pick<ProjectRule, 'text' | 'source'>>;
130
+ gates: Array<Pick<RulesGate, 'id' | 'kind' | 'command'>>;
131
+ }
132
+ /** Reads `.specrails/rules.json` next to the run's backlog, else in the artifact repository. */
133
+ export declare function projectRulesContext(context: Pick<PipelineContext, 'backlogRoot' | 'artifactRoot'>): ProjectRulesContext | undefined;
134
+ /** `specrails-core rules inventory [--root <dir>] [--artifact-root <dir>] [--offline] [--reclassify] [--json]`. */
135
+ export declare function runRulesCommand(flags: Record<string, string | boolean>, positionals: string[], io?: {
136
+ stdout?: (text: string) => void;
137
+ stderr?: (text: string) => void;
138
+ executors?: Record<string, AgentExecutor>;
139
+ }): Promise<number>;