specrails-core 6.6.4 → 6.7.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.
Files changed (123) 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 +7 -0
  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/archive-preflight.d.ts +3 -18
  56. package/dist/agent-runtime/graph/archive-preflight.js +6 -51
  57. package/dist/agent-runtime/graph/artifacts.d.ts +3 -1
  58. package/dist/agent-runtime/graph/artifacts.js +6 -2
  59. package/dist/agent-runtime/graph/baseline.d.ts +50 -0
  60. package/dist/agent-runtime/graph/baseline.js +145 -0
  61. package/dist/agent-runtime/graph/capability-map.d.ts +29 -0
  62. package/dist/agent-runtime/graph/capability-map.js +65 -0
  63. package/dist/agent-runtime/graph/clarifications.d.ts +75 -0
  64. package/dist/agent-runtime/graph/clarifications.js +158 -0
  65. package/dist/agent-runtime/graph/nodes.js +178 -19
  66. package/dist/agent-runtime/graph/review-brief-record.d.ts +25 -0
  67. package/dist/agent-runtime/graph/review-brief-record.js +26 -0
  68. package/dist/agent-runtime/graph/review-brief.d.ts +73 -0
  69. package/dist/agent-runtime/graph/review-brief.js +127 -0
  70. package/dist/agent-runtime/graph/roles.js +5 -3
  71. package/dist/agent-runtime/graph/spec-trace-record.d.ts +33 -0
  72. package/dist/agent-runtime/graph/spec-trace-record.js +65 -0
  73. package/dist/agent-runtime/graph/state.d.ts +8 -0
  74. package/dist/agent-runtime/index.d.ts +7 -0
  75. package/dist/agent-runtime/index.js +7 -0
  76. package/dist/agent-runtime/openspec.d.ts +2 -1
  77. package/dist/agent-runtime/openspec.js +11 -5
  78. package/dist/agent-runtime/project-rules.d.ts +139 -0
  79. package/dist/agent-runtime/project-rules.js +683 -0
  80. package/dist/agent-runtime/prompts.d.ts +32 -1
  81. package/dist/agent-runtime/prompts.js +180 -7
  82. package/dist/agent-runtime/role-routing.d.ts +2 -1
  83. package/dist/agent-runtime/session/application/active-session.d.ts +43 -2
  84. package/dist/agent-runtime/session/application/active-session.js +163 -7
  85. package/dist/agent-runtime/session/application/session-service.d.ts +2 -2
  86. package/dist/agent-runtime/session/domain/descriptor.js +5 -0
  87. package/dist/agent-runtime/session/domain/interruption.js +1 -0
  88. package/dist/agent-runtime/session/domain/policy.js +2 -0
  89. package/dist/agent-runtime/session/domain/redaction.d.ts +19 -0
  90. package/dist/agent-runtime/session/domain/redaction.js +71 -0
  91. package/dist/agent-runtime/session/domain/types.d.ts +17 -1
  92. package/dist/agent-runtime/session/drivers/claude/argv.d.ts +14 -0
  93. package/dist/agent-runtime/session/drivers/claude/argv.js +19 -2
  94. package/dist/agent-runtime/session/drivers/claude/driver.js +11 -1
  95. package/dist/agent-runtime/session/drivers/claude/translator.d.ts +4 -0
  96. package/dist/agent-runtime/session/drivers/claude/translator.js +16 -3
  97. package/dist/agent-runtime/session/drivers/codex/driver.js +2 -0
  98. package/dist/agent-runtime/session/drivers/executor/driver.js +2 -0
  99. package/dist/agent-runtime/session/host/protocol.d.ts +12 -0
  100. package/dist/agent-runtime/session/host/protocol.js +10 -2
  101. package/dist/agent-runtime/session/host/server.js +6 -5
  102. package/dist/agent-runtime/session/index.d.ts +1 -1
  103. package/dist/agent-runtime/session/journal/sqlite-journal.js +6 -2
  104. package/dist/agent-runtime/session/ports.d.ts +6 -2
  105. package/dist/agent-runtime/session/testing/driver-conformance.js +16 -2
  106. package/dist/agent-runtime/session/testing/memory-journal.js +4 -2
  107. package/dist/installer/cli.js +10 -1
  108. package/dist/installer/commands/init.js +15 -4
  109. package/dist/installer/phases/prereqs.d.ts +1 -1
  110. package/dist/installer/phases/prereqs.js +2 -2
  111. package/dist/installer/phases/scaffold.js +59 -11
  112. package/dist/pipeline/pipeline-state.d.ts +15 -0
  113. package/dist/pipeline/pipeline-state.js +36 -3
  114. package/dist/pipeline/spec-trace.d.ts +134 -0
  115. package/dist/pipeline/spec-trace.js +392 -0
  116. package/dist/shared/secret-redaction.d.ts +42 -0
  117. package/dist/shared/secret-redaction.js +184 -0
  118. package/integration-contract.json +177 -7
  119. package/package.json +8 -8
  120. package/pinned-versions.json +1 -1
  121. package/schemas/agent-runtime.schema.json +3 -1
  122. package/schemas/workflow-definition.schema.json +1 -0
  123. package/templates/settings/codex-config.toml +3 -2
@@ -0,0 +1,65 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { changeSet } from '../change-scope.js';
4
+ import { child, write } from './artifacts.js';
5
+ /**
6
+ * `openspec/capability-map.json`: for every capability, the files archived
7
+ * changes touched. The archive node maintains it; baseline-on-touch reads it
8
+ * to decide whether the blast radius of a new change is already covered by a
9
+ * capability. Paths are repository-relative, prefixed `<repositoryId>:` for
10
+ * repositories other than the artifact repository.
11
+ */
12
+ export const CAPABILITY_MAP_PATH = 'openspec/capability-map.json';
13
+ export const CAPABILITY_MAP_SCHEMA_VERSION = 1;
14
+ const FILE_LIMIT = 2000;
15
+ const CHANGE_LIMIT = 500;
16
+ export function readCapabilityMap(context) {
17
+ try {
18
+ const file = child(context.artifactRoot, CAPABILITY_MAP_PATH);
19
+ if (!existsSync(file))
20
+ return { schemaVersion: CAPABILITY_MAP_SCHEMA_VERSION, capabilities: {} };
21
+ const value = JSON.parse(readFileSync(file, 'utf8'));
22
+ if (value?.schemaVersion !== CAPABILITY_MAP_SCHEMA_VERSION || !value.capabilities || typeof value.capabilities !== 'object')
23
+ return { schemaVersion: CAPABILITY_MAP_SCHEMA_VERSION, capabilities: {} };
24
+ return value;
25
+ }
26
+ catch {
27
+ return { schemaVersion: CAPABILITY_MAP_SCHEMA_VERSION, capabilities: {} };
28
+ }
29
+ }
30
+ /** `<repositoryId>:<path>` outside the artifact repository, the bare path inside it. */
31
+ export function qualifiedPath(context, repositoryId, file) {
32
+ return repositoryId === context.artifactRepositoryId ? file : repositoryId + ':' + file;
33
+ }
34
+ /** Capabilities a change's delta specs touch (active or archived change directory). */
35
+ export function changeCapabilities(changeDirectory) {
36
+ const specs = path.join(changeDirectory, 'specs');
37
+ try {
38
+ return readdirSync(specs, { withFileTypes: true }).filter(entry => entry.isDirectory() && existsSync(path.join(specs, entry.name, 'spec.md'))).map(entry => entry.name).sort();
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ }
44
+ /**
45
+ * Merges an archived change into the map: its capabilities gain the files the
46
+ * run changed (OpenSpec artifacts and Core metadata excluded). Idempotent, so
47
+ * a repeated archive step after a crash records nothing twice.
48
+ */
49
+ export function recordArchivedChange(context, change, archiveDirectory) {
50
+ const capabilities = changeCapabilities(archiveDirectory);
51
+ const map = readCapabilityMap(context);
52
+ if (!capabilities.length)
53
+ return map;
54
+ const files = changeSet(context, change).flatMap(set => set.files.filter(file => file.status !== 'deleted' && !/^(?:openspec|\.specrails)\//.test(file.path)).map(file => qualifiedPath(context, set.repositoryId, file.path)));
55
+ for (const capability of capabilities) {
56
+ const entry = map.capabilities[capability] ?? { files: [], changes: [] };
57
+ map.capabilities[capability] = {
58
+ files: [...new Set([...entry.files, ...files])].sort().slice(0, FILE_LIMIT),
59
+ changes: [...new Set([...entry.changes, change])].slice(-CHANGE_LIMIT),
60
+ };
61
+ }
62
+ map.capabilities = Object.fromEntries(Object.entries(map.capabilities).sort(([left], [right]) => left.localeCompare(right, 'en')));
63
+ write(child(context.artifactRoot, CAPABILITY_MAP_PATH), JSON.stringify(map, null, 2) + '\n');
64
+ return map;
65
+ }
@@ -0,0 +1,75 @@
1
+ import { type PipelineContext } from '../../pipeline/pipeline-state.js';
2
+ /**
3
+ * The architect's clarifications: the short list of ambiguities it found
4
+ * before planning, each with its impact and the default it would choose.
5
+ * A high-impact question without a safe default pauses the run (policy
6
+ * `ask-on-high`); everything else proceeds and is written down in design.md
7
+ * under Assumptions. Core also enforces a non-empty Non-Goals section.
8
+ */
9
+ export declare const CLARIFICATIONS_SCHEMA_VERSION = 1;
10
+ export declare const CLARIFICATION_LIMIT = 5;
11
+ export declare const CLARIFICATION_IMPACTS: readonly ['high', 'medium', 'low'];
12
+ export declare const CLARIFICATION_POLICIES: readonly ['ask-on-high', 'always-proceed'];
13
+ export type ClarificationImpact = typeof CLARIFICATION_IMPACTS[number];
14
+ export type ClarificationPolicy = typeof CLARIFICATION_POLICIES[number];
15
+ export declare const DEFAULT_CLARIFICATION_POLICY: ClarificationPolicy;
16
+ export interface Clarification {
17
+ question: string;
18
+ impact: ClarificationImpact;
19
+ /** The answer the architect would assume; null when no safe default exists. */
20
+ default: string | null;
21
+ }
22
+ /** The optional `clarifications` property of ARCHITECT_OUTPUT_SCHEMA. */
23
+ export declare const CLARIFICATIONS_SCHEMA: Record<string, unknown>;
24
+ /** Throws a repairable `Invalid architect clarifications` error for a malformed list. */
25
+ export declare function parseClarifications(value: unknown): Clarification[];
26
+ /** The clarifications that pause the run under the policy: high impact without a safe default. */
27
+ export declare function blockingClarifications(clarifications: readonly Clarification[], policy?: ClarificationPolicy): Clarification[];
28
+ /** True when a raw architect reply carries a high-impact clarification without a default: it may stop before authoring artifacts. */
29
+ export declare function asksBeforePlanning(value: unknown): boolean;
30
+ /** The single question the paused run carries (`pendingQuestion.question`). */
31
+ export declare function clarificationQuestion(blocking: readonly Clarification[], lowConfidenceQuestion?: string): string;
32
+ /**
33
+ * The content lines of a design.md section, or undefined when the section is
34
+ * absent. A section is a heading that contains the name (`## Non-Goals`,
35
+ * `## Goals / Non-Goals` without labels) or a bold label (`**Non-Goals:**`),
36
+ * which takes precedence; it ends at the next heading or label. HTML comments
37
+ * (template placeholders) and empty bullets do not count as content.
38
+ */
39
+ export declare function designSection(markdown: string, name: RegExp): string[] | undefined;
40
+ export declare const NON_GOALS: RegExp;
41
+ export declare const ASSUMPTIONS: RegExp;
42
+ /**
43
+ * Validates the active change's design.md once the architect proceeds:
44
+ * a non-empty Non-Goals section always, and a non-empty Assumptions section
45
+ * when it reported clarifications. Missing design.md is left to the design
46
+ * gate. Throws a repairable `Invalid design.md` error.
47
+ */
48
+ export declare function validateDesignSections(context: PipelineContext, change: string, options: {
49
+ clarifications: readonly Clarification[];
50
+ policy?: ClarificationPolicy;
51
+ }): void;
52
+ export interface ClarificationsRecord {
53
+ schemaVersion: typeof CLARIFICATIONS_SCHEMA_VERSION;
54
+ change: string;
55
+ policy: ClarificationPolicy;
56
+ /** `awaiting-answers`: the run paused on `blocking`; `proceeded`: the architect planned on its defaults and the answers. */
57
+ status: 'awaiting-answers' | 'proceeded';
58
+ recordedAt: string;
59
+ clarifications: Clarification[];
60
+ /** The high-impact questions without a safe default the run paused on (empty once it proceeds). */
61
+ blocking: Clarification[];
62
+ /** Answers the requester gave so far, oldest first; authoritative for the design. */
63
+ answers: string[];
64
+ /** The Assumptions lines design.md records once the architect proceeds (empty when none). */
65
+ assumptions: string[];
66
+ }
67
+ export declare const CLARIFICATIONS_FILE = "clarifications.json";
68
+ export declare function clarificationsPath(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>): string;
69
+ export declare function writeClarificationsRecord(context: PipelineContext, entry: Omit<ClarificationsRecord, 'schemaVersion' | 'recordedAt' | 'assumptions'> & {
70
+ assumptions?: string[];
71
+ }): ClarificationsRecord;
72
+ /** The Assumptions lines of the active change's design.md, for the host record. */
73
+ export declare function designAssumptions(context: PipelineContext, change: string): string[];
74
+ /** The latest clarifications record of a run, or null when the architect has not recorded one (or it is unreadable). */
75
+ export declare function readClarificationsRecord(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>): ClarificationsRecord | null;
@@ -0,0 +1,158 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pipelineStateDirectory } from '../../pipeline/pipeline-state.js';
4
+ import { child, write } from './artifacts.js';
5
+ /**
6
+ * The architect's clarifications: the short list of ambiguities it found
7
+ * before planning, each with its impact and the default it would choose.
8
+ * A high-impact question without a safe default pauses the run (policy
9
+ * `ask-on-high`); everything else proceeds and is written down in design.md
10
+ * under Assumptions. Core also enforces a non-empty Non-Goals section.
11
+ */
12
+ export const CLARIFICATIONS_SCHEMA_VERSION = 1;
13
+ export const CLARIFICATION_LIMIT = 5;
14
+ export const CLARIFICATION_IMPACTS = ['high', 'medium', 'low'];
15
+ export const CLARIFICATION_POLICIES = ['ask-on-high', 'always-proceed'];
16
+ export const DEFAULT_CLARIFICATION_POLICY = 'ask-on-high';
17
+ const TEXT_LIMIT = 2000;
18
+ /** The optional `clarifications` property of ARCHITECT_OUTPUT_SCHEMA. */
19
+ export const CLARIFICATIONS_SCHEMA = {
20
+ type: 'array', maxItems: CLARIFICATION_LIMIT,
21
+ description: 'Ambiguities found before planning; empty or omitted when the ticket is clear.',
22
+ items: { type: 'object', additionalProperties: false, required: ['question', 'impact', 'default'], properties: {
23
+ question: { type: 'string', minLength: 1, maxLength: TEXT_LIMIT },
24
+ impact: { type: 'string', enum: [...CLARIFICATION_IMPACTS] },
25
+ default: { type: ['string', 'null'], maxLength: TEXT_LIMIT, description: 'The answer you would assume, or null when no safe default exists.' },
26
+ } },
27
+ };
28
+ /** Throws a repairable `Invalid architect clarifications` error for a malformed list. */
29
+ export function parseClarifications(value) {
30
+ if (value === undefined || value === null)
31
+ return [];
32
+ const fail = (detail) => { throw new Error('Invalid architect clarifications: ' + detail); };
33
+ if (!Array.isArray(value))
34
+ fail('expected an array');
35
+ const list = value;
36
+ if (list.length > CLARIFICATION_LIMIT)
37
+ fail(`at most ${CLARIFICATION_LIMIT} entries`);
38
+ return list.map((item, index) => {
39
+ if (!item || typeof item !== 'object' || Array.isArray(item))
40
+ return fail(`entry ${index} must be an object`);
41
+ const entry = item;
42
+ if (typeof entry.question !== 'string' || !entry.question.trim())
43
+ return fail(`entry ${index} needs a question`);
44
+ if (!CLARIFICATION_IMPACTS.includes(entry.impact))
45
+ return fail(`entry ${index} impact must be one of ${CLARIFICATION_IMPACTS.join(', ')}`);
46
+ if (entry.default !== undefined && entry.default !== null && typeof entry.default !== 'string')
47
+ return fail(`entry ${index} default must be a string or null`);
48
+ const fallback = typeof entry.default === 'string' && entry.default.trim() ? entry.default.trim().slice(0, TEXT_LIMIT) : null;
49
+ return { question: entry.question.trim().slice(0, TEXT_LIMIT), impact: entry.impact, default: fallback };
50
+ });
51
+ }
52
+ /** The clarifications that pause the run under the policy: high impact without a safe default. */
53
+ export function blockingClarifications(clarifications, policy = DEFAULT_CLARIFICATION_POLICY) {
54
+ return policy === 'ask-on-high' ? clarifications.filter(item => item.impact === 'high' && item.default === null) : [];
55
+ }
56
+ /** True when a raw architect reply carries a high-impact clarification without a default: it may stop before authoring artifacts. */
57
+ export function asksBeforePlanning(value) {
58
+ return Array.isArray(value) && value.some(item => !!item && typeof item === 'object' && item.impact === 'high'
59
+ && !item.default?.toString().trim());
60
+ }
61
+ /** The single question the paused run carries (`pendingQuestion.question`). */
62
+ export function clarificationQuestion(blocking, lowConfidenceQuestion) {
63
+ const questions = [...blocking.map(item => item.question), ...(lowConfidenceQuestion && !blocking.some(item => item.question === lowConfidenceQuestion) ? [lowConfidenceQuestion] : [])];
64
+ if (questions.length === 1)
65
+ return questions[0];
66
+ return ['Before planning, the architect needs answers to:', ...questions.map((question, index) => `${index + 1}. ${question}`)].join('\n');
67
+ }
68
+ const HEADING = /^\s{0,3}#{1,6}\s+(.*?)\s*#*\s*$/;
69
+ /** A bold or underscored label on its own line, such as OpenSpec's `**Non-Goals:**`. */
70
+ const LABEL = /^\s*(?:[-*+]\s+)?(?:\*\*|__)\s*([^*_]+?)\s*:?\s*(?:\*\*|__)\s*:?\s*(.*)$/;
71
+ function meaningful(line) {
72
+ return line.replace(/<!--[\s\S]*?-->/g, '').replace(/^\s*(?:[-*+]|\d+\.)\s*$/, '').trim().length > 0;
73
+ }
74
+ /**
75
+ * The content lines of a design.md section, or undefined when the section is
76
+ * absent. A section is a heading that contains the name (`## Non-Goals`,
77
+ * `## Goals / Non-Goals` without labels) or a bold label (`**Non-Goals:**`),
78
+ * which takes precedence; it ends at the next heading or label. HTML comments
79
+ * (template placeholders) and empty bullets do not count as content.
80
+ */
81
+ export function designSection(markdown, name) {
82
+ const lines = markdown.replace(/<!--[\s\S]*?-->/g, '').split(/\r?\n/);
83
+ const collect = (start, inline, stopAtLabels) => {
84
+ const content = inline.trim() ? [inline.trim()] : [];
85
+ for (let index = start + 1; index < lines.length; index++) {
86
+ const line = lines[index];
87
+ if (HEADING.test(line) || (stopAtLabels && LABEL.test(line) && !LABEL.exec(line)[2].trim() && !/^\s*[-*+]\s/.test(line)))
88
+ break;
89
+ if (meaningful(line))
90
+ content.push(line.trim());
91
+ }
92
+ return content;
93
+ };
94
+ const label = lines.findIndex(line => { const match = LABEL.exec(line); return !!match && name.test(match[1]) && !HEADING.test(line); });
95
+ if (label >= 0)
96
+ return collect(label, LABEL.exec(lines[label])[2], true);
97
+ const heading = lines.findIndex(line => { const match = HEADING.exec(line); return !!match && name.test(match[1]); });
98
+ if (heading >= 0)
99
+ return collect(heading, '', false);
100
+ return undefined;
101
+ }
102
+ export const NON_GOALS = /\bnon[\s-]?goals?\b/i;
103
+ export const ASSUMPTIONS = /\bassumptions?\b/i;
104
+ /**
105
+ * Validates the active change's design.md once the architect proceeds:
106
+ * a non-empty Non-Goals section always, and a non-empty Assumptions section
107
+ * when it reported clarifications. Missing design.md is left to the design
108
+ * gate. Throws a repairable `Invalid design.md` error.
109
+ */
110
+ export function validateDesignSections(context, change, options) {
111
+ const file = child(context.artifactRoot, 'openspec/changes/' + change + '/design.md');
112
+ if (!existsSync(file)) {
113
+ // Under always-proceed a reply that stopped on a question has nothing to proceed with.
114
+ if (options.policy === 'always-proceed' && options.clarifications.some(item => item.impact === 'high' && item.default === null))
115
+ throw new Error('Invalid architect reply: the clarification policy is always-proceed, so plan on your best default for every clarification, author the artifacts and record the defaults under `## Assumptions` in design.md.');
116
+ return;
117
+ }
118
+ const design = readFileSync(file, 'utf8');
119
+ const missing = [];
120
+ if (!designSection(design, NON_GOALS)?.length)
121
+ missing.push('a `## Non-Goals` section with at least one line naming what this change deliberately does not do');
122
+ if (options.clarifications.length && !designSection(design, ASSUMPTIONS)?.length)
123
+ missing.push('a `## Assumptions` section with one line per clarification you proceeded on (question and the answer you assumed)');
124
+ if (missing.length)
125
+ throw new Error(`Invalid design.md: it needs ${missing.join(' and ')}. Update design.md through the workflow's write_artifact action, then resend the same JSON reply.`);
126
+ }
127
+ export const CLARIFICATIONS_FILE = 'clarifications.json';
128
+ export function clarificationsPath(context) {
129
+ return path.join(pipelineStateDirectory(context), CLARIFICATIONS_FILE);
130
+ }
131
+ export function writeClarificationsRecord(context, entry) {
132
+ const record = { schemaVersion: CLARIFICATIONS_SCHEMA_VERSION, change: entry.change, policy: entry.policy, status: entry.status, recordedAt: new Date().toISOString(), clarifications: entry.clarifications, blocking: entry.blocking, answers: entry.answers, assumptions: entry.assumptions ?? [] };
133
+ write(clarificationsPath(context), JSON.stringify(record, null, 2) + '\n');
134
+ return record;
135
+ }
136
+ /** The Assumptions lines of the active change's design.md, for the host record. */
137
+ export function designAssumptions(context, change) {
138
+ try {
139
+ const file = child(context.artifactRoot, 'openspec/changes/' + change + '/design.md');
140
+ return existsSync(file) ? (designSection(readFileSync(file, 'utf8'), ASSUMPTIONS) ?? []).slice(0, 50).map(line => line.slice(0, TEXT_LIMIT)) : [];
141
+ }
142
+ catch {
143
+ return [];
144
+ }
145
+ }
146
+ /** The latest clarifications record of a run, or null when the architect has not recorded one (or it is unreadable). */
147
+ export function readClarificationsRecord(context) {
148
+ try {
149
+ const file = clarificationsPath(context);
150
+ if (!existsSync(file))
151
+ return null;
152
+ const value = JSON.parse(readFileSync(file, 'utf8'));
153
+ return value && typeof value === 'object' && value.schemaVersion === CLARIFICATIONS_SCHEMA_VERSION ? value : null;
154
+ }
155
+ catch {
156
+ return null;
157
+ }
158
+ }