shift-ax 0.3.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 (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +145 -0
  3. package/README.md +143 -0
  4. package/dist/adapters/claude-code/adapter.js +90 -0
  5. package/dist/adapters/codex/adapter.js +94 -0
  6. package/dist/adapters/contracts.js +7 -0
  7. package/dist/adapters/index.js +12 -0
  8. package/dist/core/context/context-bundle.js +300 -0
  9. package/dist/core/context/discovery.js +82 -0
  10. package/dist/core/context/global-index-authoring.js +199 -0
  11. package/dist/core/context/global-knowledge-updates.js +116 -0
  12. package/dist/core/context/glossary.js +73 -0
  13. package/dist/core/context/guided-onboarding.js +233 -0
  14. package/dist/core/context/index-authoring.js +47 -0
  15. package/dist/core/context/index-resolver.js +78 -0
  16. package/dist/core/context/onboarding.js +186 -0
  17. package/dist/core/diagnostics/doctor.js +154 -0
  18. package/dist/core/finalization/commit-message.js +76 -0
  19. package/dist/core/finalization/commit-workflow.js +131 -0
  20. package/dist/core/memory/consolidation.js +99 -0
  21. package/dist/core/memory/decision-register.js +141 -0
  22. package/dist/core/memory/entity-memory.js +25 -0
  23. package/dist/core/memory/learned-debug.js +52 -0
  24. package/dist/core/memory/summary-checkpoints.js +9 -0
  25. package/dist/core/memory/thread-promotion.js +22 -0
  26. package/dist/core/memory/threads.js +66 -0
  27. package/dist/core/memory/topic-recall.js +52 -0
  28. package/dist/core/observability/context-health.js +15 -0
  29. package/dist/core/observability/context-monitor.js +29 -0
  30. package/dist/core/observability/state-handoff.js +78 -0
  31. package/dist/core/observability/topic-status.js +40 -0
  32. package/dist/core/observability/topics-status.js +26 -0
  33. package/dist/core/observability/verification-debt.js +82 -0
  34. package/dist/core/planning/brainstorm.js +120 -0
  35. package/dist/core/planning/escalation.js +69 -0
  36. package/dist/core/planning/execution-handoff.js +61 -0
  37. package/dist/core/planning/execution-launch.js +156 -0
  38. package/dist/core/planning/execution-orchestrator.js +87 -0
  39. package/dist/core/planning/feedback-reactions.js +75 -0
  40. package/dist/core/planning/lifecycle-events.js +45 -0
  41. package/dist/core/planning/plan-review.js +76 -0
  42. package/dist/core/planning/policy-context-sync.js +154 -0
  43. package/dist/core/planning/request-pipeline.js +386 -0
  44. package/dist/core/planning/workflow-state.js +18 -0
  45. package/dist/core/policies/project-profile.js +28 -0
  46. package/dist/core/policies/team-preferences.js +17 -0
  47. package/dist/core/review/aggregate-reviews.js +129 -0
  48. package/dist/core/review/run-lanes.js +376 -0
  49. package/dist/core/settings/global-context-home.js +28 -0
  50. package/dist/core/settings/project-settings.js +37 -0
  51. package/dist/core/shell/platform-shell.js +144 -0
  52. package/dist/core/topics/bootstrap.js +119 -0
  53. package/dist/core/topics/topic-artifacts.js +36 -0
  54. package/dist/core/topics/worktree-runtime.js +141 -0
  55. package/dist/core/topics/worktree.js +8 -0
  56. package/dist/platform/claude-code/bootstrap.js +66 -0
  57. package/dist/platform/claude-code/execution.js +157 -0
  58. package/dist/platform/claude-code/scaffold/CLAUDE.template.md +40 -0
  59. package/dist/platform/claude-code/scaffold/commands/doctor.template.md +11 -0
  60. package/dist/platform/claude-code/scaffold/commands/export-context.template.md +20 -0
  61. package/dist/platform/claude-code/scaffold/commands/onboard.template.md +43 -0
  62. package/dist/platform/claude-code/scaffold/commands/onboarding.template.md +43 -0
  63. package/dist/platform/claude-code/scaffold/commands/request.template.md +19 -0
  64. package/dist/platform/claude-code/scaffold/commands/resume.template.md +12 -0
  65. package/dist/platform/claude-code/scaffold/commands/review.template.md +10 -0
  66. package/dist/platform/claude-code/scaffold/commands/status.template.md +14 -0
  67. package/dist/platform/claude-code/scaffold/commands/topics.template.md +10 -0
  68. package/dist/platform/claude-code/scaffold/hooks/shift-ax-session-start.template.md +29 -0
  69. package/dist/platform/claude-code/tmux.js +35 -0
  70. package/dist/platform/claude-code/upstream/tmux/imported/detached-session.js +40 -0
  71. package/dist/platform/claude-code/upstream/tmux/imported/session-name.js +19 -0
  72. package/dist/platform/claude-code/upstream/worktree/imported/get-worktree-root.js +39 -0
  73. package/dist/platform/claude-code/upstream/worktree/imported/managed-worktree.js +77 -0
  74. package/dist/platform/claude-code/worktree.js +79 -0
  75. package/dist/platform/codex/bootstrap.js +69 -0
  76. package/dist/platform/codex/execution.js +163 -0
  77. package/dist/platform/codex/scaffold/AGENTS.template.md +40 -0
  78. package/dist/platform/codex/scaffold/prompts/doctor.template.md +11 -0
  79. package/dist/platform/codex/scaffold/prompts/export-context.template.md +20 -0
  80. package/dist/platform/codex/scaffold/prompts/onboard.template.md +43 -0
  81. package/dist/platform/codex/scaffold/prompts/onboarding.template.md +43 -0
  82. package/dist/platform/codex/scaffold/prompts/request.template.md +19 -0
  83. package/dist/platform/codex/scaffold/prompts/resume.template.md +14 -0
  84. package/dist/platform/codex/scaffold/prompts/review.template.md +10 -0
  85. package/dist/platform/codex/scaffold/prompts/shift-ax-bootstrap.template.md +23 -0
  86. package/dist/platform/codex/scaffold/prompts/status.template.md +14 -0
  87. package/dist/platform/codex/scaffold/prompts/topics.template.md +10 -0
  88. package/dist/platform/codex/scaffold/skills/doctor/SKILL.template.md +11 -0
  89. package/dist/platform/codex/scaffold/skills/export-context/SKILL.template.md +20 -0
  90. package/dist/platform/codex/scaffold/skills/onboard/SKILL.template.md +43 -0
  91. package/dist/platform/codex/scaffold/skills/request/SKILL.template.md +19 -0
  92. package/dist/platform/codex/scaffold/skills/resume/SKILL.template.md +14 -0
  93. package/dist/platform/codex/scaffold/skills/review/SKILL.template.md +10 -0
  94. package/dist/platform/codex/scaffold/skills/status/SKILL.template.md +14 -0
  95. package/dist/platform/codex/scaffold/skills/topics/SKILL.template.md +10 -0
  96. package/dist/platform/codex/tmux.js +45 -0
  97. package/dist/platform/codex/upstream/tmux/imported/resize-hook-registration.js +37 -0
  98. package/dist/platform/codex/upstream/tmux/imported/resize-hooks.js +29 -0
  99. package/dist/platform/codex/upstream/tmux/imported/sanitize-team-name.js +18 -0
  100. package/dist/platform/codex/upstream/worktree/imported/managed-worktree.js +208 -0
  101. package/dist/platform/codex/upstream/worktree/imported/resolve-repo-root.js +14 -0
  102. package/dist/platform/codex/worktree.js +99 -0
  103. package/dist/platform/index.js +10 -0
  104. package/dist/platform/product-shell-commands.js +17 -0
  105. package/dist/platform/scaffold.js +16 -0
  106. package/dist/platform/upstream-imports.js +5 -0
  107. package/dist/scripts/ax-approve-plan.js +30 -0
  108. package/dist/scripts/ax-bootstrap-assets.js +19 -0
  109. package/dist/scripts/ax-bootstrap-topic.js +24 -0
  110. package/dist/scripts/ax-build-context-bundle.js +35 -0
  111. package/dist/scripts/ax-checkpoint-context.js +22 -0
  112. package/dist/scripts/ax-consolidate-memory.js +7 -0
  113. package/dist/scripts/ax-context-health.js +26 -0
  114. package/dist/scripts/ax-decisions.js +32 -0
  115. package/dist/scripts/ax-doctor.js +25 -0
  116. package/dist/scripts/ax-entity-memory.js +19 -0
  117. package/dist/scripts/ax-export-context.js +8 -0
  118. package/dist/scripts/ax-finalize-commit.js +23 -0
  119. package/dist/scripts/ax-init-context.js +41 -0
  120. package/dist/scripts/ax-launch-execution.js +24 -0
  121. package/dist/scripts/ax-learned-debug-save.js +30 -0
  122. package/dist/scripts/ax-learned-debug.js +12 -0
  123. package/dist/scripts/ax-monitor-context.js +28 -0
  124. package/dist/scripts/ax-onboard-context.js +112 -0
  125. package/dist/scripts/ax-pause-work.js +33 -0
  126. package/dist/scripts/ax-platform-manifest.js +19 -0
  127. package/dist/scripts/ax-promote-thread.js +20 -0
  128. package/dist/scripts/ax-react-feedback.js +28 -0
  129. package/dist/scripts/ax-recall-topics.js +20 -0
  130. package/dist/scripts/ax-recall.js +58 -0
  131. package/dist/scripts/ax-refresh-state.js +15 -0
  132. package/dist/scripts/ax-resolve-context.js +34 -0
  133. package/dist/scripts/ax-review.js +24 -0
  134. package/dist/scripts/ax-run-request.js +198 -0
  135. package/dist/scripts/ax-scaffold-build.js +19 -0
  136. package/dist/scripts/ax-shell.js +123 -0
  137. package/dist/scripts/ax-sync-policy-context.js +40 -0
  138. package/dist/scripts/ax-team-preferences.js +20 -0
  139. package/dist/scripts/ax-thread-save.js +26 -0
  140. package/dist/scripts/ax-threads.js +11 -0
  141. package/dist/scripts/ax-topic-status.js +18 -0
  142. package/dist/scripts/ax-topics-status.js +22 -0
  143. package/dist/scripts/ax-verification-debt.js +22 -0
  144. package/dist/scripts/ax-worktree-create.js +22 -0
  145. package/dist/scripts/ax-worktree-plan.js +18 -0
  146. package/dist/scripts/ax-worktree-remove.js +18 -0
  147. package/dist/scripts/ax.js +132 -0
  148. package/package.json +71 -0
@@ -0,0 +1,386 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile, writeFile } from 'node:fs/promises';
4
+ import { relative } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { resolveContextFromIndex } from '../context/index-resolver.js';
7
+ import { applyGlobalKnowledgeUpdatesFromArtifacts } from '../context/global-knowledge-updates.js';
8
+ import { ensureTopicCommitMessageArtifact } from '../finalization/commit-workflow.js';
9
+ import { finalizeTopicCommit } from '../finalization/commit-workflow.js';
10
+ import { readProjectProfile } from '../policies/project-profile.js';
11
+ import { aggregateReviewVerdicts, writeAggregateReviewArtifacts, } from '../review/aggregate-reviews.js';
12
+ import { runReviewLanes } from '../review/run-lanes.js';
13
+ import { bootstrapTopic, } from '../topics/bootstrap.js';
14
+ import { topicArtifactPath, getRootDirFromTopicDir, } from '../topics/topic-artifacts.js';
15
+ import { createTopicWorktree, } from '../topics/worktree-runtime.js';
16
+ import { verifyApprovedPlanFingerprint } from './plan-review.js';
17
+ import { clearWorkflowEscalations, recordWorkflowEscalations, } from './escalation.js';
18
+ import { writeExecutionHandoff } from './execution-handoff.js';
19
+ import { recordLifecycleEvent } from './lifecycle-events.js';
20
+ import { inferPolicyContextSyncArtifact, readPolicyContextSyncArtifact, writePolicyContextSyncArtifact, } from './policy-context-sync.js';
21
+ import { hasActiveWorkflowEscalations, readWorkflowState, writeWorkflowState, } from './workflow-state.js';
22
+ import { getGlobalContextHome } from '../settings/global-context-home.js';
23
+ const execFileAsync = promisify(execFile);
24
+ async function resolveRequestContext({ rootDir, request, contextQuery, indexPath = getGlobalContextHome().indexPath, maxMatches = 5, }) {
25
+ const effectiveQuery = contextQuery?.trim() || request.trim();
26
+ if (existsSync(indexPath)) {
27
+ return resolveContextFromIndex({
28
+ rootDir,
29
+ indexPath,
30
+ indexRootDir: getGlobalContextHome().root,
31
+ query: effectiveQuery,
32
+ maxMatches,
33
+ });
34
+ }
35
+ return {
36
+ version: 1,
37
+ index_path: indexPath,
38
+ query: effectiveQuery,
39
+ matches: [],
40
+ unresolved_paths: [relative(rootDir, indexPath) || indexPath],
41
+ };
42
+ }
43
+ function buildDefaultBrainstorm(request, matchedLabels) {
44
+ return [
45
+ '# Brainstorm',
46
+ '',
47
+ '## Request',
48
+ '',
49
+ request.trim(),
50
+ '',
51
+ '## Relevant Context',
52
+ '',
53
+ ...(matchedLabels.length > 0 ? matchedLabels.map((label) => `- ${label}`) : ['- No matched context documents yet.']),
54
+ '',
55
+ '## Global Knowledge Updates',
56
+ '',
57
+ '- None yet.',
58
+ '',
59
+ ].join('\n');
60
+ }
61
+ function buildDefaultSpec(request, matchedLabels) {
62
+ return [
63
+ '# Topic Spec',
64
+ '',
65
+ '## Goal',
66
+ '',
67
+ request.trim(),
68
+ '',
69
+ '## Relevant Context',
70
+ '',
71
+ ...(matchedLabels.length > 0 ? matchedLabels.map((label) => `- ${label}`) : ['- No matched context documents yet.']),
72
+ '',
73
+ '## Global Knowledge Updates',
74
+ '',
75
+ '- None yet.',
76
+ '',
77
+ ].join('\n');
78
+ }
79
+ function buildDefaultImplementationPlan(testStrategy, architecture) {
80
+ return [
81
+ '# Implementation Plan',
82
+ '',
83
+ `Use ${testStrategy.toUpperCase()} first.`,
84
+ `Respect ${architecture.replace(/-/g, ' ')}.`,
85
+ 'Add focused verification steps before commit finalization.',
86
+ '',
87
+ '## Global Knowledge Updates',
88
+ '',
89
+ '- None yet.',
90
+ '',
91
+ ].join('\n');
92
+ }
93
+ async function writeVerificationReport(topicDir, verification) {
94
+ const lines = ['# Final Verification', ''];
95
+ if (verification.length === 0) {
96
+ lines.push('No verification commands were provided.');
97
+ }
98
+ else {
99
+ for (const item of verification) {
100
+ lines.push(`## ${item.command}`);
101
+ lines.push('');
102
+ lines.push(`- exit_code: ${item.exit_code}`);
103
+ lines.push('');
104
+ lines.push('```text');
105
+ lines.push(item.stdout.trim() || item.stderr.trim() || '(no output)');
106
+ lines.push('```');
107
+ lines.push('');
108
+ }
109
+ }
110
+ await writeFile(topicArtifactPath(topicDir, 'verification'), `${lines.join('\n')}\n`, 'utf8');
111
+ }
112
+ async function runVerificationCommands(cwd, commands) {
113
+ const results = [];
114
+ for (const command of commands) {
115
+ try {
116
+ const { stdout, stderr } = await execFileAsync('/bin/sh', ['-lc', command], { cwd });
117
+ results.push({
118
+ command,
119
+ exit_code: 0,
120
+ stdout,
121
+ stderr,
122
+ });
123
+ }
124
+ catch (error) {
125
+ const failed = error;
126
+ results.push({
127
+ command,
128
+ exit_code: typeof failed.code === 'number' ? failed.code : 1,
129
+ stdout: failed.stdout ?? '',
130
+ stderr: failed.stderr ?? '',
131
+ });
132
+ break;
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ async function resolveExecutionCwd(topicDir) {
138
+ const rootDir = getRootDirFromTopicDir(topicDir);
139
+ try {
140
+ const raw = await readFile(topicArtifactPath(topicDir, 'worktree_state'), 'utf8');
141
+ const state = JSON.parse(raw);
142
+ if (state.worktree_path &&
143
+ ['created', 'reused'].includes(String(state.status || '')) &&
144
+ existsSync(state.worktree_path)) {
145
+ return state.worktree_path;
146
+ }
147
+ }
148
+ catch {
149
+ // fall back to rootDir
150
+ }
151
+ return rootDir;
152
+ }
153
+ async function assertResolvedContextReady(topicDir) {
154
+ const raw = await readFile(topicArtifactPath(topicDir, 'resolved_context'), 'utf8').catch(() => '');
155
+ if (!raw) {
156
+ throw new Error('resolved context artifact is missing');
157
+ }
158
+ const parsed = JSON.parse(raw);
159
+ if ((parsed.unresolved_paths ?? []).length > 0) {
160
+ throw new Error('resolved context still has unresolved base-context paths');
161
+ }
162
+ }
163
+ export async function startRequestPipeline({ rootDir, request, summary, indexPath, contextQuery, maxMatches, brainstormContent, specContent, implementationPlanContent, baseBranch = 'main', allowMissingGlobalIndex = false, now = new Date(), }) {
164
+ const topic = await bootstrapTopic({
165
+ rootDir,
166
+ request,
167
+ summary,
168
+ now,
169
+ });
170
+ const profile = await readProjectProfile(rootDir);
171
+ const resolvedContext = await resolveRequestContext({
172
+ rootDir,
173
+ request,
174
+ contextQuery,
175
+ indexPath,
176
+ maxMatches,
177
+ });
178
+ const matchedLabels = resolvedContext.matches.map((match) => match.label);
179
+ if (!existsSync(indexPath ?? getGlobalContextHome().indexPath) && !allowMissingGlobalIndex) {
180
+ throw new Error('global context index is missing. Run /onboarding first, or pass --allow-missing-global-context to continue with reduced accuracy.');
181
+ }
182
+ if (resolvedContext.unresolved_paths.length > 0) {
183
+ throw new Error('resolved context still has unresolved base-context paths');
184
+ }
185
+ await writeFile(topicArtifactPath(topic.topicDir, 'resolved_context'), `${JSON.stringify(resolvedContext, null, 2)}\n`, 'utf8');
186
+ await writeFile(topicArtifactPath(topic.topicDir, 'brainstorm'), `${brainstormContent ?? buildDefaultBrainstorm(request, matchedLabels)}\n`, 'utf8');
187
+ await writeFile(topicArtifactPath(topic.topicDir, 'spec'), `${specContent ?? buildDefaultSpec(request, matchedLabels)}\n`, 'utf8');
188
+ await writeFile(topicArtifactPath(topic.topicDir, 'implementation_plan'), `${implementationPlanContent ?? buildDefaultImplementationPlan(profile?.engineering_defaults.test_strategy ?? 'tdd', profile?.engineering_defaults.architecture ?? 'clean-boundaries')}\n`, 'utf8');
189
+ await applyGlobalKnowledgeUpdatesFromArtifacts({
190
+ brainstormContent: brainstormContent ?? buildDefaultBrainstorm(request, matchedLabels),
191
+ specContent: specContent ?? buildDefaultSpec(request, matchedLabels),
192
+ implementationPlanContent: implementationPlanContent ??
193
+ buildDefaultImplementationPlan(profile?.engineering_defaults.test_strategy ?? 'tdd', profile?.engineering_defaults.architecture ?? 'clean-boundaries'),
194
+ });
195
+ await writePolicyContextSyncArtifact(topic.topicDir, inferPolicyContextSyncArtifact({
196
+ brainstormContent: brainstormContent ?? buildDefaultBrainstorm(request, matchedLabels),
197
+ specContent: specContent ?? buildDefaultSpec(request, matchedLabels),
198
+ implementationPlanContent: implementationPlanContent ??
199
+ buildDefaultImplementationPlan(profile?.engineering_defaults.test_strategy ?? 'tdd', profile?.engineering_defaults.architecture ?? 'clean-boundaries'),
200
+ now,
201
+ }));
202
+ await writeExecutionHandoff(topic.topicDir, now);
203
+ const worktree = await createTopicWorktree({
204
+ topicDir: topic.topicDir,
205
+ baseBranch,
206
+ });
207
+ const workflow = {
208
+ ...(await readWorkflowState(topic.topicDir)),
209
+ updated_at: now.toISOString(),
210
+ phase: 'awaiting_plan_review',
211
+ plan_review_status: 'pending',
212
+ worktree: {
213
+ branch_name: worktree.branch_name,
214
+ worktree_path: worktree.worktree_path,
215
+ base_branch: worktree.base_branch,
216
+ },
217
+ resolved_context: {
218
+ index_path: resolvedContext.index_path,
219
+ query: resolvedContext.query,
220
+ matches: resolvedContext.matches.length,
221
+ unresolved_paths: resolvedContext.unresolved_paths,
222
+ },
223
+ escalation: {
224
+ status: 'clear',
225
+ triggers: [],
226
+ },
227
+ };
228
+ await writeWorkflowState(topic.topicDir, workflow);
229
+ await recordLifecycleEvent({
230
+ topicDir: topic.topicDir,
231
+ phase: workflow.phase,
232
+ event: 'plan.review_required',
233
+ summary: 'Waiting for the human plan review.',
234
+ now,
235
+ });
236
+ return {
237
+ ...topic,
238
+ resolvedContext,
239
+ worktree,
240
+ workflow,
241
+ };
242
+ }
243
+ export async function resumeRequestPipeline({ topicDir, verificationCommands = [], escalationTriggers = [], clearEscalations = false, escalationResolution, autoCommit = false, executionRunner, now = new Date(), }) {
244
+ if (clearEscalations) {
245
+ await clearWorkflowEscalations({
246
+ topicDir,
247
+ resolution: escalationResolution,
248
+ now,
249
+ });
250
+ }
251
+ if (escalationTriggers.length > 0) {
252
+ await recordWorkflowEscalations({
253
+ topicDir,
254
+ triggers: escalationTriggers,
255
+ now,
256
+ });
257
+ await recordLifecycleEvent({
258
+ topicDir,
259
+ phase: 'awaiting_human_escalation',
260
+ event: 'execution.blocked',
261
+ summary: 'Execution stopped because a mandatory escalation trigger was raised.',
262
+ reaction: {
263
+ key: escalationTriggers[0].kind,
264
+ action: 'await_human_escalation',
265
+ outcome: 'blocked',
266
+ },
267
+ now,
268
+ });
269
+ throw new Error('workflow requires human escalation review before automation can continue');
270
+ }
271
+ const fingerprint = await verifyApprovedPlanFingerprint({ topicDir });
272
+ if (!fingerprint.matches) {
273
+ throw new Error(fingerprint.reason ?? 'approved plan fingerprint check failed');
274
+ }
275
+ await assertResolvedContextReady(topicDir);
276
+ const workflow = await readWorkflowState(topicDir);
277
+ if (hasActiveWorkflowEscalations(workflow)) {
278
+ throw new Error('workflow has active escalation triggers; resolve them before resuming automation');
279
+ }
280
+ const policyContextSync = await readPolicyContextSyncArtifact(topicDir);
281
+ if (policyContextSync.status === 'required') {
282
+ workflow.phase = 'awaiting_policy_sync';
283
+ workflow.updated_at = now.toISOString();
284
+ await writeWorkflowState(topicDir, workflow);
285
+ throw new Error('policy context sync is required before implementation can start');
286
+ }
287
+ workflow.phase = 'implementation_running';
288
+ workflow.plan_review_status = 'approved';
289
+ workflow.updated_at = now.toISOString();
290
+ await writeWorkflowState(topicDir, workflow);
291
+ await recordLifecycleEvent({
292
+ topicDir,
293
+ phase: workflow.phase,
294
+ event: 'execution.started',
295
+ summary: 'Execution resumed after human plan approval.',
296
+ now,
297
+ });
298
+ const executionCwd = await resolveExecutionCwd(topicDir);
299
+ if (executionRunner) {
300
+ const executionState = await executionRunner({
301
+ topicDir,
302
+ worktreePath: executionCwd,
303
+ });
304
+ await writeFile(topicArtifactPath(topicDir, 'execution_state'), `${JSON.stringify(executionState, null, 2)}\n`, 'utf8');
305
+ if (executionState.overall_status !== 'completed') {
306
+ throw new Error('execution orchestration failed');
307
+ }
308
+ }
309
+ const verification = await runVerificationCommands(executionCwd, verificationCommands);
310
+ await writeVerificationReport(topicDir, verification);
311
+ workflow.verification = verification;
312
+ if (verification.some((item) => item.exit_code !== 0)) {
313
+ workflow.updated_at = now.toISOString();
314
+ await writeWorkflowState(topicDir, workflow);
315
+ throw new Error('verification commands failed');
316
+ }
317
+ workflow.phase = 'review_pending';
318
+ workflow.updated_at = now.toISOString();
319
+ await writeWorkflowState(topicDir, workflow);
320
+ await recordLifecycleEvent({
321
+ topicDir,
322
+ phase: workflow.phase,
323
+ event: 'review.started',
324
+ summary: 'Structured review lanes are running.',
325
+ now,
326
+ });
327
+ await runReviewLanes({ topicDir });
328
+ const aggregate = await aggregateReviewVerdicts({ topicDir });
329
+ await writeAggregateReviewArtifacts(topicDir, aggregate);
330
+ workflow.review = {
331
+ overall_status: aggregate.overall_status,
332
+ commit_allowed: aggregate.commit_allowed,
333
+ next_stage: aggregate.next_stage,
334
+ };
335
+ if (aggregate.commit_allowed) {
336
+ await ensureTopicCommitMessageArtifact({
337
+ topicDir,
338
+ verification,
339
+ });
340
+ }
341
+ workflow.phase = aggregate.commit_allowed ? 'commit_ready' : 'implementation_running';
342
+ workflow.updated_at = now.toISOString();
343
+ await writeWorkflowState(topicDir, workflow);
344
+ await recordLifecycleEvent({
345
+ topicDir,
346
+ phase: workflow.phase,
347
+ event: 'review.completed',
348
+ summary: aggregate.commit_allowed
349
+ ? 'Review passed and the topic is commit-ready.'
350
+ : 'Review requested more implementation work.',
351
+ ...(aggregate.commit_allowed
352
+ ? {}
353
+ : {
354
+ reaction: {
355
+ key: 'review-changes-requested',
356
+ action: 'return_to_implementation',
357
+ outcome: 'changes_requested',
358
+ },
359
+ }),
360
+ now,
361
+ });
362
+ let finalization;
363
+ if (aggregate.commit_allowed && autoCommit) {
364
+ finalization = await finalizeTopicCommit({
365
+ topicDir,
366
+ now,
367
+ });
368
+ workflow.phase = 'committed';
369
+ workflow.updated_at = now.toISOString();
370
+ await writeWorkflowState(topicDir, workflow);
371
+ await recordLifecycleEvent({
372
+ topicDir,
373
+ phase: workflow.phase,
374
+ event: 'finalization.committed',
375
+ summary: 'Automatic finalization created the local commit.',
376
+ now,
377
+ });
378
+ }
379
+ return {
380
+ workflow,
381
+ aggregate,
382
+ verification,
383
+ ...(finalization ? { finalization } : {}),
384
+ };
385
+ }
386
+ export { readWorkflowState } from './workflow-state.js';
@@ -0,0 +1,18 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { topicArtifactPath } from '../topics/topic-artifacts.js';
3
+ export const SHIFT_AX_ESCALATION_KINDS = [
4
+ 'new-user-flow',
5
+ 'policy-conflict',
6
+ 'risky-data-or-permission-change',
7
+ ];
8
+ export function hasActiveWorkflowEscalations(state) {
9
+ return (state.escalation?.status === 'required' &&
10
+ state.escalation.triggers.some((trigger) => !trigger.resolved_at));
11
+ }
12
+ export async function readWorkflowState(topicDir) {
13
+ const raw = await readFile(topicArtifactPath(topicDir, 'workflow_state'), 'utf8');
14
+ return JSON.parse(raw);
15
+ }
16
+ export async function writeWorkflowState(topicDir, state) {
17
+ await writeFile(topicArtifactPath(topicDir, 'workflow_state'), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
18
+ }
@@ -0,0 +1,28 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { getGlobalContextHome } from '../settings/global-context-home.js';
3
+ export function defaultEngineeringDefaults() {
4
+ return {
5
+ test_strategy: 'tdd',
6
+ architecture: 'clean-boundaries',
7
+ short_task_execution: 'subagent',
8
+ long_task_execution: 'tmux',
9
+ verification_commands: ['npm test', 'npm run build'],
10
+ };
11
+ }
12
+ export function getProjectProfilePath(rootDir) {
13
+ return getGlobalContextHome().profilePath;
14
+ }
15
+ export async function readProjectProfile(rootDir) {
16
+ try {
17
+ const raw = await readFile(getProjectProfilePath(rootDir), 'utf8');
18
+ return JSON.parse(raw);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export async function writeProjectProfile(rootDir, profile) {
25
+ const path = getProjectProfilePath(rootDir);
26
+ await mkdir(getGlobalContextHome().root, { recursive: true });
27
+ await writeFile(path, `${JSON.stringify(profile, null, 2)}\n`, 'utf8');
28
+ }
@@ -0,0 +1,17 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ function path(rootDir) {
4
+ return join(rootDir, '.ax', 'team-preferences.json');
5
+ }
6
+ export async function readTeamPreferences(rootDir) {
7
+ try {
8
+ return JSON.parse(await readFile(path(rootDir), 'utf8'));
9
+ }
10
+ catch {
11
+ return null;
12
+ }
13
+ }
14
+ export async function writeTeamPreferences({ rootDir, preferences, }) {
15
+ await mkdir(join(rootDir, '.ax'), { recursive: true });
16
+ await writeFile(path(rootDir), `${JSON.stringify(preferences, null, 2)}\n`, 'utf8');
17
+ }
@@ -0,0 +1,129 @@
1
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export const REQUIRED_REVIEW_LANES = [
4
+ 'domain-policy',
5
+ 'spec-conformance',
6
+ 'test-adequacy',
7
+ 'engineering-discipline',
8
+ 'conversation-trace',
9
+ ];
10
+ async function readVerdictFile(path) {
11
+ const raw = await readFile(path, 'utf8');
12
+ return JSON.parse(raw);
13
+ }
14
+ export async function aggregateReviewVerdicts({ topicDir, requiredLanes = [...REQUIRED_REVIEW_LANES], }) {
15
+ if (!topicDir)
16
+ throw new Error('topicDir is required');
17
+ const reviewDir = join(topicDir, 'review');
18
+ let files = [];
19
+ try {
20
+ files = (await readdir(reviewDir))
21
+ .filter((entry) => entry.endsWith('.json'))
22
+ .sort();
23
+ }
24
+ catch {
25
+ files = [];
26
+ }
27
+ const verdicts = [];
28
+ for (const file of files) {
29
+ try {
30
+ const verdict = await readVerdictFile(join(reviewDir, file));
31
+ verdicts.push(verdict);
32
+ }
33
+ catch {
34
+ // Ignore malformed verdicts in aggregation.
35
+ }
36
+ }
37
+ const byLane = new Map();
38
+ for (const verdict of verdicts) {
39
+ if (!verdict?.lane)
40
+ continue;
41
+ byLane.set(verdict.lane, verdict);
42
+ }
43
+ const approvedLanes = [];
44
+ const changesRequestedLanes = [];
45
+ const blockedLanes = [];
46
+ const missingLanes = [];
47
+ for (const lane of requiredLanes) {
48
+ const verdict = byLane.get(lane);
49
+ if (!verdict) {
50
+ missingLanes.push(lane);
51
+ continue;
52
+ }
53
+ if (verdict.status === 'approved')
54
+ approvedLanes.push(lane);
55
+ else if (verdict.status === 'changes_requested') {
56
+ changesRequestedLanes.push(lane);
57
+ }
58
+ else {
59
+ blockedLanes.push(lane);
60
+ }
61
+ }
62
+ let overallStatus = 'approved';
63
+ if (missingLanes.length > 0 || blockedLanes.length > 0) {
64
+ overallStatus = 'blocked';
65
+ }
66
+ else if (changesRequestedLanes.length > 0) {
67
+ overallStatus = 'changes_requested';
68
+ }
69
+ const commitAllowed = overallStatus === 'approved';
70
+ const nextStage = commitAllowed ? 'finalization' : 'implementation';
71
+ return {
72
+ version: 1,
73
+ overall_status: overallStatus,
74
+ commit_allowed: commitAllowed,
75
+ next_stage: nextStage,
76
+ required_lanes: [...requiredLanes],
77
+ approved_lanes: approvedLanes,
78
+ changes_requested_lanes: changesRequestedLanes,
79
+ blocked_lanes: blockedLanes,
80
+ missing_lanes: missingLanes,
81
+ verdicts: requiredLanes
82
+ .map((lane) => byLane.get(lane))
83
+ .filter((verdict) => Boolean(verdict)),
84
+ };
85
+ }
86
+ export function renderReviewSummaryMarkdown(result) {
87
+ const lines = [
88
+ '# Review Summary',
89
+ '',
90
+ `- Overall Status: ${result.overall_status}`,
91
+ `- Commit Allowed: ${result.commit_allowed ? 'yes' : 'no'}`,
92
+ `- Next Stage: ${result.next_stage}`,
93
+ '',
94
+ '## Lane Status',
95
+ '',
96
+ `- Approved: ${result.approved_lanes.join(', ') || '(none)'}`,
97
+ `- Changes Requested: ${result.changes_requested_lanes.join(', ') || '(none)'}`,
98
+ `- Blocked: ${result.blocked_lanes.join(', ') || '(none)'}`,
99
+ `- Missing Lanes: ${result.missing_lanes.join(', ') || '(none)'}`,
100
+ '',
101
+ '## Lane Summaries',
102
+ '',
103
+ ];
104
+ if (result.verdicts.length === 0) {
105
+ lines.push('- No lane verdicts were recorded.');
106
+ }
107
+ else {
108
+ for (const verdict of result.verdicts) {
109
+ lines.push(`### ${verdict.lane}`);
110
+ lines.push(`- Status: ${verdict.status}`);
111
+ lines.push(`- Summary: ${verdict.summary}`);
112
+ if (verdict.issues && verdict.issues.length > 0) {
113
+ lines.push('- Issues:');
114
+ for (const issue of verdict.issues) {
115
+ lines.push(` - [${issue.severity}] ${issue.message}`);
116
+ }
117
+ }
118
+ lines.push('');
119
+ }
120
+ }
121
+ return `${lines.join('\n').trimEnd()}\n`;
122
+ }
123
+ export async function writeAggregateReviewArtifacts(topicDir, result) {
124
+ const reviewDir = join(topicDir, 'review');
125
+ await Promise.all([
126
+ writeFile(join(reviewDir, 'aggregate.json'), `${JSON.stringify(result, null, 2)}\n`, 'utf8'),
127
+ writeFile(join(reviewDir, 'summary.md'), renderReviewSummaryMarkdown(result), 'utf8'),
128
+ ]);
129
+ }