@sabaiway/agent-workflow-memory 1.0.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 +33 -0
- package/README.md +1 -1
- package/SKILL.md +30 -10
- package/bin/install.mjs +33 -10
- package/bin/install.test.mjs +36 -3
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/contracts.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ 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
|
+
|
|
24
|
+
## 1.1.0 — Hidden mode writes project-local, not global, excludes
|
|
25
|
+
|
|
26
|
+
Memory's **hidden** visibility now targets the **project-local** `.git/info/exclude` (its own footprint
|
|
27
|
+
only — `/AGENTS.md`, `/CLAUDE.md`, `/docs/ai/`, the added `/scripts/*.mjs`, `/docs/plans/`, both
|
|
28
|
+
`/.claude/settings*.json` — in canonical anchored form, idempotently), **never** the machine-global
|
|
29
|
+
`core.excludesFile`. Hiding a deployment no longer affects every other repo on the host (visibility is
|
|
30
|
+
a project setting). This is a **docs/prose** release (memory's hide was always prose-driven; no new
|
|
31
|
+
executable code); the tested superset path is the family composition root's hide tool, which absorbs
|
|
32
|
+
memory's project-local lines into one canonical managed block and adds the external footprint. The
|
|
33
|
+
deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file).
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
- `references/contracts.md` Visibility contract + `SKILL.md` step 9 retarget the hide to project-local.
|
|
37
|
+
- The upgrade flow moves an older machine-global hide to project-local — **after** the never-downgrade
|
|
38
|
+
gate and **before** the equal-head short-circuit, so even an at-head hidden deployment is migrated.
|
|
39
|
+
|
|
7
40
|
## 1.0.0
|
|
8
41
|
|
|
9
42
|
Initial standalone release. The **memory substrate** extracted into its own package as Plan 1
|
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.
|
|
6
|
+
version: '1.1.1'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
|
@@ -73,7 +73,8 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
73
73
|
`AGENTS.md`/`CLAUDE.md`, CI / linter configs; `git log --oneline -30` + `git status`; `src/`
|
|
74
74
|
2–3 levels deep; tests + linter rules. Record stack, package manager, daily commands, layers.
|
|
75
75
|
2. **Choose visibility — ASK explicitly and wait.** `visible` (committed — canonical,
|
|
76
|
-
recommended) or `hidden` (in-tree,
|
|
76
|
+
recommended) or `hidden` (in-tree, git-ignored via the **project-local** `.git/info/exclude` —
|
|
77
|
+
never the machine-global excludes). See
|
|
77
78
|
[Visibility contract](references/contracts.md#visibility-contract).
|
|
78
79
|
3. **Choose conversational language — ASK explicitly and wait.** Which language the agent
|
|
79
80
|
*talks to them* in. Offer the language they're already writing in as the default. Carry it
|
|
@@ -99,11 +100,15 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
99
100
|
follow the cap/archive/index policy manually.
|
|
100
101
|
9. **Wire / hide** per visibility (see contract). Install the pre-commit hook (Node projects):
|
|
101
102
|
`node scripts/install-git-hooks.mjs`. If it reports a pre-existing non-marker hook, stop and
|
|
102
|
-
ask the user to merge it manually.
|
|
103
|
+
ask the user to merge it manually. **Hidden** → add memory's own artifact paths (the canonical
|
|
104
|
+
anchored list in the [Visibility contract](references/contracts.md#visibility-contract)) to the
|
|
105
|
+
**project-local** `.git/info/exclude` (resolve via `git rev-parse --git-path info/exclude`),
|
|
106
|
+
append-only (never duplicate an existing line), then **verify `git status` shows them ignored**.
|
|
107
|
+
Never the machine-global excludes; never edit `package.json`.
|
|
103
108
|
10. **Stamp the deployment lineage.** Write the **deployment-lineage head** into
|
|
104
109
|
`docs/ai/.memory-version` (one semver line). The lineage head is **`1.3.0`** (the
|
|
105
110
|
`LINEAGE_HEAD` constant in `scripts/stamp-takeover.mjs`) — the shared `agent-workflow`
|
|
106
|
-
lineage, **not** this package's npm version (`1.
|
|
111
|
+
lineage, **not** this package's npm version (`1.1.0`). Use the atomic writer in
|
|
107
112
|
`scripts/stamp-takeover.mjs` (write-temp + rename).
|
|
108
113
|
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on filled-vs-TODO, then **ask before
|
|
109
114
|
committing** — never auto-commit. **Exception — delegated mode (below): skip this gate.**
|
|
@@ -135,9 +140,22 @@ Fill strategy:
|
|
|
135
140
|
`.memory-version` (and never deletes the legacy stamp). The pure state machine in
|
|
136
141
|
`scripts/stamp-takeover.mjs` decides the action per state; the Markdown migration is the
|
|
137
142
|
no-Node manual fallback. If **no** stamp exists at all, offer a conservative re-bootstrap.
|
|
138
|
-
2. **
|
|
139
|
-
|
|
140
|
-
(never downgrade or guess
|
|
143
|
+
2. **Never-downgrade gate FIRST, then the stamp-independent hidden-mode reconcile (D14).** Compare the
|
|
144
|
+
stamp to the **deployment-lineage head** (`LINEAGE_HEAD`, `1.3.0`). **Greater than the head, or
|
|
145
|
+
unparseable → STOP and report immediately, before ANY write** (never downgrade or guess, and never
|
|
146
|
+
touch `.git/info/exclude`). Otherwise (stamp **≤ head**) reconcile the hidden-mode footprint — but
|
|
147
|
+
first **infer this deployment's OWN visibility from its git state** (NOT from whether the machine-global
|
|
148
|
+
excludes list these paths — another repo on the host may have added them): if `AGENTS.md` (or
|
|
149
|
+
`docs/ai/`) is **tracked / committed** → **VISIBLE** → do nothing (never write `.git/info/exclude`);
|
|
150
|
+
if it is **untracked AND currently git-ignored** → **HIDDEN** → move memory's own footprint to the
|
|
151
|
+
**project-local** `.git/info/exclude` (the canonical anchored list from the Visibility contract,
|
|
152
|
+
append-only and idempotent — a clean re-run changes nothing), leaving the machine-global lines for the
|
|
153
|
+
user to remove (in a family upgrade the composition root's hide tool detects + reports that residual
|
|
154
|
+
block and removes it only after the user's explicit consent — never by default); if it is
|
|
155
|
+
**untracked AND not ignored** → **AMBIGUOUS** → **ASK** the user before writing. This visibility check
|
|
156
|
+
runs on **every** in-range upgrade, even at head — it is not gated by the stamp delta, but it is gated
|
|
157
|
+
**behind** the never-downgrade STOP above. **Then**, if the stamp **equals** the head → report "up to
|
|
158
|
+
date" (plus any footprint move just made) and stop.
|
|
141
159
|
3. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` context (entries newer than the stamp).
|
|
142
160
|
4. Apply `${CLAUDE_SKILL_DIR}/migrations/<version>-<slug>.md` in **semver order**, only those
|
|
143
161
|
newer than the stamp. Migrations are **idempotent**.
|
|
@@ -163,13 +181,15 @@ Fill strategy:
|
|
|
163
181
|
- **Source vs target directory.** Templates/scripts are read from the skill's own dir; the
|
|
164
182
|
**working directory is the target project** — never write substrate files back into the skill.
|
|
165
183
|
- **Stamp = lineage head, not package version.** `.memory-version` carries `1.3.0` (the shared
|
|
166
|
-
`agent-workflow` lineage), not the npm `1.
|
|
184
|
+
`agent-workflow` lineage), not the npm `1.1.0`. They are independent axes.
|
|
167
185
|
- **The methodology slot ships empty and stays the user's.** Never author methodology text into
|
|
168
186
|
it; on upgrade, preserve its content byte-for-byte. The composition root is its only writer.
|
|
169
187
|
- **The `Co-Authored-By` trailer is added by the harness, not by prose.** When attribution is
|
|
170
188
|
`off` + Claude Code, also set `"includeCoAuthoredBy": false` in `.claude/settings.json`.
|
|
171
|
-
- **Hidden mode
|
|
172
|
-
(
|
|
189
|
+
- **Hidden mode is project-local, never machine-global.** Memory's hide writes to the repo's
|
|
190
|
+
`.git/info/exclude` (one per-project ignore list), never `core.excludesFile`. `/docs/ai/`
|
|
191
|
+
subsumes `docs/ai/.memory-version` (and any kit-fallback `.workflow-version`) — list neither
|
|
192
|
+
separately. **Never touch `package.json`.** After hiding, verify `git status` shows the
|
|
173
193
|
artifacts as ignored.
|
|
174
194
|
- **`CLAUDE.md` is a symlink, not a copy.** `ln -s AGENTS.md CLAUDE.md`.
|
|
175
195
|
- **Never overwrite an existing entry point or hook.** Stop and ask to merge vs replace.
|
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.
|
|
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",
|
package/references/contracts.md
CHANGED
|
@@ -17,7 +17,7 @@ The user chooses at bootstrap whether the AI artifacts are visible in the repo o
|
|
|
17
17
|
diverge:
|
|
18
18
|
|
|
19
19
|
- **visible** — artifacts are committed. Wire the project's `package.json` scripts (`docs:check` / `docs:index` / `docs:index:check` / `docs:archive` / `docs:archive:check` / `docs:archive:issues` / `docs:archive:issues:check` / `prepare: node scripts/install-git-hooks.mjs`) and add a minimal `.gitignore` (`docs/plans/`, `.claude/settings.local.json`). This is the canonical model.
|
|
20
|
-
- **hidden** (in-tree) — same files on disk, but the repo "looks normal":
|
|
20
|
+
- **hidden** (in-tree) — same files on disk, but the repo "looks normal": add memory's OWN artifact paths to **one project-local ignore list, the repo's `.git/info/exclude`** — resolve it with `git rev-parse --git-path info/exclude` (worktree-safe) and **never** the machine-global `core.excludesFile`, which would silently affect every repo on the host (visibility is a *project* setting). Write each path **once**, in the **canonical anchored form** — `/AGENTS.md`, `/CLAUDE.md`, `/docs/ai/` (this subsumes `docs/ai/.memory-version` and any kit-fallback `docs/ai/.workflow-version` — do **not** list the stamps separately), the `/scripts/*.mjs` you added, `/docs/plans/`, `/.claude/settings.local.json`, and `/.claude/settings.json` (step-4 attribution can create it; a standalone hidden memory hides it too). Append only — never duplicate a line that is already present. **Verify `git status` shows the artifacts as ignored** afterwards. **Do not edit `package.json`** — that is a tracked change and would leak; the pre-commit hook (always untracked in `.git/hooks/`) calls the scripts via `node scripts/<x>.mjs` directly. *(In a family deploy the composition root then runs its hide tool, which absorbs these project-local lines into one canonical managed block and adds the external AI/agent footprint; memory writes no machine-global excludes at any step.)*
|
|
21
21
|
|
|
22
22
|
Not in this version: a fully-external hidden mode (artifacts relocated outside the repo tree).
|
|
23
23
|
Deferred to a later release + migration.
|