@sabaiway/agent-workflow-memory 1.1.0 → 1.1.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/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bin/install.mjs +33 -10
- package/bin/install.test.mjs +36 -3
- package/capability.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to the memory substrate. Versions are this **package's** npm
|
|
|
4
4
|
they are distinct from the **deployment-lineage** stamp written into a project's
|
|
5
5
|
`docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `1.3.0`).
|
|
6
6
|
|
|
7
|
+
## 1.1.1 — Installer hardening (Issue-004 parity)
|
|
8
|
+
|
|
9
|
+
A patch release that applies the same two installer fixes shipped to the engine in 1.1.0, keeping the
|
|
10
|
+
two identical family installers in lockstep.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Containment check now accepts a legitimately-contained child literally named `..foo` (it wrongly
|
|
14
|
+
rejected anything starting with `..` before); `tildify` collapses only a **leading** `$HOME`, never
|
|
15
|
+
a mid-path occurrence (**Issue-004**).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- The installer is importable without side effects (the `isDirectRun` guard) and exports its
|
|
19
|
+
path/format helpers for in-process tests. The installer's own + README bare `npx … init` strings now
|
|
20
|
+
use `@latest`.
|
|
21
|
+
|
|
22
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file).
|
|
23
|
+
|
|
7
24
|
## 1.1.0 — Hidden mode writes project-local, not global, excludes
|
|
8
25
|
|
|
9
26
|
Memory's **hidden** visibility now targets the **project-local** `.git/info/exclude` (its own footprint
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ into a delimited slot in `AGENTS.md` by the family composition root, not by this
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx @sabaiway/agent-workflow-memory init
|
|
20
|
+
npx @sabaiway/agent-workflow-memory@latest init
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Installs/refreshes the skill at `~/.claude/skills/agent-workflow-memory` (override with
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-memory
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory substrate in any project — an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) and a structured `docs/ai/` context store with cap/archive/index enforcement. Use when the user wants to bootstrap `docs/ai/`, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-memory` / `/agent-workflow-memory upgrade`. Triggers on "set up the memory system", "deploy the AI memory here", "bootstrap docs/ai", "upgrade the memory substrate". This is the substrate only — the workflow methodology (plan→execute→review, queue, Cleanup) is owned elsewhere and injected into AGENTS.md by the family composition root.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '1.1.
|
|
6
|
+
version: '1.1.1'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
package/bin/install.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// One-shot installer for @sabaiway/agent-workflow-memory.
|
|
3
3
|
//
|
|
4
|
-
// npx @sabaiway/agent-workflow-memory init
|
|
4
|
+
// npx @sabaiway/agent-workflow-memory@latest init
|
|
5
5
|
//
|
|
6
6
|
// Copies the memory substrate into the canonical skill home
|
|
7
7
|
// (~/.claude/skills/agent-workflow-memory, override with --dir or
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// No telemetry, no phone-home. Dependency-free, Node >= 18.
|
|
17
17
|
|
|
18
18
|
import { readFile, mkdir, readdir, copyFile, lstat, readlink, symlink } from 'node:fs/promises';
|
|
19
|
-
import { existsSync, lstatSync } from 'node:fs';
|
|
19
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
20
20
|
import { dirname, join, resolve, relative, sep, isAbsolute } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { homedir } from 'node:os';
|
|
@@ -38,7 +38,11 @@ const PAYLOAD = [
|
|
|
38
38
|
'migrations',
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// Collapse only a LEADING homedir() to "~" — anchored at the string start (boundary-checked with
|
|
42
|
+
// `sep`), never a mid-path occurrence. A naive `path.replace(homedir(), '~')` would rewrite the
|
|
43
|
+
// first match anywhere in the path (Issue-004). Exported so the regression can pin it in-process.
|
|
44
|
+
export const tildify = (path) =>
|
|
45
|
+
path === homedir() ? '~' : path.startsWith(homedir() + sep) ? `~${path.slice(homedir().length)}` : path;
|
|
42
46
|
|
|
43
47
|
const readVersion = async () => {
|
|
44
48
|
try {
|
|
@@ -65,9 +69,12 @@ const lstatNoFollow = (path) => {
|
|
|
65
69
|
// within `root`. Walks `root` plus each existing path component down to `dest`; if the
|
|
66
70
|
// root, an intermediate dir, or the leaf is a symlink (including a dangling one), a copy
|
|
67
71
|
// could escape the target — STOP rather than follow it. Also refuses a dest outside `root`.
|
|
68
|
-
const assertContainedRealPath = (root, dest) => {
|
|
72
|
+
export const assertContainedRealPath = (root, dest) => {
|
|
69
73
|
const rel = relative(root, dest);
|
|
70
|
-
|
|
74
|
+
// A true escape is `..` exactly or a `..`-prefixed PATH SEGMENT (`../x`) — NOT any string starting
|
|
75
|
+
// with the two chars "..": a legitimately-contained child literally named `..foo` has rel `..foo`,
|
|
76
|
+
// which the old `rel.startsWith('..')` wrongly rejected (Issue-004).
|
|
77
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
71
78
|
throw new Error(`[agent-workflow-memory] refusing to write outside the target dir: ${dest}`);
|
|
72
79
|
}
|
|
73
80
|
if (lstatNoFollow(root)?.isSymbolicLink()) {
|
|
@@ -121,7 +128,7 @@ const printHelp = (version) => {
|
|
|
121
128
|
console.log(`agent-workflow-memory ${version}
|
|
122
129
|
|
|
123
130
|
Usage:
|
|
124
|
-
npx @sabaiway/agent-workflow-memory init [--dir <path>]
|
|
131
|
+
npx @sabaiway/agent-workflow-memory@latest init [--dir <path>]
|
|
125
132
|
npx @sabaiway/agent-workflow-memory --version
|
|
126
133
|
npx @sabaiway/agent-workflow-memory --help
|
|
127
134
|
|
|
@@ -179,7 +186,23 @@ This command only installs/updates the skill itself (in ${tildify(target)}).
|
|
|
179
186
|
To update it later, re-run: npx @sabaiway/agent-workflow-memory@latest init`);
|
|
180
187
|
};
|
|
181
188
|
|
|
182
|
-
main().
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
// Run main() only when executed directly (npx / node bin/install.mjs), never on import — so tests can
|
|
190
|
+
// import this module to unit-test its exported helpers with no side effects. Compare by REAL path:
|
|
191
|
+
// npx invokes the bin through a node_modules/.bin symlink, so process.argv[1] is that symlink while
|
|
192
|
+
// import.meta.url is the resolved real file — a raw string compare reads them as different and main()
|
|
193
|
+
// never runs. realpathSync collapses the symlink so both sides match. (Same idiom as the kit.)
|
|
194
|
+
const isDirectRun = (() => {
|
|
195
|
+
const invoked = process.argv[1];
|
|
196
|
+
if (!invoked) return false;
|
|
197
|
+
try {
|
|
198
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
203
|
+
if (isDirectRun) {
|
|
204
|
+
main().catch((err) => {
|
|
205
|
+
console.error(err);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
|
208
|
+
}
|
package/bin/install.test.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { mkdtemp, rm, mkdir, symlink, readdir } from 'node:fs/promises';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { dirname, join } from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { tmpdir, homedir } from 'node:os';
|
|
7
|
+
import { dirname, join, sep } from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
import { assertContainedRealPath, tildify } from './install.mjs';
|
|
9
10
|
|
|
10
11
|
const INSTALLER = join(dirname(fileURLToPath(import.meta.url)), 'install.mjs');
|
|
11
12
|
const runInstaller = (target) => spawnSync(process.execPath, [INSTALLER, '--dir', target], { encoding: 'utf8' });
|
|
@@ -74,3 +75,35 @@ describe('memory installer — payload + symlink-traversal hardening', () => {
|
|
|
74
75
|
assert.deepEqual(await readdir(real), [], 'nothing should be written through the symlinked target');
|
|
75
76
|
});
|
|
76
77
|
});
|
|
78
|
+
|
|
79
|
+
describe('memory installer — Issue-004 regressions (exported helpers, in-process)', () => {
|
|
80
|
+
it('assertContainedRealPath accepts a contained child literally named "..foo"', () => {
|
|
81
|
+
const root = join(tmpdir(), 'aw-mem-contain-root');
|
|
82
|
+
assert.doesNotThrow(() => assertContainedRealPath(root, join(root, '..foo')));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('assertContainedRealPath rejects a true `../` escape', () => {
|
|
86
|
+
const root = join(tmpdir(), 'aw-mem-contain-root');
|
|
87
|
+
assert.throws(() => assertContainedRealPath(root, join(root, '..', 'escape')), /outside the target dir/);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('tildify collapses only a LEADING homedir, never a mid-path occurrence', () => {
|
|
91
|
+
assert.equal(tildify(`${homedir()}${sep}skills${sep}x`), `~${sep}skills${sep}x`, 'leading home → ~');
|
|
92
|
+
const midPath = `${sep}tmp${homedir()}${sep}x`;
|
|
93
|
+
assert.equal(tildify(midPath), midPath, 'a mid-path home occurrence is left untouched');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('memory installer — module hygiene', () => {
|
|
98
|
+
it('importing install.mjs runs nothing (main() is guarded by isDirectRun)', () => {
|
|
99
|
+
const url = JSON.stringify(pathToFileURL(INSTALLER).href);
|
|
100
|
+
const res = spawnSync(
|
|
101
|
+
process.execPath,
|
|
102
|
+
['--input-type=module', '-e', `import(${url}).then(() => console.log('IMPORT_OK'));`],
|
|
103
|
+
{ encoding: 'utf8' },
|
|
104
|
+
);
|
|
105
|
+
assert.equal(res.status, 0, res.stderr);
|
|
106
|
+
assert.match(res.stdout, /IMPORT_OK/);
|
|
107
|
+
assert.doesNotMatch(res.stdout, /installed v|updated the substrate/);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Portable, cross-agent memory substrate for AI coding agents — an AGENTS.md entry point + docs/ai context with cap/archive/index enforcement, deployable standalone or as part of the agent-workflow family. The memory layer of the agent-workflow family.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|