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,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.
@@ -0,0 +1,19 @@
1
+ ---
2
+ description: Start a new Shift AX request-to-commit flow.
3
+ argument-hint: "<request>"
4
+ ---
5
+
6
+ Treat everything after this command as the raw request text.
7
+
8
+ If no request text was provided, ask for it before doing anything else.
9
+
10
+ Then:
11
+
12
+ 1. resolve context from `{{GLOBAL_CONTEXT_INDEX}}` first
13
+ 2. if the global index is missing, stop and tell the user onboarding should come first because accuracy will drop
14
+ 3. ask whether they want to continue anyway
15
+ 4. only if they explicitly agree, bootstrap with `shift-ax run-request --request "$ARGUMENTS" --allow-missing-global-context`
16
+ 5. otherwise bootstrap with `shift-ax run-request --request "$ARGUMENTS"`
17
+ 6. explain the resulting topic path and that the workflow pauses at human plan review
18
+
19
+ Never skip context grounding or the planning/review gates.
@@ -0,0 +1,12 @@
1
+ ---
2
+ description: Resume an approved Shift AX topic.
3
+ argument-hint: "<topic-dir> [--verify-command <cmd> ...]"
4
+ ---
5
+
6
+ Treat the first argument as the topic directory.
7
+
8
+ Resume with:
9
+
10
+ `shift-ax run-request --topic $ARGUMENTS --resume`
11
+
12
+ Explain whether the topic is blocked by escalation or policy sync if resume cannot continue.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Run structured Shift AX review for a topic.
3
+ argument-hint: "<topic-dir>"
4
+ ---
5
+
6
+ Run:
7
+
8
+ `shift-ax review --topic $ARGUMENTS --run`
9
+
10
+ Then summarize the review lanes, aggregate verdict, and remaining blockers.
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Show Shift AX status for one topic or the repo.
3
+ argument-hint: "[<topic-dir>]"
4
+ ---
5
+
6
+ If a topic directory is provided, run:
7
+
8
+ `shift-ax topic-status --topic $ARGUMENTS`
9
+
10
+ Otherwise run:
11
+
12
+ `shift-ax topics-status`
13
+
14
+ Summarize the current phase, review gate, execution status, and blockers.
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: List recent Shift AX topics for the current repository.
3
+ argument-hint: "[--limit N]"
4
+ ---
5
+
6
+ List recent topics with:
7
+
8
+ `shift-ax topics-status $ARGUMENTS`
9
+
10
+ If the user supplied a limit, include it.
@@ -0,0 +1,29 @@
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
+ - Treat `$request <text>` as the explicit alias for starting a new request-to-commit flow inside the session.
@@ -0,0 +1,35 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { readImportedUpstreamSlice } from '../upstream-imports.js';
4
+ import { buildClaudeWorkerSessionName } from './upstream/tmux/imported/session-name.js';
5
+ import { createClaudeDetachedSession, killClaudeDetachedSession, } from './upstream/tmux/imported/detached-session.js';
6
+ const HERE = dirname(fileURLToPath(import.meta.url));
7
+ export function getClaudeCodeTmuxRuntime(rootDir) {
8
+ return {
9
+ support: 'imported-helpers',
10
+ multiplexer: 'tmux',
11
+ workspace_mode: 'detached-sessions',
12
+ naming: {
13
+ session_prefix: 'omc-team',
14
+ imported_helpers: ['sanitizeClaudeSessionNamePart', 'buildClaudeWorkerSessionName'],
15
+ },
16
+ upstream_boundary: {
17
+ import_root: join(rootDir, 'platform', 'claude-code', 'upstream', 'tmux'),
18
+ provenance_doc: join(rootDir, 'platform', 'claude-code', 'upstream', 'tmux', 'provenance.md'),
19
+ planned_upstream_modules: ['oh-my-claudecode/src/team/tmux-session.ts'],
20
+ active_imports: [
21
+ readImportedUpstreamSlice(join(HERE, 'upstream', 'tmux', 'imported', 'provenance.json')),
22
+ readImportedUpstreamSlice(join(HERE, 'upstream', 'tmux', 'imported', 'detached-session.provenance.json')),
23
+ ],
24
+ },
25
+ };
26
+ }
27
+ export function buildClaudeWorkerSessionIdentity(teamName, workerName) {
28
+ return buildClaudeWorkerSessionName(teamName, workerName);
29
+ }
30
+ export function createClaudeDetachedWorkerSession(teamName, workerName, workingDirectory) {
31
+ return createClaudeDetachedSession(teamName, workerName, workingDirectory);
32
+ }
33
+ export function killClaudeDetachedWorkerSession(teamName, workerName) {
34
+ killClaudeDetachedSession(teamName, workerName);
35
+ }
@@ -0,0 +1,40 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { buildClaudeWorkerSessionName } from './session-name.js';
3
+ /**
4
+ * Imported from oh-my-claudecode.
5
+ * Source: oh-my-claudecode/src/team/tmux-session.ts
6
+ * Commit: 2487d3878f8d25e60802940b020d5ee8774d135e
7
+ */
8
+ export function createClaudeDetachedSession(teamName, workerName, workingDirectory) {
9
+ const name = buildClaudeWorkerSessionName(teamName, workerName);
10
+ try {
11
+ execFileSync('tmux', ['kill-session', '-t', name], {
12
+ stdio: 'pipe',
13
+ timeout: 5000,
14
+ });
15
+ }
16
+ catch {
17
+ // ignore stale session misses
18
+ }
19
+ const args = ['new-session', '-d', '-s', name, '-x', '200', '-y', '50'];
20
+ if (workingDirectory) {
21
+ args.push('-c', workingDirectory);
22
+ }
23
+ execFileSync('tmux', args, {
24
+ stdio: 'pipe',
25
+ timeout: 5000,
26
+ });
27
+ return name;
28
+ }
29
+ export function killClaudeDetachedSession(teamName, workerName) {
30
+ const name = buildClaudeWorkerSessionName(teamName, workerName);
31
+ try {
32
+ execFileSync('tmux', ['kill-session', '-t', name], {
33
+ stdio: 'pipe',
34
+ timeout: 5000,
35
+ });
36
+ }
37
+ catch {
38
+ // ignore absent session
39
+ }
40
+ }
@@ -0,0 +1,19 @@
1
+ const TMUX_SESSION_PREFIX = 'omc-team';
2
+ /**
3
+ * Imported from oh-my-claudecode.
4
+ * Source: oh-my-claudecode/src/team/tmux-session.ts
5
+ * Commit: 2487d3878f8d25e60802940b020d5ee8774d135e
6
+ */
7
+ export function sanitizeClaudeSessionNamePart(name) {
8
+ const sanitized = name.replace(/[^a-zA-Z0-9-]/g, '');
9
+ if (sanitized.length === 0) {
10
+ throw new Error(`Invalid name: "${name}" contains no valid characters (alphanumeric or hyphen)`);
11
+ }
12
+ if (sanitized.length < 2) {
13
+ throw new Error(`Invalid name: "${name}" too short after sanitization (minimum 2 characters)`);
14
+ }
15
+ return sanitized.slice(0, 50);
16
+ }
17
+ export function buildClaudeWorkerSessionName(teamName, workerName) {
18
+ return `${TMUX_SESSION_PREFIX}-${sanitizeClaudeSessionNamePart(teamName)}-${sanitizeClaudeSessionNamePart(workerName)}`;
19
+ }
@@ -0,0 +1,39 @@
1
+ import { execSync } from 'node:child_process';
2
+ /**
3
+ * Imported from oh-my-claudecode.
4
+ * Source: oh-my-claudecode/src/lib/worktree-paths.ts
5
+ * Commit: 2487d3878f8d25e60802940b020d5ee8774d135e
6
+ */
7
+ const MAX_WORKTREE_CACHE_SIZE = 8;
8
+ const worktreeCacheMap = new Map();
9
+ export function clearClaudeCodeWorktreeRootCache() {
10
+ worktreeCacheMap.clear();
11
+ }
12
+ export function getClaudeCodeWorktreeRoot(cwd) {
13
+ const effectiveCwd = cwd || process.cwd();
14
+ if (worktreeCacheMap.has(effectiveCwd)) {
15
+ const root = worktreeCacheMap.get(effectiveCwd);
16
+ worktreeCacheMap.delete(effectiveCwd);
17
+ worktreeCacheMap.set(effectiveCwd, root);
18
+ return root || null;
19
+ }
20
+ try {
21
+ const root = execSync('git rev-parse --show-toplevel', {
22
+ cwd: effectiveCwd,
23
+ encoding: 'utf-8',
24
+ stdio: ['pipe', 'pipe', 'pipe'],
25
+ timeout: 5000,
26
+ }).trim();
27
+ if (worktreeCacheMap.size >= MAX_WORKTREE_CACHE_SIZE) {
28
+ const oldest = worktreeCacheMap.keys().next().value;
29
+ if (oldest !== undefined) {
30
+ worktreeCacheMap.delete(oldest);
31
+ }
32
+ }
33
+ worktreeCacheMap.set(effectiveCwd, root);
34
+ return root;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
@@ -0,0 +1,77 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ export function createClaudeManagedWorktree(plan) {
5
+ try {
6
+ execFileSync('git', ['worktree', 'prune'], {
7
+ cwd: plan.repoRoot,
8
+ stdio: 'pipe',
9
+ });
10
+ }
11
+ catch {
12
+ // best effort only
13
+ }
14
+ if (existsSync(plan.worktreePath)) {
15
+ try {
16
+ execFileSync('git', ['worktree', 'remove', '--force', plan.worktreePath], {
17
+ cwd: plan.repoRoot,
18
+ stdio: 'pipe',
19
+ });
20
+ }
21
+ catch {
22
+ // best effort only
23
+ }
24
+ }
25
+ try {
26
+ execFileSync('git', ['branch', '-D', plan.branchName], {
27
+ cwd: plan.repoRoot,
28
+ stdio: 'pipe',
29
+ });
30
+ }
31
+ catch {
32
+ // branch may not exist
33
+ }
34
+ mkdirSync(dirname(plan.worktreePath), { recursive: true });
35
+ const args = ['worktree', 'add', '-b', plan.branchName, plan.worktreePath];
36
+ if (plan.baseBranch) {
37
+ args.push(plan.baseBranch);
38
+ }
39
+ execFileSync('git', args, {
40
+ cwd: plan.repoRoot,
41
+ stdio: 'pipe',
42
+ });
43
+ return {
44
+ path: plan.worktreePath,
45
+ branch: plan.branchName,
46
+ createdAt: new Date().toISOString(),
47
+ };
48
+ }
49
+ export function removeClaudeManagedWorktree(target) {
50
+ try {
51
+ execFileSync('git', ['worktree', 'remove', '--force', target.worktreePath], {
52
+ cwd: target.repoRoot,
53
+ stdio: 'pipe',
54
+ });
55
+ }
56
+ catch {
57
+ // worktree may already be absent
58
+ }
59
+ try {
60
+ execFileSync('git', ['worktree', 'prune'], {
61
+ cwd: target.repoRoot,
62
+ stdio: 'pipe',
63
+ });
64
+ }
65
+ catch {
66
+ // best effort only
67
+ }
68
+ try {
69
+ execFileSync('git', ['branch', '-D', target.branchName], {
70
+ cwd: target.repoRoot,
71
+ stdio: 'pipe',
72
+ });
73
+ }
74
+ catch {
75
+ // branch may already be absent
76
+ }
77
+ }
@@ -0,0 +1,79 @@
1
+ import { dirname, join } from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { planTopicWorktree, recordTopicWorktreeCreate, recordTopicWorktreeRemove, resolveTopicWorktreeTarget, } from '../../core/topics/worktree-runtime.js';
5
+ import { readImportedUpstreamSlice } from '../upstream-imports.js';
6
+ import { createClaudeManagedWorktree, removeClaudeManagedWorktree, } from './upstream/worktree/imported/managed-worktree.js';
7
+ const HERE = dirname(fileURLToPath(import.meta.url));
8
+ function command(name) {
9
+ return ['shift-ax', name];
10
+ }
11
+ export function getClaudeCodeWorktreeRuntime(rootDir) {
12
+ const importRoot = join(rootDir, 'platform', 'claude-code', 'upstream', 'worktree');
13
+ return {
14
+ support: 'available',
15
+ entrypoint_style: 'cli',
16
+ topic_artifacts: {
17
+ plan: 'worktree-plan.json',
18
+ state: 'worktree-state.json',
19
+ },
20
+ operations: {
21
+ plan: {
22
+ command: command('worktree-plan'),
23
+ topic_flag: '--topic',
24
+ },
25
+ create: {
26
+ command: command('worktree-create'),
27
+ topic_flag: '--topic',
28
+ additional_flags: ['--base'],
29
+ },
30
+ remove: {
31
+ command: command('worktree-remove'),
32
+ topic_flag: '--topic',
33
+ },
34
+ },
35
+ upstream_boundary: {
36
+ import_root: importRoot,
37
+ provenance_doc: join(rootDir, 'platform', 'claude-code', 'upstream', 'worktree', 'provenance.md'),
38
+ planned_upstream_modules: [
39
+ 'oh-my-claudecode/src/team/git-worktree.ts',
40
+ 'oh-my-claudecode/src/lib/worktree-paths.ts',
41
+ ],
42
+ active_imports: [
43
+ readImportedUpstreamSlice(join(HERE, 'upstream', 'worktree', 'imported', 'provenance.json')),
44
+ readImportedUpstreamSlice(join(HERE, 'upstream', 'worktree', 'imported', 'managed-worktree.provenance.json')),
45
+ ],
46
+ },
47
+ };
48
+ }
49
+ export async function planClaudeCodeTopicWorktree(input) {
50
+ return planTopicWorktree(input);
51
+ }
52
+ export async function createClaudeCodeTopicWorktree(input) {
53
+ const target = await resolveTopicWorktreeTarget(input);
54
+ if (existsSync(target.worktreePath)) {
55
+ return recordTopicWorktreeCreate(target, {
56
+ created: false,
57
+ reused: true,
58
+ });
59
+ }
60
+ createClaudeManagedWorktree({
61
+ repoRoot: target.rootDir,
62
+ worktreePath: target.worktreePath,
63
+ branchName: target.branchName,
64
+ baseBranch: target.baseBranch,
65
+ });
66
+ return recordTopicWorktreeCreate(target, {
67
+ created: true,
68
+ reused: false,
69
+ });
70
+ }
71
+ export async function removeClaudeCodeTopicWorktree(input) {
72
+ const target = await resolveTopicWorktreeTarget(input);
73
+ removeClaudeManagedWorktree({
74
+ repoRoot: target.rootDir,
75
+ worktreePath: target.worktreePath,
76
+ branchName: target.branchName,
77
+ });
78
+ return recordTopicWorktreeRemove(target, !existsSync(target.worktreePath));
79
+ }
@@ -0,0 +1,69 @@
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/codex/scaffold/AGENTS.template.md',
9
+ 'platform/codex/scaffold/prompts/shift-ax-bootstrap.template.md',
10
+ ...SHIFT_AX_PRODUCT_SHELL_COMMANDS.map((name) => `platform/codex/scaffold/skills/${name}/SKILL.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 codexScaffoldTemplateFiles() {
39
+ return [...TEMPLATE_FILES];
40
+ }
41
+ export function renderCodexAgentsBootstrap(rootDir, locale = 'en') {
42
+ return renderTemplate('platform/codex/scaffold/AGENTS.template.md', rootDir, locale);
43
+ }
44
+ export function renderCodexPromptBootstrap(rootDir, locale = 'en') {
45
+ return renderTemplate('platform/codex/scaffold/prompts/shift-ax-bootstrap.template.md', rootDir, locale);
46
+ }
47
+ export function getCodexBootstrapAssets(rootDir, locale = 'en') {
48
+ const commandAssets = buildProductShellAssets({
49
+ platform: 'codex',
50
+ commandBasePath: '.codex/skills',
51
+ renderTemplate: (name) => renderTemplate(`platform/codex/scaffold/skills/${name}/SKILL.template.md`, rootDir, locale),
52
+ });
53
+ return [
54
+ {
55
+ path: 'AGENTS.md',
56
+ description: 'Top-level AGENTS bootstrap for Codex builds.',
57
+ content: renderCodexAgentsBootstrap(rootDir, locale),
58
+ },
59
+ {
60
+ path: '.codex/prompts/shift-ax-bootstrap.md',
61
+ description: 'Codex bootstrap prompt fragment.',
62
+ content: renderCodexPromptBootstrap(rootDir, locale),
63
+ },
64
+ ...commandAssets.map((asset) => ({
65
+ ...asset,
66
+ path: asset.path.replace(/\.codex\/skills\/([^/]+)\.md$/, '.codex/skills/$1/SKILL.md'),
67
+ })),
68
+ ];
69
+ }
@@ -0,0 +1,163 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { materializeExecutionPrompts } from '../../core/planning/execution-launch.js';
4
+ import { sanitizeCodexTeamName } from './upstream/tmux/imported/sanitize-team-name.js';
5
+ function shellQuote(value) {
6
+ return `'${value.replace(/'/g, `'\\''`)}'`;
7
+ }
8
+ function buildCodexTmuxSessionName(topicSlug, taskId) {
9
+ const topicPart = sanitizeCodexTeamName(topicSlug).slice(0, 18).replace(/-$/, '');
10
+ const taskPart = sanitizeCodexTeamName(taskId).slice(0, 8).replace(/-$/, '');
11
+ return `axexec-${topicPart}-${taskPart}`;
12
+ }
13
+ function buildCodexExecShellCommand(plan) {
14
+ return `codex exec --full-auto -C ${shellQuote(plan.working_directory)} -o ${shellQuote(plan.output_path)} \"$(cat ${shellQuote(plan.prompt_path)})\"`;
15
+ }
16
+ function formatProcessError(error) {
17
+ const failure = error;
18
+ const stdout = typeof failure.stdout === 'string'
19
+ ? failure.stdout
20
+ : failure.stdout instanceof Buffer
21
+ ? failure.stdout.toString('utf8')
22
+ : '';
23
+ const stderr = typeof failure.stderr === 'string'
24
+ ? failure.stderr
25
+ : failure.stderr instanceof Buffer
26
+ ? failure.stderr.toString('utf8')
27
+ : '';
28
+ return [failure.message ?? 'process failed', stdout.trim(), stderr.trim()]
29
+ .filter(Boolean)
30
+ .join('\n');
31
+ }
32
+ function codexExecutionTimeoutMs() {
33
+ const parsed = Number(process.env.SHIFT_AX_CODEX_EXEC_TIMEOUT_MS || '180000');
34
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 180_000;
35
+ }
36
+ function clearExistingTmuxSession(sessionName) {
37
+ try {
38
+ execFileSync('tmux', ['has-session', '-t', sessionName], {
39
+ stdio: 'pipe',
40
+ });
41
+ execFileSync('tmux', ['kill-session', '-t', sessionName], {
42
+ stdio: 'pipe',
43
+ });
44
+ }
45
+ catch {
46
+ // no existing session to clear
47
+ }
48
+ }
49
+ export function getCodexExecutionRuntime() {
50
+ return {
51
+ support: 'available',
52
+ entrypoint_style: 'cli',
53
+ execution_handoff_artifact: 'execution-handoff.json',
54
+ operations: {
55
+ launch: {
56
+ command: ['shift-ax', 'launch-execution'],
57
+ topic_flag: '--topic',
58
+ additional_flags: ['--platform', '--task-id', '--dry-run'],
59
+ },
60
+ },
61
+ hosts: {
62
+ subagent_cli: 'codex',
63
+ tmux_cli: 'tmux',
64
+ },
65
+ };
66
+ }
67
+ export async function planCodexExecutionLaunch({ topicDir, taskId, }) {
68
+ const artifacts = await materializeExecutionPrompts(topicDir, taskId);
69
+ const tasks = artifacts.map((artifact) => {
70
+ const basePlan = {
71
+ task_id: artifact.task.id,
72
+ source_text: artifact.task.source_text,
73
+ execution_mode: artifact.task.execution_mode,
74
+ working_directory: artifact.worktreePath,
75
+ prompt_path: artifact.promptPath,
76
+ output_path: artifact.outputPath,
77
+ };
78
+ if (artifact.task.execution_mode === 'tmux') {
79
+ const sessionName = buildCodexTmuxSessionName(topicDir.split('/').pop() || 'topic', artifact.task.id);
80
+ const shellCommand = buildCodexExecShellCommand({
81
+ ...basePlan,
82
+ command: [],
83
+ shell_command: '',
84
+ });
85
+ return {
86
+ ...basePlan,
87
+ command: [
88
+ 'tmux',
89
+ 'new-session',
90
+ '-d',
91
+ '-s',
92
+ sessionName,
93
+ '-c',
94
+ artifact.worktreePath,
95
+ shellCommand,
96
+ ],
97
+ shell_command: shellCommand,
98
+ session_name: sessionName,
99
+ };
100
+ }
101
+ const shellCommand = buildCodexExecShellCommand({
102
+ ...basePlan,
103
+ command: [],
104
+ shell_command: '',
105
+ });
106
+ return {
107
+ ...basePlan,
108
+ command: [
109
+ 'codex',
110
+ 'exec',
111
+ '--full-auto',
112
+ '-C',
113
+ artifact.worktreePath,
114
+ '-o',
115
+ artifact.outputPath,
116
+ '-',
117
+ ],
118
+ shell_command: shellCommand,
119
+ };
120
+ });
121
+ return {
122
+ platform: 'codex',
123
+ launched: false,
124
+ topic_dir: topicDir,
125
+ tasks,
126
+ };
127
+ }
128
+ export async function launchCodexExecution({ topicDir, taskId, }) {
129
+ const plan = await planCodexExecutionLaunch({ topicDir, taskId });
130
+ for (const task of plan.tasks) {
131
+ if (task.execution_mode === 'tmux') {
132
+ if (task.session_name) {
133
+ clearExistingTmuxSession(task.session_name);
134
+ }
135
+ execFileSync(task.command[0], task.command.slice(1), {
136
+ stdio: 'pipe',
137
+ });
138
+ continue;
139
+ }
140
+ try {
141
+ execFileSync('codex', [
142
+ 'exec',
143
+ '--full-auto',
144
+ '-C',
145
+ task.working_directory,
146
+ '-o',
147
+ task.output_path,
148
+ readFileSync(task.prompt_path, 'utf8'),
149
+ ], {
150
+ cwd: task.working_directory,
151
+ stdio: ['ignore', 'pipe', 'pipe'],
152
+ timeout: codexExecutionTimeoutMs(),
153
+ });
154
+ }
155
+ catch (error) {
156
+ throw new Error(`Codex execution failed for ${task.task_id}: ${formatProcessError(error)}`);
157
+ }
158
+ }
159
+ return {
160
+ ...plan,
161
+ launched: true,
162
+ };
163
+ }