axstack 0.9.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.md +132 -0
- package/bin/axstack.js +396 -0
- package/docs/installation.md +239 -0
- package/docs/workflows.md +220 -0
- package/package.json +40 -0
- package/profiles/presets/claude-only.json +194 -0
- package/profiles/presets/codex-only.json +194 -0
- package/profiles/presets/mixed.json +194 -0
- package/skills/axstack/SKILL.md +81 -0
- package/skills/axstack/references/automations.md +368 -0
- package/skills/axstack/references/candidate-publication.md +45 -0
- package/skills/axstack/references/contracts.md +102 -0
- package/skills/axstack/references/lifecycle.md +137 -0
- package/skills/axstack/references/orca-runtime.md +109 -0
- package/skills/axstack/references/pr-shape.md +39 -0
- package/skills/axstack/references/routing.md +129 -0
- package/skills/axstack/references/run-record.md +109 -0
- package/skills/axstack-align/SKILL.md +121 -0
- package/skills/axstack-audit/SKILL.md +137 -0
- package/skills/axstack-audit/references/record.md +28 -0
- package/skills/axstack-debug/SKILL.md +157 -0
- package/skills/axstack-debug/references/packet.md +80 -0
- package/skills/axstack-explain/SKILL.md +66 -0
- package/skills/axstack-explain/references/visual-qa.md +15 -0
- package/skills/axstack-implement/SKILL.md +164 -0
- package/skills/axstack-improve/SKILL.md +69 -0
- package/skills/axstack-relay/SKILL.md +102 -0
- package/skills/axstack-research/SKILL.md +57 -0
- package/skills/axstack-research/references/checklist.md +25 -0
- package/skills/axstack-review/SKILL.md +343 -0
- package/skills/axstack-spec/SKILL.md +67 -0
- package/skills/axstack-tickets/SKILL.md +86 -0
- package/skills/axstack-watch/SKILL.md +160 -0
- package/skills/axstack-watch/references/repair-publication.md +69 -0
- package/skills/axstack-watch/references/watch-runtime.md +60 -0
- package/src/capabilities.js +138 -0
- package/src/claude-settings.js +230 -0
- package/src/installer.js +980 -0
- package/src/instructions.js +100 -0
- package/src/locations.js +43 -0
- package/src/manifest.js +251 -0
- package/src/posixpath.js +108 -0
- package/src/roles.js +142 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Axat Bhardwaj
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Axstack
|
|
2
|
+
|
|
3
|
+
Axstack is a standalone toolkit for finishing agreed engineering work with less
|
|
4
|
+
supervision while retaining independent review. Chat drives execution; a Bun
|
|
5
|
+
CLI installs owned skills and role data and checks capabilities. Axstack has no
|
|
6
|
+
daemon, scheduler, runtime database, or workflow state machine.
|
|
7
|
+
|
|
8
|
+
Orca is the only supported active runtime. Its installed, version-matched
|
|
9
|
+
`orchestration` and `orca-cli` guides own worktrees, sessions, supervised
|
|
10
|
+
dispatch, messages, settlement, and handoff mechanics. Axstack owns scope,
|
|
11
|
+
role choices, evidence, review policy, and one private derived run record.
|
|
12
|
+
|
|
13
|
+
## How a run works
|
|
14
|
+
|
|
15
|
+
Invoke `axstack` or the needed phase directly: `axstack-align`,
|
|
16
|
+
`axstack-spec`, `axstack-tickets`, `axstack-implement`, `axstack-review`, and
|
|
17
|
+
`axstack-watch`. Direct `axstack-research`, `axstack-explain`,
|
|
18
|
+
`axstack-improve`, and `axstack-debug` routes need no spec ceremony. `axstack-relay` remains an
|
|
19
|
+
optional inline route for explicit messages and authorized notifications; an
|
|
20
|
+
unavailable or legacy-runtime-only relay falls back to the current conversation
|
|
21
|
+
without changing authority.
|
|
22
|
+
|
|
23
|
+
1. Classify new engineering work as substantial, small, or unclear with a brief
|
|
24
|
+
reason. Small, bounded one-PR work uses the current request or selected issue
|
|
25
|
+
as a snapshotted small-change intent;
|
|
26
|
+
substantial or stacked work needs an approved spec and matching ticket map.
|
|
27
|
+
2. The current chat drives on whatever model runs it; there is no driver
|
|
28
|
+
profile. Bind each ready task to the selected role snapshot and an authoritative Orca
|
|
29
|
+
Run, Task, and Dispatch. Exactly one writer owns a candidate at a time.
|
|
30
|
+
3. Peer PRs receive both configured independent reviewer roles. Authored PRs
|
|
31
|
+
receive one eligible reviewer from the selected preset's explicit mapping
|
|
32
|
+
and actual author provenance. Every review binds the exact head and base.
|
|
33
|
+
Stable IDs are `axstack-reviewer-primary` and `axstack-reviewer-secondary`.
|
|
34
|
+
4. Accepted repairs return to the same author where its session and evidence
|
|
35
|
+
remain valid. The human merges by default, bottom-up for a stack.
|
|
36
|
+
5. Full handoff requires explicit recipient acceptance of the exact scope and
|
|
37
|
+
authority before ownership changes. Ordinary resume reconciles the current
|
|
38
|
+
owner instead of replacing it.
|
|
39
|
+
|
|
40
|
+
Active PR fanout is dependency- and capacity-driven; there is no fixed count.
|
|
41
|
+
Each PR has one theme and a measured size under the shared
|
|
42
|
+
[PR-shape policy](skills/axstack/references/pr-shape.md). Routine shape and
|
|
43
|
+
fanout choices remain autonomous inside approved scope. Material scope,
|
|
44
|
+
serious-risk, unavailable-model, and human-merge holds remain explicit.
|
|
45
|
+
The autonomous driver records the rationale band's cohesion rationale; the exception band needs a
|
|
46
|
+
reasonable split attempt and full exception record. Size alone never requires
|
|
47
|
+
user approval.
|
|
48
|
+
|
|
49
|
+
## Install from source
|
|
50
|
+
|
|
51
|
+
Requirements: Bun >=1.3.14, Git, `gh`, the `gh stack` extension, and a running
|
|
52
|
+
Orca with its runtime-owned guides. Filesystem access uses Bun's implementation
|
|
53
|
+
of `node:fs` and `node:fs/promises`; there are no runtime dependencies.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
bun bin/axstack.js check --bundle . [--harness claude|codex]
|
|
57
|
+
bun bin/axstack.js install --bundle . --skills-dir <dir> --instructions <file> --preset mixed [--yes]
|
|
58
|
+
bun bin/axstack.js install --bundle . --skills-dir <dir> --preset mixed
|
|
59
|
+
bun bin/axstack.js uninstall --skills-dir <dir> --instructions <file>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use `--harness codex` or `--harness claude` only for a verified default skill
|
|
63
|
+
directory. `--claude-settings` and `--no-claude-settings` manage the existing
|
|
64
|
+
Claude Code subagent default transaction; they do not configure Orca roles.
|
|
65
|
+
See [installation details](docs/installation.md).
|
|
66
|
+
|
|
67
|
+
`--instructions` manages one versioned Axstack block in `AGENTS.md` or
|
|
68
|
+
`CLAUDE.md`. Harness defaults resolve those files automatically. The block
|
|
69
|
+
points at the installed entry skill and requires delegated work to use visible
|
|
70
|
+
Orca orchestration rather than a harness-native subagent tool. Text and file
|
|
71
|
+
mode outside the markers are preserved; edited, malformed, unowned, or unsafe
|
|
72
|
+
targets are reported without normal-path adoption. Install exits nonzero when
|
|
73
|
+
an instruction conflict is preserved, while clean and idempotent installs exit
|
|
74
|
+
successfully.
|
|
75
|
+
|
|
76
|
+
The public bundle preserves three canonical 21-role inputs:
|
|
77
|
+
[mixed](profiles/presets/mixed.json),
|
|
78
|
+
[codex-only](profiles/presets/codex-only.json), and
|
|
79
|
+
[claude-only](profiles/presets/claude-only.json). Each is exactly
|
|
80
|
+
`{ "version": 1, "roles": [...] }`. Installation writes the selected snapshot
|
|
81
|
+
to `<skills-dir>/axstack/roles.json` as
|
|
82
|
+
`{ "version": 1, "preset": "<name>", "roles": [...] }` under normal ownership
|
|
83
|
+
hashes. An edited installed role file is preserved.
|
|
84
|
+
|
|
85
|
+
Mixed configures independent Astra and Fable advisers at high. Single-provider
|
|
86
|
+
presets preserve both adviser IDs and mark the unavailable one with `model:
|
|
87
|
+
null` inside that preset's provider bounds; installation remains ready, while
|
|
88
|
+
Align and Spec hold because both receipts are required. The mixed
|
|
89
|
+
`axstack-checker` model is also intentionally `null`; that role stays held
|
|
90
|
+
instead of inheriting a provider default. The single-provider presets configure
|
|
91
|
+
the checker. Preset changes affect new runs only. Stored model, effort, and permission
|
|
92
|
+
fields are declared intent until actual Orca launch receipts establish effective
|
|
93
|
+
behavior; installation never proves provider availability or permission parity.
|
|
94
|
+
Subscription availability and quota never select a fallback model.
|
|
95
|
+
End-to-end compatibility remains unverified without matching runtime receipts.
|
|
96
|
+
|
|
97
|
+
## Runtime evidence and holds
|
|
98
|
+
|
|
99
|
+
Native Orca exercises have returned Codex and Claude worker completions,
|
|
100
|
+
same-terminal follow-up, separate worktree placement, settlement cleanup,
|
|
101
|
+
`user_takeover` retention, and recovery from `consumer_fenced`. These are
|
|
102
|
+
bounded runtime facts, not proof that every role or harness is compatible.
|
|
103
|
+
|
|
104
|
+
Input acceptance is not agent readiness. A trust prompt was observed after an
|
|
105
|
+
accepted launch, so startup recovery must inspect the existing attempt, never
|
|
106
|
+
answer trust or permission prompts on the worker's behalf, and never create a
|
|
107
|
+
duplicate writer. A `worker_done` advances work only when its Task and Dispatch
|
|
108
|
+
match the active attempt and its revision evidence verifies.
|
|
109
|
+
|
|
110
|
+
The user lifted the native-watch hold by decision on 2026-09-16. Orca's native automation
|
|
111
|
+
schema exposes a provider but cannot pin model, effort, or permission, so the
|
|
112
|
+
driver automation records its model identity every tick and the watchdog treats
|
|
113
|
+
a mismatch as a safety hold. Axstack uses no historical fallback and introduces
|
|
114
|
+
no custom scheduler. The five-minute driver, hourly read-only watchdog, quiet
|
|
115
|
+
healthy ticks, and shared 24-hour deadline remain the acceptance contract.
|
|
116
|
+
|
|
117
|
+
Mobile completion/reply behavior remains unverified. Structural checks and
|
|
118
|
+
qualitative scenario evaluation are not live runtime proof.
|
|
119
|
+
|
|
120
|
+
## Historical migration boundary
|
|
121
|
+
|
|
122
|
+
Older releases used Paseo for orchestration and could leave profile ownership
|
|
123
|
+
provenance or retired skills behind. That state is historical and inert in the
|
|
124
|
+
Orca runtime. Migration preserves user-edited and unknown assets and records
|
|
125
|
+
legacy ownership without reading, writing, or deleting live host configuration.
|
|
126
|
+
Use the explicit migration guidance in [installation](docs/installation.md);
|
|
127
|
+
release installation, host cutover, and old-timer cleanup need separate
|
|
128
|
+
authorization.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
[MIT](LICENSE) © 2026 Axat Bhardwaj.
|
package/bin/axstack.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// axstack — installation/setup CLI. Installs owned skill bundles and selected
|
|
3
|
+
// role data, configures Claude settings, and checks host capabilities. Chat
|
|
4
|
+
// CLI performs installation bookkeeping only (no runtime, scheduler, or
|
|
5
|
+
// model calls).
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { join, resolve } from '../src/posixpath.js';
|
|
8
|
+
import {
|
|
9
|
+
checkInstructionBinding,
|
|
10
|
+
installBundle,
|
|
11
|
+
uninstallBundle,
|
|
12
|
+
validateBundle,
|
|
13
|
+
} from '../src/installer.js';
|
|
14
|
+
import { BUN_FLOOR, checkCapabilities, meetsFloor, runRealCheck } from '../src/capabilities.js';
|
|
15
|
+
import { harnessLocations } from '../src/locations.js';
|
|
16
|
+
|
|
17
|
+
// import.meta.dir is already a filesystem path (no URL conversion, so
|
|
18
|
+
// spaces/percent/hash characters survive verbatim).
|
|
19
|
+
const HERE = import.meta.dir;
|
|
20
|
+
const PACKAGE_ROOT = resolve(HERE, '..');
|
|
21
|
+
|
|
22
|
+
function homeDir() {
|
|
23
|
+
// Tilde expansion needs a known absolute home; with HOME missing, empty,
|
|
24
|
+
// or relative the destination is unknown, so refuse even with --yes.
|
|
25
|
+
const home = Bun.env.HOME;
|
|
26
|
+
if (!home || !home.startsWith('/')) {
|
|
27
|
+
throw new Error('HOME is missing, empty, or not absolute; pass explicit paths instead of ~');
|
|
28
|
+
}
|
|
29
|
+
return home;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function packageVersion() {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8')).version;
|
|
35
|
+
} catch {
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const HELP = `axstack — Axstack setup CLI (installation bookkeeping only)
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
axstack install --preset <mixed|codex-only|claude-only> --bundle <dir> --skills-dir <dir> [--instructions <file>] [--claude-settings <file>|--no-claude-settings] [--harness <name>] [--force] [--yes]
|
|
44
|
+
axstack check [--bundle <dir>] [--instructions <file>] [--skills-dir <dir>|--harness <name>]
|
|
45
|
+
axstack uninstall --skills-dir <dir> [--instructions <file>] [--claude-settings <file>|--no-claude-settings] [--harness <name>] [--force] [--yes]
|
|
46
|
+
axstack --help | --version
|
|
47
|
+
|
|
48
|
+
Commands:
|
|
49
|
+
install Validate a skill bundle, then copy owned skills and selected role
|
|
50
|
+
data and record ownership hashes.
|
|
51
|
+
check Probe Bun, Git, gh stack, and resolved Orca runtime/guide
|
|
52
|
+
capabilities. Probes do not prove model or execution compatibility.
|
|
53
|
+
uninstall Remove only unchanged Axstack-owned assets. User edits survive.
|
|
54
|
+
|
|
55
|
+
Flags:
|
|
56
|
+
--bundle <dir> Bundle root holding skills/axstack-*/SKILL.md and
|
|
57
|
+
profiles/presets/*.json role data. Defaults to the package root.
|
|
58
|
+
--preset <name> Required routing preset: mixed, codex-only, or claude-only.
|
|
59
|
+
Aliases: codex = codex-only; claude = claude-only.
|
|
60
|
+
--skills-dir <dir> Explicit install target (required). Overrides --harness.
|
|
61
|
+
--instructions <file>
|
|
62
|
+
Instruction file to receive the owned routing block.
|
|
63
|
+
Harness defaults: ~/.claude/CLAUDE.md for Claude and
|
|
64
|
+
$CODEX_HOME/AGENTS.md for Codex (default ~/.codex).
|
|
65
|
+
--claude-settings <file>
|
|
66
|
+
Manage the Claude Code user settings file at this path.
|
|
67
|
+
This forces testable availability without installing Claude.
|
|
68
|
+
--no-claude-settings
|
|
69
|
+
Skip Claude Code user-settings management.
|
|
70
|
+
--harness <name> Known harness (${harnessLocations().map((h) => h.harness).join(', ')}).
|
|
71
|
+
Grok has no verified auto-discovery: --skills-dir is required.
|
|
72
|
+
--force Overwrite/remove user-edited owned assets and take
|
|
73
|
+
ownership of unknown files. Off by default.
|
|
74
|
+
--yes Confirm writes inside your home directory. Temp dirs
|
|
75
|
+
outside home never need this.
|
|
76
|
+
|
|
77
|
+
Migration:
|
|
78
|
+
--profile is obsolete. Axstack installs the selected preset as
|
|
79
|
+
<skills-dir>/axstack/roles.json and never reads or writes Paseo config.
|
|
80
|
+
|
|
81
|
+
Safety:
|
|
82
|
+
Bundles with symlinks, absolute paths or '..' escapes are rejected before
|
|
83
|
+
any write. Unknown pre-existing files are never silently overwritten.
|
|
84
|
+
Partial failures roll back files created in that run.
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
function parseArgs(argv) {
|
|
88
|
+
const out = { command: null, flags: {} };
|
|
89
|
+
const rest = [...argv];
|
|
90
|
+
if (rest.length === 0 || rest[0] === '--help' || rest[0] === '-h') {
|
|
91
|
+
out.command = 'help';
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
if (rest[0] === '--version' || rest[0] === '-V') {
|
|
95
|
+
out.command = 'version';
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
out.command = rest.shift();
|
|
99
|
+
const wantsValue = new Set([
|
|
100
|
+
'--bundle', '--skills-dir', '--instructions', '--harness', '--preset', '--claude-settings',
|
|
101
|
+
]);
|
|
102
|
+
while (rest.length > 0) {
|
|
103
|
+
const tok = rest.shift();
|
|
104
|
+
if (tok === '--profile') {
|
|
105
|
+
throw new Error(
|
|
106
|
+
'--profile is obsolete in the Orca-only installer; role data is installed as axstack/roles.json and legacy Paseo cleanup is separate',
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (wantsValue.has(tok)) {
|
|
110
|
+
const val = rest.shift();
|
|
111
|
+
if (val === undefined || val.startsWith('--')) {
|
|
112
|
+
throw new Error(`${tok} requires a value (got ${val ?? 'nothing'}); refusing`);
|
|
113
|
+
}
|
|
114
|
+
out.flags[tok.slice(2)] = val;
|
|
115
|
+
} else if (tok === '--force') out.flags.force = true;
|
|
116
|
+
else if (tok === '--yes') out.flags.yes = true;
|
|
117
|
+
else if (tok === '--no-claude-settings') out.flags['no-claude-settings'] = true;
|
|
118
|
+
else if (tok === '--help' || tok === '-h') out.command = 'help';
|
|
119
|
+
else throw new Error(`unknown argument: ${tok}`);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function defaultClaudeSettingsPath() {
|
|
125
|
+
if (Bun.env.CLAUDE_CONFIG_DIR) {
|
|
126
|
+
return join(resolve(Bun.env.CLAUDE_CONFIG_DIR), 'settings.json');
|
|
127
|
+
}
|
|
128
|
+
const home = Bun.env.HOME;
|
|
129
|
+
return home?.startsWith('/') ? join(home, '.claude', 'settings.json') : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isInsideRawHome(path) {
|
|
133
|
+
const home = Bun.env.HOME;
|
|
134
|
+
if (!home?.startsWith('/') || !path) return false;
|
|
135
|
+
const absoluteHome = resolve(home);
|
|
136
|
+
const absolutePath = resolve(path);
|
|
137
|
+
return absolutePath === absoluteHome || absolutePath.startsWith(`${absoluteHome}/`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveClaudeOption(flags, command) {
|
|
141
|
+
if (flags['claude-settings'] && flags['no-claude-settings']) {
|
|
142
|
+
throw new Error('--claude-settings and --no-claude-settings cannot be used together');
|
|
143
|
+
}
|
|
144
|
+
if (flags['no-claude-settings']) {
|
|
145
|
+
return { available: false, settingsPath: null, skip: true, reason: 'disabled by --no-claude-settings' };
|
|
146
|
+
}
|
|
147
|
+
if (flags['claude-settings']) {
|
|
148
|
+
return { available: true, settingsPath: expandHome(flags['claude-settings']), explicit: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const settingsPath = defaultClaudeSettingsPath();
|
|
152
|
+
const available = Bun.which('claude') !== null;
|
|
153
|
+
if (command === 'install' && available && !flags.yes && isInsideRawHome(settingsPath)) {
|
|
154
|
+
return {
|
|
155
|
+
available: false,
|
|
156
|
+
settingsPath: null,
|
|
157
|
+
reason: 'default settings path is inside HOME; re-run with --yes',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
available,
|
|
162
|
+
settingsPath: available ? settingsPath : null,
|
|
163
|
+
reason: available ? undefined : 'Claude Code is not available',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function printClaudeSettings(report, { uninstall = false } = {}) {
|
|
168
|
+
if (!report) return;
|
|
169
|
+
if (report.status === 'skipped') {
|
|
170
|
+
console.log(`claude settings: skipped — ${report.reason}`);
|
|
171
|
+
} else if (!uninstall && report.status === 'set') {
|
|
172
|
+
console.log(`claude settings: set env.CLAUDE_CODE_SUBAGENT_MODEL=opus in ${report.path}`);
|
|
173
|
+
} else if (!uninstall && report.status === 'unchanged') {
|
|
174
|
+
console.log('claude settings: unchanged (owned)');
|
|
175
|
+
} else if (!uninstall && report.status === 'preserved') {
|
|
176
|
+
console.log(`claude settings: preserved existing value ${JSON.stringify(report.value)} (not owned)`);
|
|
177
|
+
} else if (uninstall && report.status === 'removed') {
|
|
178
|
+
console.log(`claude settings: removed env.CLAUDE_CODE_SUBAGENT_MODEL from ${report.path}`);
|
|
179
|
+
} else if (uninstall && report.status === 'retained') {
|
|
180
|
+
console.log('claude settings: unchanged (owned by another skills install)');
|
|
181
|
+
} else if (uninstall && report.status === 'preserved') {
|
|
182
|
+
console.log(`claude settings: preserved edited value ${JSON.stringify(report.value)} (ownership released)`);
|
|
183
|
+
} else if (uninstall && report.status === 'released') {
|
|
184
|
+
console.log('claude settings: ownership released (bound settings file missing)');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function expandHome(p) {
|
|
189
|
+
if (p === '~') return homeDir();
|
|
190
|
+
if (p.startsWith('~/')) return join(homeDir(), p.slice(2));
|
|
191
|
+
return p;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function resolveHarnessTarget(harness) {
|
|
195
|
+
const table = harnessLocations();
|
|
196
|
+
const entry = table.find((h) => h.harness === harness);
|
|
197
|
+
if (!entry) throw new Error(`unknown harness: ${harness} (known: ${table.map((h) => h.harness).join(', ')})`);
|
|
198
|
+
if (entry.discovery === 'unverified') {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`harness '${harness}' has no verified auto-discovery; pass an explicit --skills-dir override`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (entry.harness === 'codex') {
|
|
204
|
+
// User skills live under $CODEX_HOME/skills; CODEX_HOME defaults to ~/.codex.
|
|
205
|
+
const home = Bun.env.CODEX_HOME ? expandHome(Bun.env.CODEX_HOME) : join(homeDir(), '.codex');
|
|
206
|
+
return join(home, 'skills');
|
|
207
|
+
}
|
|
208
|
+
return expandHome(entry.skillsDir);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function resolveHarnessInstructions(harness) {
|
|
212
|
+
if (harness === 'claude') return join(homeDir(), '.claude', 'CLAUDE.md');
|
|
213
|
+
if (harness === 'codex') {
|
|
214
|
+
const codexHome = Bun.env.CODEX_HOME
|
|
215
|
+
? expandHome(Bun.env.CODEX_HOME)
|
|
216
|
+
: join(homeDir(), '.codex');
|
|
217
|
+
return join(codexHome, 'AGENTS.md');
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function printCheckReport(report) {
|
|
223
|
+
console.log('Capability check:');
|
|
224
|
+
for (const c of report.checks) {
|
|
225
|
+
console.log(` [${c.ok ? 'ok' : 'MISSING'}] ${c.label}: ${c.detail}`);
|
|
226
|
+
}
|
|
227
|
+
if (report.gaps.length > 0) {
|
|
228
|
+
console.log('Gaps detected:');
|
|
229
|
+
for (const g of report.gaps) console.log(` - ${g}`);
|
|
230
|
+
} else {
|
|
231
|
+
console.log('No gaps detected.');
|
|
232
|
+
}
|
|
233
|
+
console.log('Probe limits:');
|
|
234
|
+
for (const line of report.limitations) console.log(` - ${line}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function checkRuntimeFloor() {
|
|
238
|
+
if (!meetsFloor(Bun.version, BUN_FLOOR)) {
|
|
239
|
+
console.error(`axstack: requires Bun >= ${BUN_FLOOR} (running ${Bun.version})`);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function main() {
|
|
247
|
+
if (!checkRuntimeFloor()) return;
|
|
248
|
+
let parsed;
|
|
249
|
+
try {
|
|
250
|
+
parsed = parseArgs(Bun.argv.slice(2));
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`axstack: ${err.message}`);
|
|
253
|
+
console.error(HELP);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const { command, flags } = parsed;
|
|
258
|
+
|
|
259
|
+
if (command === 'help') {
|
|
260
|
+
console.log(HELP);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (command === 'version') {
|
|
264
|
+
console.log(`axstack ${packageVersion()}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
if (command === 'install') {
|
|
270
|
+
let skillsDir = flags['skills-dir'] ? expandHome(flags['skills-dir']) : null;
|
|
271
|
+
if (!skillsDir && flags.harness) skillsDir = resolveHarnessTarget(flags.harness);
|
|
272
|
+
const instructionsPath = flags.instructions
|
|
273
|
+
? expandHome(flags.instructions)
|
|
274
|
+
: flags.harness
|
|
275
|
+
? resolveHarnessInstructions(flags.harness)
|
|
276
|
+
: null;
|
|
277
|
+
const summary = await installBundle({
|
|
278
|
+
bundleDir: flags.bundle ? resolve(flags.bundle) : PACKAGE_ROOT,
|
|
279
|
+
skillsDir,
|
|
280
|
+
preset: flags.preset,
|
|
281
|
+
instructionsPath,
|
|
282
|
+
force: !!flags.force,
|
|
283
|
+
yes: !!flags.yes,
|
|
284
|
+
claude: resolveClaudeOption(flags, 'install'),
|
|
285
|
+
log: (m) => { if (m !== 'plan complete') console.log(m); },
|
|
286
|
+
});
|
|
287
|
+
const changed =
|
|
288
|
+
summary.added.length + summary.updated.length + summary.removed.length +
|
|
289
|
+
(['created', 'updated'].includes(summary.instructions?.status) ? 1 : 0);
|
|
290
|
+
if (changed === 0) {
|
|
291
|
+
console.log('Install complete: no changes (idempotent, everything unchanged).');
|
|
292
|
+
} else {
|
|
293
|
+
if (summary.added.length) console.log(`added: ${summary.added.join(', ')}`);
|
|
294
|
+
if (summary.updated.length) console.log(`updated: ${summary.updated.join(', ')}`);
|
|
295
|
+
if (summary.removed.length) console.log(`removed: ${summary.removed.join(', ')}`);
|
|
296
|
+
if (summary.unchanged.length) console.log(`unchanged: ${summary.unchanged.join(', ')}`);
|
|
297
|
+
}
|
|
298
|
+
if (summary.preserved.length) {
|
|
299
|
+
console.log(`preserved user edits (use --force to overwrite): ${summary.preserved.join(', ')}`);
|
|
300
|
+
}
|
|
301
|
+
if (summary.stale.length) console.log(`stale owned files left on disk: ${summary.stale.join(', ')}`);
|
|
302
|
+
if (summary.instructions) {
|
|
303
|
+
const i = summary.instructions;
|
|
304
|
+
if (i.status === 'conflict') {
|
|
305
|
+
console.log(`instruction conflict: ${i.path} preserved (${i.reason})`);
|
|
306
|
+
process.exitCode = 1;
|
|
307
|
+
} else {
|
|
308
|
+
console.log(`instruction ${i.status}: ${i.path}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (summary.notes?.length) {
|
|
312
|
+
for (const note of summary.notes) console.log(`note: ${note}`);
|
|
313
|
+
}
|
|
314
|
+
if (summary.roles) {
|
|
315
|
+
const p = summary.roles;
|
|
316
|
+
console.log(
|
|
317
|
+
p.ready
|
|
318
|
+
? `preset ${summary.preset}: ready`
|
|
319
|
+
: `preset ${summary.preset}: NOT ready — ${p.gaps.join('; ')}`,
|
|
320
|
+
);
|
|
321
|
+
if (!p.ready) process.exitCode = 1;
|
|
322
|
+
} else {
|
|
323
|
+
console.log('role data was not installed; readiness is unverified.');
|
|
324
|
+
}
|
|
325
|
+
printClaudeSettings(summary.claudeSettings);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (command === 'check') {
|
|
329
|
+
const report = await checkCapabilities(runRealCheck);
|
|
330
|
+
printCheckReport(report);
|
|
331
|
+
if (flags.bundle) {
|
|
332
|
+
const bundle = await validateBundle(resolve(flags.bundle));
|
|
333
|
+
const presetNames = Object.keys(bundle.presets);
|
|
334
|
+
console.log(
|
|
335
|
+
`bundle ok: ${bundle.files.length} skill files, ` +
|
|
336
|
+
(presetNames.length > 0 ? `role presets ${presetNames.join(', ')}` : 'no role presets'),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (flags.instructions || flags.harness) {
|
|
340
|
+
let skillsDir = flags['skills-dir'] ? expandHome(flags['skills-dir']) : null;
|
|
341
|
+
if (!skillsDir && flags.harness) skillsDir = resolveHarnessTarget(flags.harness);
|
|
342
|
+
const instructionsPath = flags.instructions
|
|
343
|
+
? expandHome(flags.instructions)
|
|
344
|
+
: resolveHarnessInstructions(flags.harness);
|
|
345
|
+
const instruction = await checkInstructionBinding({ skillsDir, instructionsPath });
|
|
346
|
+
console.log(
|
|
347
|
+
`instruction ${instruction.status}: ${instruction.path}` +
|
|
348
|
+
(instruction.reason ? ` (${instruction.reason})` : ''),
|
|
349
|
+
);
|
|
350
|
+
if (instruction.status !== 'owned') process.exitCode = 1;
|
|
351
|
+
}
|
|
352
|
+
if (report.gaps.length > 0) process.exitCode = 1;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (command === 'uninstall') {
|
|
356
|
+
let skillsDir = flags['skills-dir'] ? expandHome(flags['skills-dir']) : null;
|
|
357
|
+
if (!skillsDir && flags.harness) skillsDir = resolveHarnessTarget(flags.harness);
|
|
358
|
+
const instructionsPath = flags.instructions
|
|
359
|
+
? expandHome(flags.instructions)
|
|
360
|
+
: flags.harness
|
|
361
|
+
? resolveHarnessInstructions(flags.harness)
|
|
362
|
+
: null;
|
|
363
|
+
const summary = await uninstallBundle({
|
|
364
|
+
skillsDir,
|
|
365
|
+
instructionsPath,
|
|
366
|
+
force: !!flags.force,
|
|
367
|
+
yes: !!flags.yes,
|
|
368
|
+
claude: resolveClaudeOption(flags, 'uninstall'),
|
|
369
|
+
log: (m) => console.log(m),
|
|
370
|
+
});
|
|
371
|
+
if (summary.note) console.log(summary.note);
|
|
372
|
+
if (summary.removed.length) console.log(`removed: ${summary.removed.join(', ')}`);
|
|
373
|
+
if (summary.preserved.length) {
|
|
374
|
+
console.log(`preserved user edits (use --force to remove): ${summary.preserved.join(', ')}`);
|
|
375
|
+
}
|
|
376
|
+
if (summary.instructions) {
|
|
377
|
+
const i = summary.instructions;
|
|
378
|
+
if (i.status === 'conflict' || i.status === 'preserved') {
|
|
379
|
+
console.log(`instruction ${i.status}: ${i.path} preserved (${i.reason})`);
|
|
380
|
+
} else {
|
|
381
|
+
console.log(`instruction ${i.status}: ${i.path}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
printClaudeSettings(summary.claudeSettings, { uninstall: true });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
console.error(`axstack: unknown command: ${command}`);
|
|
388
|
+
console.error(HELP);
|
|
389
|
+
process.exitCode = 1;
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.error(`axstack: ${err.message}`);
|
|
392
|
+
process.exitCode = 1;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
await main();
|