@sabaiway/agent-workflow-memory 1.8.0 → 1.10.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/CHANGELOG.md CHANGED
@@ -4,6 +4,42 @@ 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.10.0 — Installer verb parity (the AD-034 cmp-keyed contract) + the recipe discovery step in the templates
8
+
9
+ A **feature** release (installer messaging + template text; deployment-lineage head stays `1.3.0`
10
+ — content-only, no migration):
11
+
12
+ - **`bin/install.mjs`** — the install verb is now keyed on the OBSERVED version relation, never on
13
+ mere presence (closing the false `updated the substrate to vX` on an already-current machine):
14
+ fresh/legacy-unstamped → `installed`; older → `updated the substrate to`; same →
15
+ `refreshed the already-current substrate` + the fact-only repair-on-rerun note (never a cache
16
+ accusation; conditional `@latest` hint); newer → a loud **never-downgrade refusal** (nothing
17
+ written) unless `--allow-downgrade`, which then says `downgraded the substrate to` plainly. The
18
+ installed version is read from the target SKILL.md `metadata:`-scoped `version` (decoy-proof);
19
+ an existing-but-unreadable SKILL.md **fails closed**, never silently treated as legacy. Helpers
20
+ cloned INLINE (this package references no sibling — the knows-nobody DAG).
21
+ - **`bin/install.test.mjs`** — the full engine-shape contract suite: no-op re-run wording,
22
+ downgrade refusal + `--allow-downgrade`, fail-closed unreadable SKILL.md, legacy no-stamp,
23
+ metadata-decoy version read.
24
+ - **`references/templates/agent_rules.md` §1.1** — new step 2: read `docs/ai/orchestration.json`
25
+ (the CONFIGURED orchestration recipes) BEFORE picking a task; a silent recipe downgrade is a
26
+ forbidden substitution. **`references/templates/handover.md`** — a standing `**Active recipes:**`
27
+ slot line. Both regions byte-identical with the kit template copies, path-neutral (this substrate
28
+ names no sibling skill), guarded by the kit's `template-region-parity.test.mjs` (AD-038).
29
+
30
+ ## 1.9.0 — The agent_rules lens carries the checked-vs-unchecked plan boundary
31
+
32
+ A **feature** release (template text only; scripts and installer unchanged; deployment-lineage
33
+ head stays `1.3.0` — content-only, no migration). The §2.6 lens B5 bullet mirrors the engine's §9
34
+ sharpening:
35
+
36
+ - **`references/templates/agent_rules.md` (B5)** — a plan carries only **checked syntax** (a
37
+ Step's commands, run by its own Verification against an explicit expected outcome or gate) plus
38
+ literal fixture/schema fragments a named test copies or validates; **un-run, logic-bearing
39
+ syntax** (control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL) never lives
40
+ in plan prose — a fold or draft that wants one is the trigger to write the test instead. The
41
+ line stays byte-identical to the kit template (lens-mirror guarded).
42
+
7
43
  ## 1.8.0 — ADR-cascade rotation script + the seeded per-project gate declaration
8
44
 
9
45
  A **feature** release (deployment-lineage head stays `1.3.0`; the new surfaces reach existing
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.8.0'
6
+ version: '1.10.0'
7
7
  ---
8
8
 
9
9
  # agent-workflow-memory
package/bin/install.mjs CHANGED
@@ -53,6 +53,55 @@ const readVersion = async () => {
53
53
  }
54
54
  };
55
55
 
56
+ // Never-downgrade gate helpers (D3 / AD-012, verb contract AD-034) — cloned INLINE from the
57
+ // kit/engine installers (this package references no sibling — the knows-nobody DAG), so a bare
58
+ // `npx … init` serving an OLDER cached build cannot overwrite a NEWER installed substrate.
59
+ // Dependency-free semver: parse the leading x.y.z (prerelease/build ignored). compareSemver →
60
+ // -1 | 0 | 1, or null when either side is unparseable (a legacy install predating any stamp → no gate).
61
+ const parseSemver = (str) => {
62
+ const m = typeof str === 'string' ? str.trim().match(/^(\d+)\.(\d+)\.(\d+)/) : null;
63
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
64
+ };
65
+
66
+ const compareSemver = (a, b) => {
67
+ const pa = parseSemver(a);
68
+ const pb = parseSemver(b);
69
+ if (!pa || !pb) return null;
70
+ const firstDiff = [0, 1, 2].map((i) => (pa[i] === pb[i] ? 0 : pa[i] < pb[i] ? -1 : 1)).find((c) => c !== 0);
71
+ return firstDiff ?? 0;
72
+ };
73
+
74
+ // Extract the version that is a DIRECT child of the top-level `metadata:` key — never a top-level or
75
+ // deeper-nested decoy `version:` (mirrors the manifest validator + the kit installer). Pure walk.
76
+ const metadataVersion = (frontmatter) => {
77
+ const lines = frontmatter.split(/\r?\n/);
78
+ const metaIdx = lines.findIndex((l) => /^metadata:[ \t]*$/.test(l));
79
+ if (metaIdx === -1) return null;
80
+ const after = lines.slice(metaIdx + 1);
81
+ const dedent = after.findIndex((l) => /^[^ \t]/.test(l)); // a column-0 line closes the metadata block
82
+ const block = dedent === -1 ? after : after.slice(0, dedent);
83
+ const baseIndent = block.length ? (block[0].match(/^[ \t]*/)?.[0] ?? '') : '';
84
+ const verLine = block.find((l) => l.startsWith(`${baseIndent}version:`));
85
+ return verLine?.match(/version:[ \t]*['"]?(\d+\.\d+\.\d+)['"]?/)?.[1] ?? null;
86
+ };
87
+
88
+ // The installed version is read from the target's SKILL.md frontmatter metadata.version (the
89
+ // substrate's detect.installed.file). Returns the semver string, or null when ABSENT / unstamped
90
+ // (legacy → no gate). A SKILL.md that EXISTS but cannot be read is NOT swallowed as "legacy": fail
91
+ // closed (throw) so the gate can never be silently bypassed (AGENTS.md: no silent failures).
92
+ const readInstalledVersion = async (target) => {
93
+ const skill = resolve(target, 'SKILL.md');
94
+ if (!existsSync(skill)) return null; // absent → new/legacy install, nothing to compare
95
+ const text = await readFile(skill, 'utf8').catch((err) => {
96
+ throw new Error(
97
+ `[agent-workflow-memory] cannot read the installed SKILL.md (${tildify(skill)}): ${err.message}. ` +
98
+ `Refusing to install over an unreadable substrate — fix permissions/contents or remove it, then re-run.`,
99
+ );
100
+ });
101
+ const fm = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
102
+ return fm ? metadataVersion(fm[1]) : null;
103
+ };
104
+
56
105
  // lstat without following symlinks; null when the path does not exist. Using lstat (not
57
106
  // existsSync, which FOLLOWS symlinks and so reports a *dangling* symlink as absent) is what
58
107
  // makes the guard below catch a dangling destination symlink too.
@@ -114,6 +163,7 @@ const parseArgs = (argv) => {
114
163
  return {
115
164
  help: argv.includes('--help') || argv.includes('-h'),
116
165
  version: argv.includes('--version') || argv.includes('-v'),
166
+ allowDowngrade: argv.includes('--allow-downgrade'),
117
167
  dir: dirFlag >= 0 ? argv[dirFlag + 1] : undefined,
118
168
  };
119
169
  };
@@ -128,13 +178,15 @@ const printHelp = (version) => {
128
178
  console.log(`agent-workflow-memory ${version}
129
179
 
130
180
  Usage:
131
- npx @sabaiway/agent-workflow-memory@latest init [--dir <path>]
181
+ npx @sabaiway/agent-workflow-memory@latest init [--dir <path>] [--allow-downgrade]
132
182
  npx @sabaiway/agent-workflow-memory --version
133
183
  npx @sabaiway/agent-workflow-memory --help
134
184
 
135
185
  Installs/refreshes the memory substrate at ~/.claude/skills/agent-workflow-memory
136
186
  (override with --dir <path> or AGENT_WORKFLOW_MEMORY_DIR). init is additive —
137
- it never deletes your settings and never writes through a symlink.
187
+ it never deletes your settings and never writes through a symlink. If the installed
188
+ substrate is newer than the version you ran, init refuses (no network — it compares
189
+ the version on disk) and points you at @latest; --allow-downgrade overrides that refusal.
138
190
 
139
191
  After install, invoke the skill in your agent, inside a project:
140
192
  first time in the project -> /agent-workflow-memory
@@ -169,13 +221,52 @@ const main = async () => {
169
221
  console.error(`[agent-workflow-memory] target dir is a symlink — refusing to write through it: ${tildify(target)}`);
170
222
  process.exit(1);
171
223
  }
224
+
225
+ // Stale-cache defenses (no network — version already on disk vs this runner). Read BEFORE any write
226
+ // so a refusal touches nothing. cmp is null on a legacy/unparseable install → no gate.
227
+ const installedVersion = wasPresent ? await readInstalledVersion(target) : null;
228
+ const cmp = installedVersion ? compareSemver(installedVersion, version) : null;
229
+
230
+ // Never-downgrade gate (D3 / AD-012): a bare `npx … init` can run an OLDER cached build, which
231
+ // would overwrite a NEWER installed substrate with old files. Refuse loudly (nonzero) unless
232
+ // --allow-downgrade — surfacing the cache trap instead of silently regressing (no silent failures).
233
+ if (cmp === 1 && !args.allowDowngrade) {
234
+ console.error(
235
+ `[agent-workflow-memory] refusing to downgrade: the installed substrate is v${installedVersion}, but ` +
236
+ `this runner is the OLDER v${version}.\n` +
237
+ ` This is the classic npx cache serving a stale build. To get the newest substrate, bypass the cache:\n` +
238
+ ` npx @sabaiway/agent-workflow-memory@latest init\n` +
239
+ ` (or pass --allow-downgrade to overwrite the newer install with v${version} anyway).`,
240
+ );
241
+ process.exit(1);
242
+ }
243
+
172
244
  await mkdir(target, { recursive: true });
173
245
  await Promise.all(
174
246
  PAYLOAD.map((entry) => copyRecursive(resolve(PKG_ROOT, entry), resolve(target, entry), target)),
175
247
  );
176
- console.log(
177
- `[agent-workflow-memory] ${wasPresent ? 'updated the substrate to' : 'installed'} v${version} -> ${tildify(target)}`,
178
- );
248
+ // Verb keyed on the OBSERVED version relation (cmp), never on mere presence (AD-034): null (fresh
249
+ // install, or a legacy/unstamped one whose prior version is unknowable) → "installed"; -1 a real
250
+ // update; 0 → already current (the copy still ran — see the note below); 1 is reachable only under
251
+ // --allow-downgrade (the gate above refused otherwise) and says so plainly.
252
+ const verb =
253
+ cmp === 0 ? 'refreshed the already-current substrate'
254
+ : cmp === 1 ? 'downgraded the substrate to'
255
+ : cmp === -1 ? 'updated the substrate to'
256
+ : 'installed';
257
+ console.log(`[agent-workflow-memory] ${verb} v${version} -> ${tildify(target)}`);
258
+
259
+ // Same-version re-run: state observable facts only. The copy DID run (repair-on-rerun is a feature —
260
+ // it restores locally modified/deleted files), and whether npx served a cached build is NOT
261
+ // observable here (no network), so the note never claims it; the @latest hint is conditional.
262
+ if (cmp === 0) {
263
+ console.log(
264
+ `[agent-workflow-memory] note: the substrate was already v${version} — the copy still ran, restoring ` +
265
+ `any locally modified or deleted skill file to this version's packaged contents. If you ` +
266
+ `expected a NEWER version, invoke the @latest tag explicitly:\n` +
267
+ ` npx @sabaiway/agent-workflow-memory@latest init`,
268
+ );
269
+ }
179
270
 
180
271
  console.log(`
181
272
  Next — open your agent inside a project and run the skill:
package/capability.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "name": "agent-workflow-memory",
5
5
  "kind": "memory-substrate",
6
- "version": "1.8.0",
6
+ "version": "1.10.0",
7
7
  "provides": ["context"],
8
8
  "roles": {},
9
9
  "detect": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sabaiway/agent-workflow-memory",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
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",
@@ -18,8 +18,9 @@ Every AI agent working on this project **must** adhere to this protocol before w
18
18
  ### 1.1. Start of Session
19
19
  Read in order, then confirm before starting:
20
20
  1. `docs/ai/handover.md` — where we left off.
21
- 2. `docs/ai/active_plan.md` — pick ONE task from "Immediate Priority".
22
- 3. Confirm with the user: *"I'm taking task X. Confirm?"*
21
+ 2. `docs/ai/orchestration.json` — the CONFIGURED orchestration recipes (per activity/slot). Honor them: a silent recipe downgrade is a forbidden substitution.
22
+ 3. `docs/ai/active_plan.md` pick ONE task from "Immediate Priority".
23
+ 4. Confirm with the user: *"I'm taking task X. Confirm?"*
23
24
 
24
25
  ### 1.2. During Work
25
26
  **Before any feature:** read the relevant page spec (`docs/ai/pages/<page>.md`). If behaviour changes, update the spec FIRST so docs and code never diverge.
@@ -74,7 +75,7 @@ Apply this as part of §2 before any user-facing summary:
74
75
  Apply these when authoring a plan, reviewing, folding a finding, or editing code — the layer read **before any code change**. (Full canon: the project's planning / workflow-methodology + orchestration canon.)
75
76
  - **Fold by code, not prose.** Before folding a code-touching finding into a plan or change, read the cited `file:line` and cite it — a prose fold drifts from the code and seeds the next bug.
76
77
  - **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
77
- - **No code-mechanics in the plan.** A Step still carries its exact paths + commands (the plan-structure / self-review canon); the ceiling is on *fold-bred* detail a review fold must not push fine code-mechanics (`cd`, an env default, a flag) into prose. A fold that needs a mechanic is the trigger to write the test instead.
78
+ - **No code-mechanics in the plan.** A Step still carries its exact paths + commands (the plan-structure / self-review canon) — checked syntax: the plan's own Verification runs them against an explicit expected outcome or gate; the only other syntax a plan may carry is a literal fixture/schema fragment a named test copies or validates. Un-run, logic-bearing syntax — control-flow, a regex, a glob, a grammar, an algorithm body, a mini-DSL never lives in plan prose, however plausible or shell-verified it looks: a fold or draft that wants one is the trigger to write the test instead.
78
79
  - **Test-as-spec.** Fold a code-touching finding into a red→green TEST, not a prose paragraph — the gate is the only deterministic checker; a paragraph cannot self-check.
79
80
  - **Characterize-first.** Before editing UNCOVERED code, pin its current behavior in a green test, then edit — any unintended change goes red. Never edit what has no checker; first give it one. Keep edits atomic/reversible; prefer SUBTRACTIVE folds.
80
81
  - **Fold minimally — prose has no checker.** An ephemeral, gitignored plan is prose with no executable checker; fold **minimally, in ONE place** and run a **self-consistency** read across the plan before every re-review — a fold that drifts several prose spots is what turns a 2-round review into churn.
@@ -14,6 +14,7 @@ maxLines: 80
14
14
 
15
15
  **Last session:** {{DATE}}
16
16
  **Branch:** {{BRANCH}}
17
+ **Active recipes:** not recorded yet — paste the configured-recipe line (composed from `docs/ai/orchestration.json`) and refresh it whenever the config changes.
17
18
 
18
19
  ## What was done last session
19
20