axstack 0.34.0 → 0.34.1
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/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.1",
|
|
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;
|
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) {
|