axstack 0.34.0 → 0.34.2
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/README.md +18 -0
- package/bin/axstack.js +8 -1
- package/package.json +9 -2
- package/skills/axstack/scripts/pr-digest.js +1 -1
- package/skills/axstack-relay/SKILL.md +33 -3
- package/src/archify.js +3 -1
- package/src/claude-settings.js +1 -1
- package/src/installer.js +28 -2
- package/src/posixpath.js +2 -1
package/README.md
CHANGED
|
@@ -147,6 +147,24 @@ Eligible own PRs use guarded automatic merge; see [merge boundaries, exclusions,
|
|
|
147
147
|
See [PR scope and sizing](skills/axstack/references/pr-shape.md) for candidate
|
|
148
148
|
shape and [Releases](https://github.com/axatbhardwaj/axstack/releases) for release history.
|
|
149
149
|
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```sh
|
|
153
|
+
bun install --frozen-lockfile
|
|
154
|
+
bun run lint
|
|
155
|
+
bun run typecheck
|
|
156
|
+
git config core.hooksPath .husky
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The hook is opt-in per clone via `git config core.hooksPath .husky` and requires
|
|
160
|
+
`bun install`. Without dev tools it prints a setup hint and skips the checks.
|
|
161
|
+
The hook runs lint and typecheck only. Biome formatting is optional for editors
|
|
162
|
+
or manual use; commits do not enforce formatting. TypeScript checks JavaScript
|
|
163
|
+
in `src/` and `bin/` without emitting files. All tooling is development-only and
|
|
164
|
+
is excluded from packed assets by `package.json.files`.
|
|
165
|
+
Regenerate `bun.lock` with the minimum Bun version in `package.json.engines`
|
|
166
|
+
(currently 1.3.14); newer Bun versions can write an incompatible lockfile format.
|
|
167
|
+
|
|
150
168
|
## License
|
|
151
169
|
|
|
152
170
|
[MIT](LICENSE) © 2026 Axat Bhardwaj.
|
package/bin/axstack.js
CHANGED
|
@@ -26,7 +26,7 @@ function homeDir() {
|
|
|
26
26
|
// Tilde expansion needs a known absolute home; with HOME missing, empty,
|
|
27
27
|
// or relative the destination is unknown, so refuse even with --yes.
|
|
28
28
|
const home = Bun.env.HOME;
|
|
29
|
-
if (!home
|
|
29
|
+
if (!home?.startsWith('/')) {
|
|
30
30
|
throw new Error('HOME is missing, empty, or not absolute; pass explicit paths instead of ~');
|
|
31
31
|
}
|
|
32
32
|
return home;
|
|
@@ -298,6 +298,13 @@ async function main() {
|
|
|
298
298
|
const legacyCodexManifest = legacyCodexDir
|
|
299
299
|
? await readLegacySkillsManifest(legacyCodexDir)
|
|
300
300
|
: null;
|
|
301
|
+
/**
|
|
302
|
+
* @type {Awaited<ReturnType<typeof installBundle>> & {
|
|
303
|
+
* legacyCodex?: {removed: string[], preserved: string[], missing: string[], skipped?: boolean,
|
|
304
|
+
* held?: boolean, failed?: boolean, failure?: boolean, reason?: string},
|
|
305
|
+
* addedRoleIds?: string[], removedRoleIds?: string[], changedRoleModels?: string[], notes?: string[]
|
|
306
|
+
* }}
|
|
307
|
+
*/
|
|
301
308
|
const summary = await installBundle({
|
|
302
309
|
bundleDir: flags.bundle ? resolve(flags.bundle) : PACKAGE_ROOT,
|
|
303
310
|
skillsDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "axstack",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.2",
|
|
4
4
|
"description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -32,8 +32,15 @@
|
|
|
32
32
|
"docs/*.md"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
+
"lint": "biome lint . --error-on-warnings",
|
|
36
|
+
"typecheck": "bun node_modules/typescript/bin/tsc --noEmit --project jsconfig.json",
|
|
35
37
|
"test": "bun test",
|
|
36
38
|
"test:all": "bun test"
|
|
37
39
|
},
|
|
38
|
-
"license": "MIT"
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@biomejs/biome": "2.5.15",
|
|
43
|
+
"@types/bun": "1.4.2",
|
|
44
|
+
"typescript": "7.0.2"
|
|
45
|
+
}
|
|
39
46
|
}
|
|
@@ -54,7 +54,7 @@ function fetchCurrent(args) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function nodes(connection, field) {
|
|
57
|
-
if (
|
|
57
|
+
if (connection?.pageInfo?.hasNextPage !== false || !Array.isArray(connection.nodes)) {
|
|
58
58
|
throw new Error(`incomplete ${field} connection`);
|
|
59
59
|
}
|
|
60
60
|
return connection.nodes;
|
|
@@ -155,20 +155,36 @@ reissuing only if a decision is still needed.
|
|
|
155
155
|
Each card must carry exactly one decision.
|
|
156
156
|
The card ID must be `<run id>/<n>`, never reused in the driver thread.
|
|
157
157
|
The only sender of reply-dependent cards must be the run's driver.
|
|
158
|
+
|
|
159
|
+
Decision cards and explain answers must use short paragraphs with blank lines
|
|
160
|
+
between thoughts for reading on a phone.
|
|
161
|
+
Decision cards and explain answers must use ordinary words and briefly define
|
|
162
|
+
necessary technical terms.
|
|
163
|
+
Each decision card must explain what is happening and why the choice matters.
|
|
164
|
+
Each numbered option must state the concrete action, scope, expected outcome
|
|
165
|
+
and material trade-off.
|
|
166
|
+
Defer and Cancel must describe the withheld action and what remains unresolved.
|
|
167
|
+
Any recommendation must include a brief evidence-supported reason.
|
|
168
|
+
A recommendation must leave the choice to the user.
|
|
169
|
+
|
|
158
170
|
Use plain ASCII for the card layout below, as one message:
|
|
159
171
|
|
|
160
172
|
```text
|
|
161
173
|
<project> - <decision title>
|
|
162
174
|
Card <run id>/<n>[, confirms card <run id>/<m>]
|
|
175
|
+
|
|
163
176
|
Context: <what we were doing, one line>
|
|
164
177
|
Decision: <the question>
|
|
178
|
+
|
|
179
|
+
1 <action> - <scope; outcome; trade-off>
|
|
180
|
+
2 <action> - <scope; outcome; trade-off>
|
|
181
|
+
3 Defer - <deferred action and scope; outcome; trade-off> (optional)
|
|
182
|
+
|
|
165
183
|
Driver environment: <value | not supplied>
|
|
166
184
|
Execution machine: <value | not supplied>
|
|
167
185
|
Affected targets: <values | not supplied>
|
|
168
186
|
Revision: <repo#PR @ full SHA | spec rev N sha256 | none>
|
|
169
|
-
|
|
170
|
-
2 <action> - <scope>
|
|
171
|
-
3 Defer (optional)
|
|
187
|
+
|
|
172
188
|
Reply 1, 2, 3 or explain. Open T3: <link | not available>
|
|
173
189
|
T3 reply: <env label> thread <driver threadId>
|
|
174
190
|
```
|
|
@@ -250,6 +266,13 @@ Run these four checks in order, stopping at the first failure:
|
|
|
250
266
|
Free text is user input, not a numbered option, and gets the same authority
|
|
251
267
|
revalidation and confirmation checks before it can authorize an action.
|
|
252
268
|
|
|
269
|
+
Explain answers must directly address the reader's confusion.
|
|
270
|
+
Explain answers must expand meaningful context and each option's consequences.
|
|
271
|
+
Explain answers must give a justified recommendation when evidence supports it.
|
|
272
|
+
Explain answers must state material risks and unknown facts plainly.
|
|
273
|
+
Never merely restate IDs or SHAs in an explain answer.
|
|
274
|
+
The same one-message size and final reply tag rules apply to explain answers.
|
|
275
|
+
|
|
253
276
|
## Confirm, acknowledge and act
|
|
254
277
|
|
|
255
278
|
Any forwarded reply, including free text and `(confirm)` options, authorizing
|
|
@@ -257,6 +280,13 @@ a destructive, production or fleet-wide action must first get a confirmation car
|
|
|
257
280
|
A confirmation card must have its own ID, name the card it confirms
|
|
258
281
|
(`confirms card`), bind the exact action, execution machine, affected targets
|
|
259
282
|
and revision, and offer `1 Confirm` and `2 Cancel`.
|
|
283
|
+
Use the same detail fields for confirmation option rows:
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
1 Confirm - <bound action and scope; outcome; trade-off>
|
|
287
|
+
2 Cancel - <withheld action and scope; outcome; trade-off>
|
|
288
|
+
```
|
|
289
|
+
|
|
260
290
|
Sending a confirmation must record the original card decided, pending
|
|
261
291
|
confirmation with no authority.
|
|
262
292
|
The driver must allow only a verified `1` on the open confirmation to authorize
|
package/src/archify.js
CHANGED
|
@@ -19,6 +19,7 @@ export function archifyGit(path, ...args) {
|
|
|
19
19
|
|
|
20
20
|
function verifySha(record, actual) {
|
|
21
21
|
if (actual === record.sha) return;
|
|
22
|
+
/** @type {Error & {code?: string}} */
|
|
22
23
|
const error = new Error(`archify SHA mismatch: expected ${record.sha}, got ${actual}`);
|
|
23
24
|
error.code = 'ARCHIFY_SHA_MISMATCH';
|
|
24
25
|
throw error;
|
|
@@ -31,7 +32,8 @@ async function ownersSnapshot(path) {
|
|
|
31
32
|
});
|
|
32
33
|
if (st && (!st.isFile() || st.isSymbolicLink())) throw new Error(`unsafe archify owners file: ${path}`);
|
|
33
34
|
const bytes = st ? await readFile(path) : null;
|
|
34
|
-
|
|
35
|
+
// JSON.parse coerces its argument to a string; preserve the existing Buffer coercion.
|
|
36
|
+
const owners = bytes ? /** @type {(text: {toString(): string}) => unknown} */ (JSON.parse)(bytes) : [];
|
|
35
37
|
if (!Array.isArray(owners) || owners.some((owner) => typeof owner !== 'string' || !owner.startsWith('/'))) {
|
|
36
38
|
throw new Error(`invalid archify owners file: ${path}`);
|
|
37
39
|
}
|
package/src/claude-settings.js
CHANGED
|
@@ -184,7 +184,7 @@ export async function planUninstallClaudeSettings({ claude, skillsRoot, boundPat
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
const owned = sidecar.keys[CLAUDE_SETTING_PATH];
|
|
187
|
-
if (!owned
|
|
187
|
+
if (!owned?.owners.includes(skillsRoot)) {
|
|
188
188
|
return {
|
|
189
189
|
report: { status: 'skipped', reason: 'this skills directory does not own the Claude setting' },
|
|
190
190
|
writes: [],
|
package/src/installer.js
CHANGED
|
@@ -179,7 +179,7 @@ function homeDir() {
|
|
|
179
179
|
// aliased HOME) compare equal to canonicalized targets; an unresolvable
|
|
180
180
|
// HOME also yields null and fails closed rather than bypassing the guard.
|
|
181
181
|
const home = Bun.env.HOME;
|
|
182
|
-
if (!home
|
|
182
|
+
if (!home?.startsWith('/')) return null;
|
|
183
183
|
try {
|
|
184
184
|
return realpathSync(home);
|
|
185
185
|
} catch {
|
|
@@ -238,7 +238,7 @@ export async function validateBundle(bundleDir, selectedPreset = null) {
|
|
|
238
238
|
if (skillsStat.isSymbolicLink() || !skillsStat.isDirectory()) {
|
|
239
239
|
throw new Error('bundle skills/ must be a real directory, not a symlink');
|
|
240
240
|
}
|
|
241
|
-
|
|
241
|
+
const entries = await readdir(skillsRoot, { withFileTypes: true });
|
|
242
242
|
// Symlinked (or non-directory) top-level skill entries must be rejected,
|
|
243
243
|
// not silently skipped: Dirent.isDirectory() is false for symlinks.
|
|
244
244
|
for (const entry of entries) {
|
|
@@ -380,6 +380,11 @@ async function walkSkills(dir, skillsRoot, out) {
|
|
|
380
380
|
// never followed or truncated. After the rename, existing permissions are
|
|
381
381
|
// preserved; new files take an explicit `mode` (e.g. 0600 for configs) or
|
|
382
382
|
// the umask default. Guards pre-existing entries, not active races.
|
|
383
|
+
/**
|
|
384
|
+
* @param {string} dest
|
|
385
|
+
* @param {string | Uint8Array} bytes
|
|
386
|
+
* @param {{mode?: number}} [options]
|
|
387
|
+
*/
|
|
383
388
|
export async function writeAtomic(dest, bytes, { mode } = {}) {
|
|
384
389
|
await mkdir(dirname(dest), { recursive: true });
|
|
385
390
|
let existingMode = null;
|
|
@@ -408,6 +413,7 @@ export async function writeAtomic(dest, bytes, { mode } = {}) {
|
|
|
408
413
|
}
|
|
409
414
|
}
|
|
410
415
|
|
|
416
|
+
/** @param {Pick<InstallerOptions, 'skillsDir' | 'instructionsPath'>} [options] */
|
|
411
417
|
export async function checkInstructionBinding({ skillsDir, instructionsPath } = {}) {
|
|
412
418
|
if (!skillsDir) throw new Error('instruction check requires --skills-dir <dir> or --harness <name>');
|
|
413
419
|
if (!instructionsPath) throw new Error('instruction check requires --instructions <file> or a supported harness');
|
|
@@ -467,6 +473,21 @@ async function assertFileSnapshot(dest, expected) {
|
|
|
467
473
|
}
|
|
468
474
|
}
|
|
469
475
|
|
|
476
|
+
/**
|
|
477
|
+
* @typedef {{available: boolean, settingsPath?: string, skip?: boolean, reason?: string}} ClaudeOption
|
|
478
|
+
* @typedef {Object} InstallerOptions
|
|
479
|
+
* @property {string} [skillsDir]
|
|
480
|
+
* @property {string} [instructionsPath]
|
|
481
|
+
* @property {boolean} [force]
|
|
482
|
+
* @property {boolean} [yes]
|
|
483
|
+
* @property {ClaudeOption} [claude]
|
|
484
|
+
* @property {(message: string) => void} [log]
|
|
485
|
+
*/
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* @param {InstallerOptions & {bundleDir?: string, preset?: string, toolsDir?: string,
|
|
489
|
+
* inheritedInstructions?: {path: string, hash: string}}} [options]
|
|
490
|
+
*/
|
|
470
491
|
export async function installBundle({
|
|
471
492
|
bundleDir,
|
|
472
493
|
skillsDir,
|
|
@@ -857,6 +878,7 @@ export async function installBundle({
|
|
|
857
878
|
}
|
|
858
879
|
}
|
|
859
880
|
|
|
881
|
+
/** @param {InstallerOptions} [options] */
|
|
860
882
|
export async function uninstallBundle({
|
|
861
883
|
skillsDir,
|
|
862
884
|
instructionsPath = null,
|
|
@@ -1110,6 +1132,10 @@ export async function uninstallBundle({
|
|
|
1110
1132
|
// is present and byte-for-byte verified. This deliberately handles skill
|
|
1111
1133
|
// payloads only: instruction files, Claude settings, and historical profile
|
|
1112
1134
|
// ownership may belong to another harness and remain bound to the old manifest.
|
|
1135
|
+
/**
|
|
1136
|
+
* @param {{canonicalSkillsDir?: string, legacySkillsDir?: string, yes?: boolean,
|
|
1137
|
+
* acceptedInstructionTransfer?: {path: string, legacyHash: string, canonicalHash: string}}} [options]
|
|
1138
|
+
*/
|
|
1113
1139
|
export async function retireLegacySkills({
|
|
1114
1140
|
canonicalSkillsDir,
|
|
1115
1141
|
legacySkillsDir,
|
package/src/posixpath.js
CHANGED
|
@@ -65,7 +65,8 @@ export function resolveFrom(cwd, ...paths) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export function resolve(...paths) {
|
|
68
|
-
|
|
68
|
+
// Bun.cwd is a runtime string missing from the current Bun declarations.
|
|
69
|
+
return resolveFrom(/** @type {typeof Bun & {cwd: string}} */ (Bun).cwd, ...paths);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
export function relative(from, to) {
|