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.
- package/LICENSE +21 -0
- package/README.ko.md +145 -0
- package/README.md +143 -0
- package/dist/adapters/claude-code/adapter.js +90 -0
- package/dist/adapters/codex/adapter.js +94 -0
- package/dist/adapters/contracts.js +7 -0
- package/dist/adapters/index.js +12 -0
- package/dist/core/context/context-bundle.js +300 -0
- package/dist/core/context/discovery.js +82 -0
- package/dist/core/context/global-index-authoring.js +199 -0
- package/dist/core/context/global-knowledge-updates.js +116 -0
- package/dist/core/context/glossary.js +73 -0
- package/dist/core/context/guided-onboarding.js +233 -0
- package/dist/core/context/index-authoring.js +47 -0
- package/dist/core/context/index-resolver.js +78 -0
- package/dist/core/context/onboarding.js +186 -0
- package/dist/core/diagnostics/doctor.js +154 -0
- package/dist/core/finalization/commit-message.js +76 -0
- package/dist/core/finalization/commit-workflow.js +131 -0
- package/dist/core/memory/consolidation.js +99 -0
- package/dist/core/memory/decision-register.js +141 -0
- package/dist/core/memory/entity-memory.js +25 -0
- package/dist/core/memory/learned-debug.js +52 -0
- package/dist/core/memory/summary-checkpoints.js +9 -0
- package/dist/core/memory/thread-promotion.js +22 -0
- package/dist/core/memory/threads.js +66 -0
- package/dist/core/memory/topic-recall.js +52 -0
- package/dist/core/observability/context-health.js +15 -0
- package/dist/core/observability/context-monitor.js +29 -0
- package/dist/core/observability/state-handoff.js +78 -0
- package/dist/core/observability/topic-status.js +40 -0
- package/dist/core/observability/topics-status.js +26 -0
- package/dist/core/observability/verification-debt.js +82 -0
- package/dist/core/planning/brainstorm.js +120 -0
- package/dist/core/planning/escalation.js +69 -0
- package/dist/core/planning/execution-handoff.js +61 -0
- package/dist/core/planning/execution-launch.js +156 -0
- package/dist/core/planning/execution-orchestrator.js +87 -0
- package/dist/core/planning/feedback-reactions.js +75 -0
- package/dist/core/planning/lifecycle-events.js +45 -0
- package/dist/core/planning/plan-review.js +76 -0
- package/dist/core/planning/policy-context-sync.js +154 -0
- package/dist/core/planning/request-pipeline.js +386 -0
- package/dist/core/planning/workflow-state.js +18 -0
- package/dist/core/policies/project-profile.js +28 -0
- package/dist/core/policies/team-preferences.js +17 -0
- package/dist/core/review/aggregate-reviews.js +129 -0
- package/dist/core/review/run-lanes.js +376 -0
- package/dist/core/settings/global-context-home.js +28 -0
- package/dist/core/settings/project-settings.js +37 -0
- package/dist/core/shell/platform-shell.js +144 -0
- package/dist/core/topics/bootstrap.js +119 -0
- package/dist/core/topics/topic-artifacts.js +36 -0
- package/dist/core/topics/worktree-runtime.js +141 -0
- package/dist/core/topics/worktree.js +8 -0
- package/dist/platform/claude-code/bootstrap.js +66 -0
- package/dist/platform/claude-code/execution.js +157 -0
- package/dist/platform/claude-code/scaffold/CLAUDE.template.md +40 -0
- package/dist/platform/claude-code/scaffold/commands/doctor.template.md +11 -0
- package/dist/platform/claude-code/scaffold/commands/export-context.template.md +20 -0
- package/dist/platform/claude-code/scaffold/commands/onboard.template.md +43 -0
- package/dist/platform/claude-code/scaffold/commands/onboarding.template.md +43 -0
- package/dist/platform/claude-code/scaffold/commands/request.template.md +19 -0
- package/dist/platform/claude-code/scaffold/commands/resume.template.md +12 -0
- package/dist/platform/claude-code/scaffold/commands/review.template.md +10 -0
- package/dist/platform/claude-code/scaffold/commands/status.template.md +14 -0
- package/dist/platform/claude-code/scaffold/commands/topics.template.md +10 -0
- package/dist/platform/claude-code/scaffold/hooks/shift-ax-session-start.template.md +29 -0
- package/dist/platform/claude-code/tmux.js +35 -0
- package/dist/platform/claude-code/upstream/tmux/imported/detached-session.js +40 -0
- package/dist/platform/claude-code/upstream/tmux/imported/session-name.js +19 -0
- package/dist/platform/claude-code/upstream/worktree/imported/get-worktree-root.js +39 -0
- package/dist/platform/claude-code/upstream/worktree/imported/managed-worktree.js +77 -0
- package/dist/platform/claude-code/worktree.js +79 -0
- package/dist/platform/codex/bootstrap.js +69 -0
- package/dist/platform/codex/execution.js +163 -0
- package/dist/platform/codex/scaffold/AGENTS.template.md +40 -0
- package/dist/platform/codex/scaffold/prompts/doctor.template.md +11 -0
- package/dist/platform/codex/scaffold/prompts/export-context.template.md +20 -0
- package/dist/platform/codex/scaffold/prompts/onboard.template.md +43 -0
- package/dist/platform/codex/scaffold/prompts/onboarding.template.md +43 -0
- package/dist/platform/codex/scaffold/prompts/request.template.md +19 -0
- package/dist/platform/codex/scaffold/prompts/resume.template.md +14 -0
- package/dist/platform/codex/scaffold/prompts/review.template.md +10 -0
- package/dist/platform/codex/scaffold/prompts/shift-ax-bootstrap.template.md +23 -0
- package/dist/platform/codex/scaffold/prompts/status.template.md +14 -0
- package/dist/platform/codex/scaffold/prompts/topics.template.md +10 -0
- package/dist/platform/codex/scaffold/skills/doctor/SKILL.template.md +11 -0
- package/dist/platform/codex/scaffold/skills/export-context/SKILL.template.md +20 -0
- package/dist/platform/codex/scaffold/skills/onboard/SKILL.template.md +43 -0
- package/dist/platform/codex/scaffold/skills/request/SKILL.template.md +19 -0
- package/dist/platform/codex/scaffold/skills/resume/SKILL.template.md +14 -0
- package/dist/platform/codex/scaffold/skills/review/SKILL.template.md +10 -0
- package/dist/platform/codex/scaffold/skills/status/SKILL.template.md +14 -0
- package/dist/platform/codex/scaffold/skills/topics/SKILL.template.md +10 -0
- package/dist/platform/codex/tmux.js +45 -0
- package/dist/platform/codex/upstream/tmux/imported/resize-hook-registration.js +37 -0
- package/dist/platform/codex/upstream/tmux/imported/resize-hooks.js +29 -0
- package/dist/platform/codex/upstream/tmux/imported/sanitize-team-name.js +18 -0
- package/dist/platform/codex/upstream/worktree/imported/managed-worktree.js +208 -0
- package/dist/platform/codex/upstream/worktree/imported/resolve-repo-root.js +14 -0
- package/dist/platform/codex/worktree.js +99 -0
- package/dist/platform/index.js +10 -0
- package/dist/platform/product-shell-commands.js +17 -0
- package/dist/platform/scaffold.js +16 -0
- package/dist/platform/upstream-imports.js +5 -0
- package/dist/scripts/ax-approve-plan.js +30 -0
- package/dist/scripts/ax-bootstrap-assets.js +19 -0
- package/dist/scripts/ax-bootstrap-topic.js +24 -0
- package/dist/scripts/ax-build-context-bundle.js +35 -0
- package/dist/scripts/ax-checkpoint-context.js +22 -0
- package/dist/scripts/ax-consolidate-memory.js +7 -0
- package/dist/scripts/ax-context-health.js +26 -0
- package/dist/scripts/ax-decisions.js +32 -0
- package/dist/scripts/ax-doctor.js +25 -0
- package/dist/scripts/ax-entity-memory.js +19 -0
- package/dist/scripts/ax-export-context.js +8 -0
- package/dist/scripts/ax-finalize-commit.js +23 -0
- package/dist/scripts/ax-init-context.js +41 -0
- package/dist/scripts/ax-launch-execution.js +24 -0
- package/dist/scripts/ax-learned-debug-save.js +30 -0
- package/dist/scripts/ax-learned-debug.js +12 -0
- package/dist/scripts/ax-monitor-context.js +28 -0
- package/dist/scripts/ax-onboard-context.js +112 -0
- package/dist/scripts/ax-pause-work.js +33 -0
- package/dist/scripts/ax-platform-manifest.js +19 -0
- package/dist/scripts/ax-promote-thread.js +20 -0
- package/dist/scripts/ax-react-feedback.js +28 -0
- package/dist/scripts/ax-recall-topics.js +20 -0
- package/dist/scripts/ax-recall.js +58 -0
- package/dist/scripts/ax-refresh-state.js +15 -0
- package/dist/scripts/ax-resolve-context.js +34 -0
- package/dist/scripts/ax-review.js +24 -0
- package/dist/scripts/ax-run-request.js +198 -0
- package/dist/scripts/ax-scaffold-build.js +19 -0
- package/dist/scripts/ax-shell.js +123 -0
- package/dist/scripts/ax-sync-policy-context.js +40 -0
- package/dist/scripts/ax-team-preferences.js +20 -0
- package/dist/scripts/ax-thread-save.js +26 -0
- package/dist/scripts/ax-threads.js +11 -0
- package/dist/scripts/ax-topic-status.js +18 -0
- package/dist/scripts/ax-topics-status.js +22 -0
- package/dist/scripts/ax-verification-debt.js +22 -0
- package/dist/scripts/ax-worktree-create.js +22 -0
- package/dist/scripts/ax-worktree-plan.js +18 -0
- package/dist/scripts/ax-worktree-remove.js +18 -0
- package/dist/scripts/ax.js +132 -0
- package/package.json +71 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { execFile as execFileCb, spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
const execFilePromise = promisify(execFileCb);
|
|
6
|
+
const BRANCH_IN_USE_PATTERN = /already checked out|already used by worktree|is already checked out/i;
|
|
7
|
+
function readGit(repoRoot, args) {
|
|
8
|
+
const result = spawnSync('git', args, {
|
|
9
|
+
cwd: repoRoot,
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
windowsHide: true,
|
|
12
|
+
});
|
|
13
|
+
if (result.status === 0) {
|
|
14
|
+
return (result.stdout || '').trim();
|
|
15
|
+
}
|
|
16
|
+
const stderr = (result.stderr || '').trim();
|
|
17
|
+
throw new Error(stderr || `git ${args.join(' ')} failed`);
|
|
18
|
+
}
|
|
19
|
+
function branchExists(repoRoot, branchName) {
|
|
20
|
+
const result = spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branchName}`], {
|
|
21
|
+
cwd: repoRoot,
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
windowsHide: true,
|
|
24
|
+
});
|
|
25
|
+
return result.status === 0;
|
|
26
|
+
}
|
|
27
|
+
function isWorktreeDirty(worktreePath) {
|
|
28
|
+
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
29
|
+
cwd: worktreePath,
|
|
30
|
+
encoding: 'utf-8',
|
|
31
|
+
windowsHide: true,
|
|
32
|
+
});
|
|
33
|
+
if (result.status !== 0) {
|
|
34
|
+
const stderr = (result.stderr || '').trim();
|
|
35
|
+
throw new Error(stderr || `worktree_status_failed:${worktreePath}`);
|
|
36
|
+
}
|
|
37
|
+
return (result.stdout || '').trim() !== '';
|
|
38
|
+
}
|
|
39
|
+
function listWorktrees(repoRoot) {
|
|
40
|
+
const raw = readGit(repoRoot, ['worktree', 'list', '--porcelain']);
|
|
41
|
+
if (!raw)
|
|
42
|
+
return [];
|
|
43
|
+
return raw
|
|
44
|
+
.split(/\n\n+/)
|
|
45
|
+
.map((chunk) => chunk.trim())
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.map((chunk) => {
|
|
48
|
+
const lines = chunk
|
|
49
|
+
.split(/\r?\n/)
|
|
50
|
+
.map((line) => line.trim())
|
|
51
|
+
.filter(Boolean);
|
|
52
|
+
const worktreeLine = lines.find((line) => line.startsWith('worktree '));
|
|
53
|
+
const headLine = lines.find((line) => line.startsWith('HEAD '));
|
|
54
|
+
const branchLine = lines.find((line) => line.startsWith('branch '));
|
|
55
|
+
if (!worktreeLine || !headLine)
|
|
56
|
+
return null;
|
|
57
|
+
return {
|
|
58
|
+
path: resolve(worktreeLine.slice('worktree '.length)),
|
|
59
|
+
head: headLine.slice('HEAD '.length).trim(),
|
|
60
|
+
branchRef: branchLine ? branchLine.slice('branch '.length).trim() : null,
|
|
61
|
+
detached: lines.includes('detached') || !branchLine,
|
|
62
|
+
};
|
|
63
|
+
})
|
|
64
|
+
.filter((entry) => Boolean(entry));
|
|
65
|
+
}
|
|
66
|
+
function findWorktreeByPath(entries, worktreePath) {
|
|
67
|
+
const resolvedPath = canonicalizePath(worktreePath);
|
|
68
|
+
return entries.find((entry) => canonicalizePath(entry.path) === resolvedPath) || null;
|
|
69
|
+
}
|
|
70
|
+
function hasBranchInUse(entries, branchName, worktreePath) {
|
|
71
|
+
const expectedRef = `refs/heads/${branchName}`;
|
|
72
|
+
const resolvedPath = canonicalizePath(worktreePath);
|
|
73
|
+
return entries.some((entry) => entry.branchRef === expectedRef && canonicalizePath(entry.path) !== resolvedPath);
|
|
74
|
+
}
|
|
75
|
+
function resolveGitCommonDir(cwd) {
|
|
76
|
+
const result = spawnSync('git', ['rev-parse', '--git-common-dir'], {
|
|
77
|
+
cwd,
|
|
78
|
+
encoding: 'utf-8',
|
|
79
|
+
windowsHide: true,
|
|
80
|
+
});
|
|
81
|
+
if (result.status !== 0)
|
|
82
|
+
return null;
|
|
83
|
+
const value = (result.stdout || '').trim();
|
|
84
|
+
return value ? resolve(cwd, value) : null;
|
|
85
|
+
}
|
|
86
|
+
function readWorktreeEntryFromPath(repoRoot, worktreePath) {
|
|
87
|
+
if (!existsSync(worktreePath))
|
|
88
|
+
return null;
|
|
89
|
+
const repoCommonDir = resolveGitCommonDir(repoRoot);
|
|
90
|
+
const worktreeCommonDir = resolveGitCommonDir(worktreePath);
|
|
91
|
+
if (!repoCommonDir || !worktreeCommonDir || repoCommonDir !== worktreeCommonDir) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const headResult = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
95
|
+
cwd: worktreePath,
|
|
96
|
+
encoding: 'utf-8',
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
});
|
|
99
|
+
if (headResult.status !== 0)
|
|
100
|
+
return null;
|
|
101
|
+
const head = (headResult.stdout || '').trim();
|
|
102
|
+
if (!head)
|
|
103
|
+
return null;
|
|
104
|
+
const branchResult = spawnSync('git', ['symbolic-ref', '-q', 'HEAD'], {
|
|
105
|
+
cwd: worktreePath,
|
|
106
|
+
encoding: 'utf-8',
|
|
107
|
+
windowsHide: true,
|
|
108
|
+
});
|
|
109
|
+
const branchRef = branchResult.status === 0 ? (branchResult.stdout || '').trim() : null;
|
|
110
|
+
return {
|
|
111
|
+
path: canonicalizePath(worktreePath),
|
|
112
|
+
head,
|
|
113
|
+
branchRef: branchRef || null,
|
|
114
|
+
detached: !branchRef,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function canonicalizePath(path) {
|
|
118
|
+
if (existsSync(path)) {
|
|
119
|
+
try {
|
|
120
|
+
return realpathSync(path);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return resolve(path);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return resolve(path);
|
|
127
|
+
}
|
|
128
|
+
export function ensureCodexManagedWorktree(plan) {
|
|
129
|
+
const allWorktrees = listWorktrees(plan.repoRoot);
|
|
130
|
+
const existingAtPath = findWorktreeByPath(allWorktrees, plan.worktreePath) ??
|
|
131
|
+
readWorktreeEntryFromPath(plan.repoRoot, plan.worktreePath);
|
|
132
|
+
const expectedBranchRef = `refs/heads/${plan.branchName}`;
|
|
133
|
+
if (existingAtPath) {
|
|
134
|
+
if (existingAtPath.branchRef !== expectedBranchRef) {
|
|
135
|
+
throw new Error(`worktree_target_mismatch:${plan.worktreePath}`);
|
|
136
|
+
}
|
|
137
|
+
if (isWorktreeDirty(plan.worktreePath)) {
|
|
138
|
+
throw new Error(`worktree_dirty:${plan.worktreePath}`);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
repoRoot: plan.repoRoot,
|
|
142
|
+
worktreePath: canonicalizePath(plan.worktreePath),
|
|
143
|
+
branchName: plan.branchName,
|
|
144
|
+
created: false,
|
|
145
|
+
reused: true,
|
|
146
|
+
createdBranch: false,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (existsSync(plan.worktreePath)) {
|
|
150
|
+
throw new Error(`worktree_path_conflict:${plan.worktreePath}`);
|
|
151
|
+
}
|
|
152
|
+
if (hasBranchInUse(allWorktrees, plan.branchName, plan.worktreePath)) {
|
|
153
|
+
throw new Error(`branch_in_use:${plan.branchName}`);
|
|
154
|
+
}
|
|
155
|
+
mkdirSync(dirname(plan.worktreePath), { recursive: true });
|
|
156
|
+
const branchAlreadyExisted = branchExists(plan.repoRoot, plan.branchName);
|
|
157
|
+
const addArgs = branchAlreadyExisted
|
|
158
|
+
? ['worktree', 'add', plan.worktreePath, plan.branchName]
|
|
159
|
+
: ['worktree', 'add', '-b', plan.branchName, plan.worktreePath, plan.baseRef];
|
|
160
|
+
const result = spawnSync('git', addArgs, {
|
|
161
|
+
cwd: plan.repoRoot,
|
|
162
|
+
encoding: 'utf-8',
|
|
163
|
+
windowsHide: true,
|
|
164
|
+
});
|
|
165
|
+
if (result.status !== 0) {
|
|
166
|
+
const stderr = (result.stderr || '').trim();
|
|
167
|
+
if (BRANCH_IN_USE_PATTERN.test(stderr)) {
|
|
168
|
+
throw new Error(`branch_in_use:${plan.branchName}`);
|
|
169
|
+
}
|
|
170
|
+
throw new Error(stderr || `worktree_add_failed:${addArgs.join(' ')}`);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
repoRoot: plan.repoRoot,
|
|
174
|
+
worktreePath: canonicalizePath(plan.worktreePath),
|
|
175
|
+
branchName: plan.branchName,
|
|
176
|
+
created: true,
|
|
177
|
+
reused: false,
|
|
178
|
+
createdBranch: !branchAlreadyExisted,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export async function removeCodexManagedWorktree(result) {
|
|
182
|
+
await execFilePromise('git', ['worktree', 'remove', '--force', result.worktreePath], {
|
|
183
|
+
cwd: result.repoRoot,
|
|
184
|
+
encoding: 'utf-8',
|
|
185
|
+
});
|
|
186
|
+
const entriesAfterRemove = listWorktrees(result.repoRoot);
|
|
187
|
+
const stillCheckedOut = hasBranchInUse(entriesAfterRemove, result.branchName, result.worktreePath);
|
|
188
|
+
if (stillCheckedOut) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await execFilePromise('git', ['branch', '-D', result.branchName], {
|
|
193
|
+
cwd: result.repoRoot,
|
|
194
|
+
encoding: 'utf-8',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (branchExists(result.repoRoot, result.branchName)) {
|
|
199
|
+
const err = error;
|
|
200
|
+
const stderr = typeof err.stderr === 'string'
|
|
201
|
+
? err.stderr.trim()
|
|
202
|
+
: err.stderr instanceof Buffer
|
|
203
|
+
? err.stderr.toString('utf-8').trim()
|
|
204
|
+
: '';
|
|
205
|
+
throw new Error(stderr || `delete_branch:${result.branchName}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Imported from oh-my-codex.
|
|
4
|
+
* Source: oh-my-codex/src/cli/autoresearch.ts
|
|
5
|
+
* Commit: fabb3ce0b96e42c20feb2940c74f2aa5addb8cee
|
|
6
|
+
*/
|
|
7
|
+
export function resolveCodexRepoRoot(cwd) {
|
|
8
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
9
|
+
cwd,
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
12
|
+
windowsHide: true,
|
|
13
|
+
}).trim();
|
|
14
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { planTopicWorktree, recordTopicWorktreeCreate, recordTopicWorktreeRemove, resolveTopicWorktreeTarget, } from '../../core/topics/worktree-runtime.js';
|
|
6
|
+
import { readImportedUpstreamSlice } from '../upstream-imports.js';
|
|
7
|
+
import { ensureCodexManagedWorktree, removeCodexManagedWorktree, } from './upstream/worktree/imported/managed-worktree.js';
|
|
8
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
function command(name) {
|
|
10
|
+
return ['shift-ax', name];
|
|
11
|
+
}
|
|
12
|
+
export function getCodexWorktreeRuntime(rootDir) {
|
|
13
|
+
const importRoot = join(rootDir, 'platform', 'codex', 'upstream', 'worktree');
|
|
14
|
+
return {
|
|
15
|
+
support: 'available',
|
|
16
|
+
entrypoint_style: 'cli',
|
|
17
|
+
topic_artifacts: {
|
|
18
|
+
plan: 'worktree-plan.json',
|
|
19
|
+
state: 'worktree-state.json',
|
|
20
|
+
},
|
|
21
|
+
operations: {
|
|
22
|
+
plan: {
|
|
23
|
+
command: command('worktree-plan'),
|
|
24
|
+
topic_flag: '--topic',
|
|
25
|
+
},
|
|
26
|
+
create: {
|
|
27
|
+
command: command('worktree-create'),
|
|
28
|
+
topic_flag: '--topic',
|
|
29
|
+
additional_flags: ['--base'],
|
|
30
|
+
},
|
|
31
|
+
remove: {
|
|
32
|
+
command: command('worktree-remove'),
|
|
33
|
+
topic_flag: '--topic',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
upstream_boundary: {
|
|
37
|
+
import_root: importRoot,
|
|
38
|
+
provenance_doc: join(rootDir, 'platform', 'codex', 'upstream', 'worktree', 'provenance.md'),
|
|
39
|
+
planned_upstream_modules: [
|
|
40
|
+
'oh-my-codex/src/team/worktree.ts',
|
|
41
|
+
'oh-my-codex/src/team/state-root.ts',
|
|
42
|
+
],
|
|
43
|
+
active_imports: [
|
|
44
|
+
readImportedUpstreamSlice(join(HERE, 'upstream', 'worktree', 'imported', 'provenance.json')),
|
|
45
|
+
readImportedUpstreamSlice(join(HERE, 'upstream', 'worktree', 'imported', 'managed-worktree.provenance.json')),
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function planCodexTopicWorktree(input) {
|
|
51
|
+
return planTopicWorktree(input);
|
|
52
|
+
}
|
|
53
|
+
export async function createCodexTopicWorktree(input) {
|
|
54
|
+
const target = await resolveTopicWorktreeTarget(input);
|
|
55
|
+
const baseRef = ensureBaseRef(target.rootDir, target.baseBranch);
|
|
56
|
+
const result = ensureCodexManagedWorktree({
|
|
57
|
+
repoRoot: target.rootDir,
|
|
58
|
+
worktreePath: target.worktreePath,
|
|
59
|
+
branchName: target.branchName,
|
|
60
|
+
baseRef,
|
|
61
|
+
});
|
|
62
|
+
return recordTopicWorktreeCreate(target, {
|
|
63
|
+
created: result.created,
|
|
64
|
+
reused: result.reused,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function removeCodexTopicWorktree(input) {
|
|
68
|
+
const target = await resolveTopicWorktreeTarget(input);
|
|
69
|
+
if (existsSync(target.worktreePath)) {
|
|
70
|
+
await removeCodexManagedWorktree({
|
|
71
|
+
repoRoot: target.rootDir,
|
|
72
|
+
worktreePath: target.worktreePath,
|
|
73
|
+
branchName: target.branchName,
|
|
74
|
+
created: false,
|
|
75
|
+
reused: false,
|
|
76
|
+
createdBranch: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
try {
|
|
81
|
+
execFileSync('git', ['branch', '-D', target.branchName], {
|
|
82
|
+
cwd: target.rootDir,
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Branch may already be absent.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return recordTopicWorktreeRemove(target, !existsSync(target.worktreePath));
|
|
92
|
+
}
|
|
93
|
+
function ensureBaseRef(rootDir, baseBranch) {
|
|
94
|
+
return execFileSync('git', ['rev-parse', baseBranch], {
|
|
95
|
+
cwd: rootDir,
|
|
96
|
+
encoding: 'utf8',
|
|
97
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
98
|
+
}).trim();
|
|
99
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { codexScaffoldTemplateFiles, getCodexBootstrapAssets, renderCodexAgentsBootstrap, } from './codex/bootstrap.js';
|
|
2
|
+
export { claudeCodeScaffoldTemplateFiles, getClaudeCodeBootstrapAssets, renderClaudeCodeSessionStartContext, } from './claude-code/bootstrap.js';
|
|
3
|
+
import { getClaudeCodeBootstrapAssets } from './claude-code/bootstrap.js';
|
|
4
|
+
import { getCodexBootstrapAssets } from './codex/bootstrap.js';
|
|
5
|
+
export function getPlatformBootstrapAssets(platform, rootDir, locale = 'en') {
|
|
6
|
+
if (platform === 'codex') {
|
|
7
|
+
return getCodexBootstrapAssets(rootDir, locale);
|
|
8
|
+
}
|
|
9
|
+
return getClaudeCodeBootstrapAssets(rootDir, locale);
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const SHIFT_AX_PRODUCT_SHELL_COMMANDS = [
|
|
2
|
+
'onboard',
|
|
3
|
+
'request',
|
|
4
|
+
'export-context',
|
|
5
|
+
'doctor',
|
|
6
|
+
'status',
|
|
7
|
+
'topics',
|
|
8
|
+
'resume',
|
|
9
|
+
'review',
|
|
10
|
+
];
|
|
11
|
+
export function buildProductShellAssets({ platform, commandBasePath, renderTemplate, }) {
|
|
12
|
+
return SHIFT_AX_PRODUCT_SHELL_COMMANDS.map((name) => ({
|
|
13
|
+
path: `${commandBasePath}/${name}.md`,
|
|
14
|
+
description: `Shift AX ${platform === 'codex' ? 'Codex' : 'Claude Code'} product-shell command: ${platform === 'codex' ? '$' : '/'}${name}`,
|
|
15
|
+
content: renderTemplate(name),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { getPlatformBootstrapAssets } from './index.js';
|
|
4
|
+
export async function scaffoldPlatformBuild({ platform, rootDir, }) {
|
|
5
|
+
const assets = getPlatformBootstrapAssets(platform, rootDir);
|
|
6
|
+
await Promise.all(assets.map(async (asset) => {
|
|
7
|
+
const absolutePath = join(rootDir, asset.path);
|
|
8
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
9
|
+
await writeFile(absolutePath, asset.content, 'utf8');
|
|
10
|
+
}));
|
|
11
|
+
return {
|
|
12
|
+
platform,
|
|
13
|
+
rootDir,
|
|
14
|
+
written: assets.map((asset) => asset.path),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { recordPlanReviewDecision } from '../core/planning/plan-review.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-approve-plan --topic DIR --reviewer NAME --decision <approve|reject> [--notes "<text>"]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const topicDir = readArg('--topic');
|
|
13
|
+
const reviewer = readArg('--reviewer');
|
|
14
|
+
const decision = readArg('--decision');
|
|
15
|
+
const notes = readArg('--notes');
|
|
16
|
+
if (!topicDir || !reviewer || !decision) {
|
|
17
|
+
usage();
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (decision !== 'approve' && decision !== 'reject') {
|
|
21
|
+
usage();
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const result = await recordPlanReviewDecision({
|
|
25
|
+
topicDir,
|
|
26
|
+
reviewer,
|
|
27
|
+
status: decision === 'approve' ? 'approved' : 'changes_requested',
|
|
28
|
+
notes,
|
|
29
|
+
});
|
|
30
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getPlatformBootstrapAssets } from '../platform/index.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-bootstrap-assets --platform <codex|claude-code> [--root DIR]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const platform = readArg('--platform');
|
|
13
|
+
const rootDir = readArg('--root') || process.cwd();
|
|
14
|
+
if (platform !== 'codex' && platform !== 'claude-code') {
|
|
15
|
+
usage();
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const assets = getPlatformBootstrapAssets(platform, rootDir);
|
|
19
|
+
process.stdout.write(`${JSON.stringify(assets, null, 2)}\n`);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { bootstrapTopic } from '../core/topics/bootstrap.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-bootstrap-topic --request "<text>" [--summary "<text>"] [--root DIR]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const request = readArg('--request');
|
|
13
|
+
const summary = readArg('--summary');
|
|
14
|
+
const rootDir = readArg('--root') || process.cwd();
|
|
15
|
+
if (!request) {
|
|
16
|
+
usage();
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const result = await bootstrapTopic({
|
|
20
|
+
rootDir,
|
|
21
|
+
request,
|
|
22
|
+
summary,
|
|
23
|
+
});
|
|
24
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildContextBundle } from '../core/context/context-bundle.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-build-context-bundle [--root DIR] [--topic DIR] --query "<text>" [--max-chars N] [--output PATH]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const rootDir = readArg('--root');
|
|
13
|
+
const topicDir = readArg('--topic');
|
|
14
|
+
const query = readArg('--query');
|
|
15
|
+
const outputPath = readArg('--output');
|
|
16
|
+
const maxChars = Number(readArg('--max-chars') || '6000');
|
|
17
|
+
if (!query || (!rootDir && !topicDir)) {
|
|
18
|
+
usage();
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const result = outputPath
|
|
22
|
+
? (await import('../core/context/context-bundle.js')).writeContextBundle({
|
|
23
|
+
...(rootDir ? { rootDir } : {}),
|
|
24
|
+
...(topicDir ? { topicDir } : {}),
|
|
25
|
+
query,
|
|
26
|
+
outputPath,
|
|
27
|
+
maxChars: Number.isFinite(maxChars) && maxChars > 0 ? maxChars : 6000,
|
|
28
|
+
}).then(({ bundle, output_path }) => ({ ...bundle, output_path }))
|
|
29
|
+
: await buildContextBundle({
|
|
30
|
+
...(rootDir ? { rootDir } : {}),
|
|
31
|
+
...(topicDir ? { topicDir } : {}),
|
|
32
|
+
query,
|
|
33
|
+
maxChars: Number.isFinite(maxChars) && maxChars > 0 ? maxChars : 6000,
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeTopicSummaryCheckpoint } from '../core/memory/summary-checkpoints.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-checkpoint-context --topic DIR --summary "<text>"\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const topicDir = readArg('--topic');
|
|
13
|
+
const summary = readArg('--summary');
|
|
14
|
+
if (!topicDir || !summary) {
|
|
15
|
+
usage();
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const result = await writeTopicSummaryCheckpoint({
|
|
19
|
+
topicDir,
|
|
20
|
+
summary,
|
|
21
|
+
});
|
|
22
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { consolidateMemory } from '../core/memory/consolidation.js';
|
|
3
|
+
const rootDir = process.argv.includes('--root')
|
|
4
|
+
? process.argv[process.argv.indexOf('--root') + 1]
|
|
5
|
+
: process.cwd();
|
|
6
|
+
const result = await consolidateMemory({ rootDir });
|
|
7
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { assessContextHealth } from '../core/observability/context-health.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-context-health [--root DIR] [--topic DIR] --query "<text>" [--max-chars N]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const rootDir = readArg('--root');
|
|
13
|
+
const topicDir = readArg('--topic');
|
|
14
|
+
const query = readArg('--query');
|
|
15
|
+
const maxChars = Number(readArg('--max-chars') || '6000');
|
|
16
|
+
if (!query || (!rootDir && !topicDir)) {
|
|
17
|
+
usage();
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const result = await assessContextHealth({
|
|
21
|
+
...(rootDir ? { rootDir } : {}),
|
|
22
|
+
...(topicDir ? { topicDir } : {}),
|
|
23
|
+
query,
|
|
24
|
+
maxChars: Number.isFinite(maxChars) && maxChars > 0 ? maxChars : 6000,
|
|
25
|
+
});
|
|
26
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { listDecisionRecords, searchDecisionMemory } from '../core/memory/decision-register.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-decisions [--root DIR] [--query "<text>"] [--active-at YYYY-MM-DD] [--limit N]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const rootDir = readArg('--root') || process.cwd();
|
|
13
|
+
const query = readArg('--query');
|
|
14
|
+
const activeAt = readArg('--active-at');
|
|
15
|
+
const limit = Number(readArg('--limit') || '5');
|
|
16
|
+
if (process.argv.includes('--help')) {
|
|
17
|
+
usage();
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
const result = query
|
|
21
|
+
? await searchDecisionMemory({
|
|
22
|
+
rootDir,
|
|
23
|
+
query,
|
|
24
|
+
activeAt,
|
|
25
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 5,
|
|
26
|
+
})
|
|
27
|
+
: await listDecisionRecords({
|
|
28
|
+
rootDir,
|
|
29
|
+
query,
|
|
30
|
+
activeAt,
|
|
31
|
+
});
|
|
32
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runDoctor } from '../core/diagnostics/doctor.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-doctor [--root DIR] [--topic DIR] [--platform <codex|claude-code>]\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const rootDir = readArg('--root') || process.cwd();
|
|
13
|
+
const topicDir = readArg('--topic');
|
|
14
|
+
const platformArg = readArg('--platform');
|
|
15
|
+
if (platformArg && platformArg !== 'codex' && platformArg !== 'claude-code') {
|
|
16
|
+
usage();
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const platform = platformArg;
|
|
20
|
+
const report = await runDoctor({
|
|
21
|
+
rootDir,
|
|
22
|
+
...(topicDir ? { topicDir } : {}),
|
|
23
|
+
...(platform ? { platform } : {}),
|
|
24
|
+
});
|
|
25
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { buildEntityMemoryView } from '../core/memory/entity-memory.js';
|
|
3
|
+
function usage() {
|
|
4
|
+
process.stderr.write('Usage: ax-entity-memory --root DIR --entity NAME\n');
|
|
5
|
+
}
|
|
6
|
+
function readArg(flag) {
|
|
7
|
+
const index = process.argv.indexOf(flag);
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return undefined;
|
|
10
|
+
return process.argv[index + 1];
|
|
11
|
+
}
|
|
12
|
+
const rootDir = readArg('--root') || process.cwd();
|
|
13
|
+
const entity = readArg('--entity');
|
|
14
|
+
if (!entity) {
|
|
15
|
+
usage();
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const result = await buildEntityMemoryView({ rootDir, entity });
|
|
19
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getGlobalContextHome } from '../core/settings/global-context-home.js';
|
|
3
|
+
const home = getGlobalContextHome();
|
|
4
|
+
process.stdout.write(JSON.stringify({
|
|
5
|
+
share_root: home.root,
|
|
6
|
+
index_path: home.indexPath,
|
|
7
|
+
message: `Share ${home.root} with teammates who do similar work and tell them to place it at the same location on their machine.`,
|
|
8
|
+
}, null, 2) + '\n');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { finalizeTopicCommit } from '../core/finalization/commit-workflow.js';
|
|
4
|
+
function usage() {
|
|
5
|
+
process.stderr.write('Usage: ax-finalize-commit --topic DIR [--message-file PATH]\n');
|
|
6
|
+
}
|
|
7
|
+
function readArg(flag) {
|
|
8
|
+
const index = process.argv.indexOf(flag);
|
|
9
|
+
if (index === -1)
|
|
10
|
+
return undefined;
|
|
11
|
+
return process.argv[index + 1];
|
|
12
|
+
}
|
|
13
|
+
const topicDir = readArg('--topic');
|
|
14
|
+
const messageFile = readArg('--message-file');
|
|
15
|
+
if (!topicDir) {
|
|
16
|
+
usage();
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
const result = await finalizeTopicCommit({
|
|
20
|
+
topicDir,
|
|
21
|
+
message: messageFile ? await readFile(messageFile, 'utf8') : undefined,
|
|
22
|
+
});
|
|
23
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|