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,119 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { buildWorktreePlan } from './worktree.js';
4
+ import { defaultTopicArtifacts } from './topic-artifacts.js';
5
+ function timestampForSlug(date = new Date()) {
6
+ return date.toISOString().slice(0, 10);
7
+ }
8
+ export function slugifyTopic(input) {
9
+ const normalized = String(input || '')
10
+ .trim()
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, '-')
13
+ .replace(/-+/g, '-')
14
+ .replace(/^-|-$/g, '');
15
+ return normalized || 'topic';
16
+ }
17
+ export function summarizeRequest(request) {
18
+ const compact = String(request || '').trim().replace(/\s+/g, ' ');
19
+ if (compact.length <= 160)
20
+ return compact;
21
+ return `${compact.slice(0, 157)}...`;
22
+ }
23
+ export function buildTopicSlug(request, now = new Date()) {
24
+ return `${timestampForSlug(now)}-${slugifyTopic(request).slice(0, 48)}`;
25
+ }
26
+ export async function bootstrapTopic({ rootDir, request, summary, now = new Date(), }) {
27
+ if (!rootDir)
28
+ throw new Error('rootDir is required');
29
+ if (!request || String(request).trim() === '') {
30
+ throw new Error('request is required');
31
+ }
32
+ const topicSlug = buildTopicSlug(request, now);
33
+ const topicDir = join(rootDir, '.ax', 'topics', topicSlug);
34
+ const artifacts = defaultTopicArtifacts();
35
+ await mkdir(topicDir, { recursive: true });
36
+ const requestContent = `${String(request).trim()}\n`;
37
+ const requestSummary = (summary && String(summary).trim()) || summarizeRequest(request);
38
+ const requestSummaryContent = `${requestSummary}\n`;
39
+ const resolvedContextContent = `${JSON.stringify({
40
+ version: 1,
41
+ request: String(request).trim(),
42
+ matches: [],
43
+ unresolved_paths: [],
44
+ }, null, 2)}\n`;
45
+ const brainstormContent = '# Brainstorm\n\n> Shift AX placeholder: planning interview notes must replace this section before review.\n';
46
+ const specContent = '# Topic Spec\n\n## Goal\n\n> Shift AX placeholder: define the reviewed goal before implementation.\n';
47
+ const planReviewContent = `${JSON.stringify({ version: 1, status: 'pending' }, null, 2)}\n`;
48
+ const policyContextSyncContent = `${JSON.stringify({
49
+ version: 1,
50
+ status: 'not_needed',
51
+ required_updates: [],
52
+ created_at: now.toISOString(),
53
+ updated_at: now.toISOString(),
54
+ }, null, 2)}\n`;
55
+ const implementationPlanContent = '# Implementation Plan\n\n> Shift AX placeholder: write the reviewed implementation steps before execution.\n';
56
+ const executionHandoffContent = `${JSON.stringify({
57
+ version: 1,
58
+ status: 'pending',
59
+ tasks: [],
60
+ }, null, 2)}\n`;
61
+ const executionStateContent = `${JSON.stringify({
62
+ version: 1,
63
+ overall_status: 'pending',
64
+ tasks: [],
65
+ }, null, 2)}\n`;
66
+ const workflowStateContent = `${JSON.stringify({
67
+ version: 1,
68
+ topic_slug: topicSlug,
69
+ phase: 'bootstrapped',
70
+ created_at: now.toISOString(),
71
+ updated_at: now.toISOString(),
72
+ plan_review_status: 'pending',
73
+ escalation: {
74
+ status: 'clear',
75
+ triggers: [],
76
+ },
77
+ }, null, 2)}\n`;
78
+ const commitMessageContent = '# Commit Message\n\n> Shift AX placeholder: generate or write the Lore commit message before finalization.\n';
79
+ const commitStateContent = `${JSON.stringify({ version: 1, status: 'not_committed' }, null, 2)}\n`;
80
+ const verificationContent = '# Final Verification\n\n> Shift AX placeholder: record verification evidence before finalization.\n';
81
+ const worktreePlan = buildWorktreePlan({
82
+ rootDir,
83
+ topicSlug,
84
+ request,
85
+ });
86
+ const metadata = {
87
+ version: 1,
88
+ topic_slug: topicSlug,
89
+ created_at: now.toISOString(),
90
+ status: 'bootstrapped',
91
+ artifacts,
92
+ };
93
+ await Promise.all([
94
+ mkdir(join(topicDir, artifacts.review_dir), { recursive: true }),
95
+ mkdir(join(topicDir, artifacts.final_dir), { recursive: true }),
96
+ writeFile(join(topicDir, artifacts.request), requestContent, 'utf8'),
97
+ writeFile(join(topicDir, artifacts.request_summary), requestSummaryContent, 'utf8'),
98
+ writeFile(join(topicDir, artifacts.resolved_context), resolvedContextContent, 'utf8'),
99
+ writeFile(join(topicDir, artifacts.brainstorm), brainstormContent, 'utf8'),
100
+ writeFile(join(topicDir, artifacts.spec), specContent, 'utf8'),
101
+ writeFile(join(topicDir, artifacts.plan_review), planReviewContent, 'utf8'),
102
+ writeFile(join(topicDir, artifacts.policy_context_sync), policyContextSyncContent, 'utf8'),
103
+ writeFile(join(topicDir, artifacts.implementation_plan), implementationPlanContent, 'utf8'),
104
+ writeFile(join(topicDir, artifacts.execution_handoff), executionHandoffContent, 'utf8'),
105
+ writeFile(join(topicDir, artifacts.execution_state), executionStateContent, 'utf8'),
106
+ writeFile(join(topicDir, artifacts.workflow_state), workflowStateContent, 'utf8'),
107
+ writeFile(join(topicDir, artifacts.commit_message), commitMessageContent, 'utf8'),
108
+ writeFile(join(topicDir, artifacts.commit_state), commitStateContent, 'utf8'),
109
+ writeFile(join(topicDir, artifacts.verification), verificationContent, 'utf8'),
110
+ writeFile(join(topicDir, artifacts.worktree_plan), `${JSON.stringify(worktreePlan, null, 2)}\n`, 'utf8'),
111
+ writeFile(join(topicDir, artifacts.worktree_state), `${JSON.stringify({ version: 1, status: 'not_created' }, null, 2)}\n`, 'utf8'),
112
+ writeFile(join(topicDir, 'topic.json'), `${JSON.stringify(metadata, null, 2)}\n`, 'utf8'),
113
+ ]);
114
+ return {
115
+ topicSlug,
116
+ topicDir,
117
+ metadata,
118
+ };
119
+ }
@@ -0,0 +1,36 @@
1
+ import { join, resolve, sep } from 'node:path';
2
+ export function defaultTopicArtifacts() {
3
+ return {
4
+ request: 'request.md',
5
+ request_summary: 'request-summary.md',
6
+ resolved_context: 'resolved-context.json',
7
+ brainstorm: 'brainstorm.md',
8
+ spec: 'spec.md',
9
+ plan_review: 'plan-review.json',
10
+ policy_context_sync: 'policy-context-sync.json',
11
+ implementation_plan: 'implementation-plan.md',
12
+ execution_handoff: 'execution-handoff.json',
13
+ execution_state: 'execution-state.json',
14
+ workflow_state: 'workflow-state.json',
15
+ review_dir: 'review',
16
+ final_dir: 'final',
17
+ commit_message: 'final/commit-message.md',
18
+ commit_state: 'final/commit-state.json',
19
+ verification: 'final/verification.md',
20
+ handoff: 'handoff.md',
21
+ worktree_plan: 'worktree-plan.json',
22
+ worktree_state: 'worktree-state.json',
23
+ };
24
+ }
25
+ export function topicArtifactPath(topicDir, key) {
26
+ return join(topicDir, defaultTopicArtifacts()[key]);
27
+ }
28
+ export function getRootDirFromTopicDir(topicDir) {
29
+ const resolved = resolve(topicDir);
30
+ const marker = `${sep}.ax${sep}topics${sep}`;
31
+ const index = resolved.lastIndexOf(marker);
32
+ if (index === -1) {
33
+ throw new Error(`topicDir is not inside .ax/topics: ${topicDir}`);
34
+ }
35
+ return resolved.slice(0, index);
36
+ }
@@ -0,0 +1,141 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { readFile, writeFile } from 'node:fs/promises';
4
+ import { resolve, sep, join } from 'node:path';
5
+ import { buildWorktreePlan } from './worktree.js';
6
+ function runGit(cwd, args) {
7
+ return execFileSync('git', args, {
8
+ cwd,
9
+ encoding: 'utf8',
10
+ stdio: ['ignore', 'pipe', 'pipe'],
11
+ }).trim();
12
+ }
13
+ function getRootDirFromTopicDir(topicDir) {
14
+ const resolved = resolve(topicDir);
15
+ const marker = `${sep}.ax${sep}topics${sep}`;
16
+ const index = resolved.lastIndexOf(marker);
17
+ if (index === -1) {
18
+ throw new Error(`topicDir is not inside .ax/topics: ${topicDir}`);
19
+ }
20
+ return resolved.slice(0, index);
21
+ }
22
+ async function readTopicMetadata(topicDir) {
23
+ const raw = await readFile(join(topicDir, 'topic.json'), 'utf8');
24
+ return JSON.parse(raw);
25
+ }
26
+ async function readTopicRequest(topicDir) {
27
+ return (await readFile(join(topicDir, 'request.md'), 'utf8')).trim();
28
+ }
29
+ async function writeWorktreeState(topicDir, payload) {
30
+ await writeFile(join(topicDir, 'worktree-state.json'), `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
31
+ }
32
+ function isGitRepository(rootDir) {
33
+ try {
34
+ runGit(rootDir, ['rev-parse', '--show-toplevel']);
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ async function readWorktreePlan(topicDir) {
42
+ const raw = await readFile(join(topicDir, 'worktree-plan.json'), 'utf8');
43
+ return JSON.parse(raw);
44
+ }
45
+ export async function planTopicWorktree({ topicDir, }) {
46
+ const rootDir = getRootDirFromTopicDir(topicDir);
47
+ const metadata = await readTopicMetadata(topicDir);
48
+ const request = await readTopicRequest(topicDir);
49
+ const plan = buildWorktreePlan({
50
+ rootDir,
51
+ topicSlug: metadata.topic_slug,
52
+ request,
53
+ });
54
+ await writeFile(join(topicDir, 'worktree-plan.json'), `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
55
+ return plan;
56
+ }
57
+ export async function resolveTopicWorktreeTarget({ topicDir, baseBranch = 'main', }) {
58
+ const rootDir = getRootDirFromTopicDir(topicDir);
59
+ const metadata = await readTopicMetadata(topicDir);
60
+ const plan = existsSync(join(topicDir, 'worktree-plan.json'))
61
+ ? await readWorktreePlan(topicDir)
62
+ : await planTopicWorktree({ topicDir });
63
+ return {
64
+ topicDir,
65
+ rootDir,
66
+ topicSlug: metadata.topic_slug,
67
+ branchName: plan.preferred_branch_name,
68
+ worktreePath: plan.preferred_worktree_path,
69
+ baseBranch,
70
+ };
71
+ }
72
+ export async function recordTopicWorktreeCreate(target, state) {
73
+ const result = {
74
+ version: 1,
75
+ topic_slug: target.topicSlug,
76
+ branch_name: target.branchName,
77
+ worktree_path: target.worktreePath,
78
+ base_branch: target.baseBranch,
79
+ created: state.created,
80
+ reused: state.reused,
81
+ };
82
+ await writeWorktreeState(target.topicDir, {
83
+ ...result,
84
+ status: state.reused ? 'reused' : state.created ? 'created' : 'not_created',
85
+ });
86
+ return result;
87
+ }
88
+ export async function recordTopicWorktreeRemove(target, removed) {
89
+ const result = {
90
+ version: 1,
91
+ topic_slug: target.topicSlug,
92
+ branch_name: target.branchName,
93
+ worktree_path: target.worktreePath,
94
+ removed,
95
+ };
96
+ await writeWorktreeState(target.topicDir, {
97
+ ...result,
98
+ status: removed ? 'removed' : 'remove_failed',
99
+ });
100
+ return result;
101
+ }
102
+ export async function createTopicWorktree({ topicDir, baseBranch = 'main', }) {
103
+ const target = await resolveTopicWorktreeTarget({
104
+ topicDir,
105
+ baseBranch,
106
+ });
107
+ if (!isGitRepository(target.rootDir)) {
108
+ throw new Error(`rootDir is not a git repository: ${target.rootDir}`);
109
+ }
110
+ if (existsSync(target.worktreePath)) {
111
+ return recordTopicWorktreeCreate(target, {
112
+ created: false,
113
+ reused: true,
114
+ });
115
+ }
116
+ runGit(target.rootDir, [
117
+ 'worktree',
118
+ 'add',
119
+ '-b',
120
+ target.branchName,
121
+ target.worktreePath,
122
+ target.baseBranch,
123
+ ]);
124
+ return recordTopicWorktreeCreate(target, {
125
+ created: true,
126
+ reused: false,
127
+ });
128
+ }
129
+ export async function removeTopicWorktree({ topicDir, }) {
130
+ const target = await resolveTopicWorktreeTarget({ topicDir });
131
+ if (existsSync(target.worktreePath)) {
132
+ runGit(target.rootDir, ['worktree', 'remove', '--force', target.worktreePath]);
133
+ }
134
+ try {
135
+ runGit(target.rootDir, ['branch', '-D', target.branchName]);
136
+ }
137
+ catch {
138
+ // Branch may already be absent.
139
+ }
140
+ return recordTopicWorktreeRemove(target, !existsSync(target.worktreePath));
141
+ }
@@ -0,0 +1,8 @@
1
+ export function buildWorktreePlan({ rootDir, topicSlug, }) {
2
+ return {
3
+ version: 1,
4
+ topic_slug: topicSlug,
5
+ preferred_branch_name: `ax/${topicSlug}`,
6
+ preferred_worktree_path: `${rootDir}/.ax/worktrees/${topicSlug}`,
7
+ };
8
+ }
@@ -0,0 +1,66 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+ import { buildProductShellAssets, SHIFT_AX_PRODUCT_SHELL_COMMANDS } from '../product-shell-commands.js';
5
+ import { getGlobalContextHome } from '../../core/settings/global-context-home.js';
6
+ const HERE = dirname(fileURLToPath(import.meta.url));
7
+ const TEMPLATE_FILES = [
8
+ 'platform/claude-code/scaffold/CLAUDE.template.md',
9
+ 'platform/claude-code/scaffold/hooks/shift-ax-session-start.template.md',
10
+ ...SHIFT_AX_PRODUCT_SHELL_COMMANDS.map((name) => `platform/claude-code/scaffold/commands/${name}.template.md`),
11
+ ];
12
+ function templatePath(relativePath) {
13
+ return join(HERE, 'scaffold', relativePath.split('/').slice(3).join('/'));
14
+ }
15
+ function localePrelude(locale) {
16
+ return locale === 'ko'
17
+ ? '선호 사용자 언어: 한국어. 사용자가 명시적으로 바꾸라고 하지 않으면 한국어로 응답하세요.'
18
+ : 'Preferred user language: English. Respond in English unless the user explicitly asks to switch.';
19
+ }
20
+ function injectLocalePrelude(raw, locale) {
21
+ const prelude = localePrelude(locale);
22
+ if (raw.startsWith('---\n')) {
23
+ const closing = raw.indexOf('\n---\n', 4);
24
+ if (closing !== -1) {
25
+ const frontmatter = raw.slice(0, closing + 5);
26
+ const rest = raw.slice(closing + 5);
27
+ return `${frontmatter}\n${prelude}\n\n${rest}`;
28
+ }
29
+ }
30
+ return `${prelude}\n\n${raw}`;
31
+ }
32
+ function renderTemplate(relativePath, rootDir, locale) {
33
+ const raw = readFileSync(templatePath(relativePath), 'utf8');
34
+ return injectLocalePrelude(raw
35
+ .replaceAll('{{BASE_CONTEXT_INDEX}}', `${rootDir}/docs/base-context/index.md`)
36
+ .replaceAll('{{GLOBAL_CONTEXT_INDEX}}', getGlobalContextHome().indexPath), locale);
37
+ }
38
+ export function claudeCodeScaffoldTemplateFiles() {
39
+ return [...TEMPLATE_FILES];
40
+ }
41
+ export function renderClaudeCodeSessionStartContext(rootDir, locale = 'en') {
42
+ return renderTemplate('platform/claude-code/scaffold/hooks/shift-ax-session-start.template.md', rootDir, locale);
43
+ }
44
+ export function renderClaudeCodeBootstrap(rootDir, locale = 'en') {
45
+ return renderTemplate('platform/claude-code/scaffold/CLAUDE.template.md', rootDir, locale);
46
+ }
47
+ export function getClaudeCodeBootstrapAssets(rootDir, locale = 'en') {
48
+ const commandAssets = buildProductShellAssets({
49
+ platform: 'claude-code',
50
+ commandBasePath: '.claude/commands',
51
+ renderTemplate: (name) => renderTemplate(`platform/claude-code/scaffold/commands/${name}.template.md`, rootDir, locale),
52
+ });
53
+ return [
54
+ {
55
+ path: 'CLAUDE.md',
56
+ description: 'Top-level CLAUDE bootstrap for Claude Code builds.',
57
+ content: renderClaudeCodeBootstrap(rootDir, locale),
58
+ },
59
+ {
60
+ path: '.claude/hooks/shift-ax-session-start.md',
61
+ description: 'SessionStart hook payload guidance for Claude Code builds.',
62
+ content: renderClaudeCodeSessionStartContext(rootDir, locale),
63
+ },
64
+ ...commandAssets,
65
+ ];
66
+ }
@@ -0,0 +1,157 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import { materializeExecutionPrompts } from '../../core/planning/execution-launch.js';
5
+ import { buildClaudeWorkerSessionName } from './upstream/tmux/imported/session-name.js';
6
+ function shellQuote(value) {
7
+ return `'${value.replace(/'/g, `'\\''`)}'`;
8
+ }
9
+ function buildClaudeExecShellCommand(plan) {
10
+ return `cd ${shellQuote(plan.working_directory)} && claude -p --output-format text --permission-mode bypassPermissions --no-session-persistence \"$(cat ${shellQuote(plan.prompt_path)})\" > ${shellQuote(plan.output_path)}`;
11
+ }
12
+ function formatProcessError(error) {
13
+ const failure = error;
14
+ const stdout = typeof failure.stdout === 'string'
15
+ ? failure.stdout
16
+ : failure.stdout instanceof Buffer
17
+ ? failure.stdout.toString('utf8')
18
+ : '';
19
+ const stderr = typeof failure.stderr === 'string'
20
+ ? failure.stderr
21
+ : failure.stderr instanceof Buffer
22
+ ? failure.stderr.toString('utf8')
23
+ : '';
24
+ return [failure.message ?? 'process failed', stdout.trim(), stderr.trim()]
25
+ .filter(Boolean)
26
+ .join('\n');
27
+ }
28
+ function claudeExecutionTimeoutMs() {
29
+ const parsed = Number(process.env.SHIFT_AX_CLAUDE_EXEC_TIMEOUT_MS || '45000');
30
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 45_000;
31
+ }
32
+ function clearExistingTmuxSession(sessionName) {
33
+ try {
34
+ execFileSync('tmux', ['has-session', '-t', sessionName], {
35
+ stdio: 'pipe',
36
+ });
37
+ execFileSync('tmux', ['kill-session', '-t', sessionName], {
38
+ stdio: 'pipe',
39
+ });
40
+ }
41
+ catch {
42
+ // no existing session to clear
43
+ }
44
+ }
45
+ export function getClaudeCodeExecutionRuntime() {
46
+ return {
47
+ support: 'available',
48
+ entrypoint_style: 'cli',
49
+ execution_handoff_artifact: 'execution-handoff.json',
50
+ operations: {
51
+ launch: {
52
+ command: ['shift-ax', 'launch-execution'],
53
+ topic_flag: '--topic',
54
+ additional_flags: ['--platform', '--task-id', '--dry-run'],
55
+ },
56
+ },
57
+ hosts: {
58
+ subagent_cli: 'claude',
59
+ tmux_cli: 'tmux',
60
+ },
61
+ };
62
+ }
63
+ export async function planClaudeCodeExecutionLaunch({ topicDir, taskId, }) {
64
+ const artifacts = await materializeExecutionPrompts(topicDir, taskId);
65
+ const tasks = artifacts.map((artifact) => {
66
+ const basePlan = {
67
+ task_id: artifact.task.id,
68
+ source_text: artifact.task.source_text,
69
+ execution_mode: artifact.task.execution_mode,
70
+ working_directory: artifact.worktreePath,
71
+ prompt_path: artifact.promptPath,
72
+ output_path: artifact.outputPath,
73
+ };
74
+ if (artifact.task.execution_mode === 'tmux') {
75
+ const sessionName = buildClaudeWorkerSessionName(topicDir.split('/').pop() || 'topic', artifact.task.id);
76
+ const shellCommand = buildClaudeExecShellCommand({
77
+ ...basePlan,
78
+ command: [],
79
+ shell_command: '',
80
+ });
81
+ return {
82
+ ...basePlan,
83
+ command: [
84
+ 'tmux',
85
+ 'new-session',
86
+ '-d',
87
+ '-s',
88
+ sessionName,
89
+ '-c',
90
+ artifact.worktreePath,
91
+ shellCommand,
92
+ ],
93
+ shell_command: shellCommand,
94
+ session_name: sessionName,
95
+ };
96
+ }
97
+ const shellCommand = buildClaudeExecShellCommand({
98
+ ...basePlan,
99
+ command: [],
100
+ shell_command: '',
101
+ });
102
+ return {
103
+ ...basePlan,
104
+ command: [
105
+ '/bin/sh',
106
+ '-lc',
107
+ shellCommand,
108
+ ],
109
+ shell_command: shellCommand,
110
+ };
111
+ });
112
+ return {
113
+ platform: 'claude-code',
114
+ launched: false,
115
+ topic_dir: topicDir,
116
+ tasks,
117
+ };
118
+ }
119
+ export async function launchClaudeCodeExecution({ topicDir, taskId, }) {
120
+ const plan = await planClaudeCodeExecutionLaunch({ topicDir, taskId });
121
+ for (const task of plan.tasks) {
122
+ if (task.execution_mode === 'tmux') {
123
+ if (task.session_name) {
124
+ clearExistingTmuxSession(task.session_name);
125
+ }
126
+ execFileSync(task.command[0], task.command.slice(1), {
127
+ stdio: 'pipe',
128
+ });
129
+ continue;
130
+ }
131
+ try {
132
+ const output = execFileSync('claude', [
133
+ '-p',
134
+ '--output-format',
135
+ 'text',
136
+ '--permission-mode',
137
+ 'bypassPermissions',
138
+ '--no-session-persistence',
139
+ readFileSync(task.prompt_path, 'utf8'),
140
+ ], {
141
+ cwd: task.working_directory,
142
+ encoding: 'utf8',
143
+ stdio: ['ignore', 'pipe', 'pipe'],
144
+ timeout: claudeExecutionTimeoutMs(),
145
+ });
146
+ mkdirSync(dirname(task.output_path), { recursive: true });
147
+ writeFileSync(task.output_path, output, 'utf8');
148
+ }
149
+ catch (error) {
150
+ throw new Error(`Claude Code execution failed for ${task.task_id}: ${formatProcessError(error)}`);
151
+ }
152
+ }
153
+ return {
154
+ ...plan,
155
+ launched: true,
156
+ };
157
+ }
@@ -0,0 +1,40 @@
1
+ # Shift AX Claude Code SessionStart Bootstrap
2
+
3
+ This build is expected to use SessionStart hook-driven context injection.
4
+
5
+ ## Hook Intent
6
+
7
+ - SessionStart should inject Shift AX bootstrap context.
8
+ - Before planning or implementation, resolve context from {{GLOBAL_CONTEXT_INDEX}}.
9
+ - If the global index is missing, recommend `/onboard` before `/request`. Do not pretend the missing context does not matter.
10
+ - Use `shift-ax doctor` when setup, launcher availability, or topic state looks unhealthy.
11
+ - Use `shift-ax resolve-context` before answering when relevant documents may exist.
12
+ - Use `shift-ax run-request` to create the request-scoped topic/worktree, run the planning interview, write brainstorming/spec/plan artifacts plus `execution-handoff.json`, and pause at the human planning-review gate.
13
+ - Use `shift-ax approve-plan` after the human reviewer signs off.
14
+ - If the reviewed plan requires shared policy or base-context doc changes, record them first with `shift-ax sync-policy-context --topic <dir> --summary "<what changed>" [--path <doc>]... [--entry "Label -> path"]...`.
15
+ - Then resume with `shift-ax run-request --topic <dir> --resume` for automatic review and commit. Use `--no-auto-commit` only when a human explicitly wants the final commit step held back.
16
+ - If downstream review or CI fails after the topic looked ready, use `shift-ax react-feedback --topic <dir> --kind <review-changes-requested|ci-failed> --summary "<text>"` to reopen implementation with a file-backed reaction trail.
17
+ - Use `shift-ax launch-execution --platform claude-code --topic <dir> [--task-id <id>] [--dry-run]` when you need the concrete Claude or tmux launch commands from `execution-handoff.json`.
18
+ - Use `shift-ax topic-status --topic <dir>` when you need a compact summary of phase, review gate, execution state, and last failure.
19
+ - Use `shift-ax topics-status [--root DIR] [--limit N]` when you need a compact multi-topic view without leaving the CLI.
20
+ - If a reviewed request hits a mandatory escalation trigger, persist that stop with `shift-ax run-request --topic <dir> --resume --escalation <kind>:<summary>` and resume only after human review with `--clear-escalations`.
21
+ - Use `shift-ax worktree-plan` to inspect the preferred branch/worktree path for the topic.
22
+ - Use `shift-ax worktree-create` before implementation begins and `shift-ax worktree-remove` when the topic worktree should be torn down.
23
+ - Worktree runtime provenance is tracked in `platform/claude-code/upstream/worktree/provenance.md`.
24
+ - Active imported worktree helpers currently include `getWorktreeRoot`, `createClaudeManagedWorktree`, and `removeClaudeManagedWorktree`.
25
+ - Use `shift-ax review --run` before finalization and `shift-ax finalize-commit` only after the review gate allows commit.
26
+ - Natural language is the primary user surface. Internal AX commands exist to support the flow, not replace the conversation.
27
+ - In Shift AX Claude Code sessions, use `/onboard`, `/request <text>`, `/export-context`, `/doctor`, `/status`, `/topics`, `/resume <topic>`, `/review <topic>`, `/help` as the primary visible commands. `$...` aliases may mirror them when useful.
28
+ - Native product-shell command files are installed under `.claude/commands/` for: `onboard`, `request`, `export-context`, `doctor`, `status`, `topics`, `resume`, and `review`.
29
+
30
+ ## Product-shell aliases
31
+
32
+ Treat these as explicit Shift AX commands inside the session:
33
+
34
+ - `$onboard` -> onboarding flow
35
+ - `$doctor` -> repo/topic health
36
+ - `$request <text>` -> new request-to-commit flow
37
+ - `$status` -> current topic or repo status
38
+ - `$topics` -> recent topics list
39
+ - `$resume <topic>` -> resume a topic
40
+ - `$review <topic>` -> run structured review
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Run Shift AX health checks for the current repository.
3
+ argument-hint: "[--topic <dir>]"
4
+ ---
5
+
6
+ Run Shift AX doctor for the current repository.
7
+
8
+ - If arguments specify a topic, use `shift-ax doctor --topic <dir>`.
9
+ - Otherwise use `shift-ax doctor`.
10
+
11
+ Summarize the repo health, base-context health, and any launcher warnings.
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Explain how to share the user's Shift AX global knowledge base.
3
+ argument-hint: ""
4
+ ---
5
+
6
+ Do not build an archive.
7
+
8
+ Explain that the user should share:
9
+
10
+ - `~/.shift-ax/`
11
+
12
+ And tell the recipient to place the shared files at the same location on their machine:
13
+
14
+ - `~/.shift-ax/`
15
+
16
+ Mention that the most important file is:
17
+
18
+ - `~/.shift-ax/index.md`
19
+
20
+ If useful, remind them that `/onboard` can refresh the profile before sharing.
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Capture or refresh the user's global Shift AX knowledge base.
3
+ argument-hint: "[optional note]"
4
+ ---
5
+
6
+ This command is the most important setup step.
7
+
8
+ Start by saying:
9
+
10
+ > This step matters most. Please invest 10 minutes so Shift AX can understand how you work.
11
+
12
+ Then run a conversational interview that captures:
13
+
14
+ 1. primary role summary
15
+ 2. work types
16
+ 3. related repositories for each work type
17
+ 4. per-repository working methods
18
+ 5. company/domain language
19
+
20
+ For each work type and repository:
21
+
22
+ - ask which directories matter
23
+ - inspect the repository when a path is available
24
+ - infer likely workflow details from real files
25
+ - present your inferred workflow back to the user
26
+ - ask them to correct anything wrong or missing
27
+
28
+ When you have enough information:
29
+
30
+ 1. write `.ax/onboarding-input.json`
31
+ 2. if `{{GLOBAL_CONTEXT_INDEX}}` or other global knowledge files already exist, ask whether to overwrite them first
32
+ 3. persist with:
33
+ - `shift-ax onboard-context --root "$PWD" --input .ax/onboarding-input.json`
34
+ - add `--overwrite` only if the user explicitly agreed
35
+
36
+ Keep the top-level knowledge base in `~/.shift-ax/` with:
37
+
38
+ - `index.md` listing only titles and linked pages
39
+ - linked work type pages
40
+ - linked repository/procedure pages
41
+ - linked domain-language pages
42
+
43
+ After completion, remind the user to share `~/.shift-ax/` with teammates who do similar work.