@sabaiway/agent-workflow-memory 8.1.2 → 8.1.4
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 +22 -0
- package/SKILL.md +1 -1
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/archive-decisions.mjs +8 -5
- package/references/scripts/archive-decisions.test.mjs +41 -0
- package/references/templates/SPEC_TEMPLATE.md +2 -1
- package/references/templates/agent_rules.md +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ 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 `4.0.0`).
|
|
6
6
|
|
|
7
|
+
## 8.1.4 — the templates carry the four-session story flow (AD-170)
|
|
8
|
+
|
|
9
|
+
`references/templates/agent_rules.md`: §2.6 is the engine 5.12.0 lens render (the Finding scope, Spec-first, Fold
|
|
10
|
+
minimally and Heavy review bullets name the spec review) and §2.7 Story sessions states four sessions (spec and plan,
|
|
11
|
+
tests, code, then diff review, release and record) and gives a bridge executor only work that writes new tests or
|
|
12
|
+
code; both regions stay byte-identical to the kit's copies; §2.7's one source is the kit's template, §2.6's the
|
|
13
|
+
engine's `references/agent-rules-lens.md`.
|
|
14
|
+
`references/templates/SPEC_TEMPLATE.md`: a draft spec exists at the spec review of the session that writes its plan.
|
|
15
|
+
No script changed. PATCH: only a new bootstrap's text changes.
|
|
16
|
+
|
|
17
|
+
## 8.1.3 — the ADR navigator stamps a cap it honours (AD-167)
|
|
18
|
+
|
|
19
|
+
`references/scripts/archive-decisions.mjs`: `--write-navigator` stamped `maxLines: 200` into `docs/ai/adr/log.md`
|
|
20
|
+
whatever its length, and the navigator grows by one row per governing ADR, so past 162 governing ADRs the docs gate
|
|
21
|
+
refused the generator's own output. The stamp is now 200 while the navigator fits and its own line count past it,
|
|
22
|
+
counted the way `check-docs-size.mjs` counts. A navigator of 200 lines or fewer is written byte for byte as before, and
|
|
23
|
+
`--check` still compares the whole file with a regeneration, so a hand-edited cap reads as stale. PATCH: a navigator
|
|
24
|
+
of 200 lines or fewer is written as before. An upgrade keeps a deployed `scripts/archive-decisions.mjs` byte for byte,
|
|
25
|
+
so a project already on the ADR store takes the fix by copying `archive-decisions.mjs` and `archive-decisions.test.mjs`
|
|
26
|
+
from this package's `references/scripts/` into its `scripts/`; a project whose navigator is past 200 lines then runs
|
|
27
|
+
`node scripts/archive-decisions.mjs --write-navigator` once.
|
|
28
|
+
|
|
7
29
|
## 8.1.2 — the templates carry the session close for the user (AD-159)
|
|
8
30
|
|
|
9
31
|
`references/templates/agent_rules.md`: §2.5 Communication gains the last bullet `Two blocks for the user`, and §2.7
|
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: '8.1.
|
|
6
|
+
version: '8.1.4'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-memory",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.4",
|
|
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",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// One-file-per-ADR store for docs/ai/decisions.md (ADRs) — the durable replacement for the retired
|
|
3
3
|
// 3-tier cascade (HOT → WARM archive → a single COLD monolith whose cap was raised release after
|
|
4
4
|
// release). Every ADR beyond the HOT window becomes its OWN immutable record so no artifact is ever
|
|
5
|
-
// O(n) and no cap is
|
|
5
|
+
// O(n) and no cap is raised by hand again (the navigator stamps its own count past 200).
|
|
6
6
|
//
|
|
7
7
|
// HOT (docs/ai/decisions.md) — the active ADR window (newest at the bottom), self-bounding
|
|
8
8
|
// under its own frontmatter maxLines.
|
|
@@ -95,7 +95,8 @@ export const ADR_DIR_REL = 'docs/ai/adr';
|
|
|
95
95
|
export const NAV_REL = 'docs/ai/adr/log.md';
|
|
96
96
|
|
|
97
97
|
// A per-record cap (generous vs the largest real ADR ~74 lines; a body over it is a genuine smell) and
|
|
98
|
-
// the navigator
|
|
98
|
+
// the navigator's FLOOR: the navigator stamps 200 while it fits and its own line count past it, since
|
|
99
|
+
// nothing rotates a governing row out (docs/ai/specs/memory/adr-navigator.md).
|
|
99
100
|
export const RECORD_CAP = 400;
|
|
100
101
|
export const NAV_MAXLINES = 200;
|
|
101
102
|
// How many most-recent ids the navigator's recent window carries (status-annotated, incl. supersessions).
|
|
@@ -701,8 +702,8 @@ export const computeGoverningIds = (entries) => {
|
|
|
701
702
|
return new Set(entries.filter((e) => e.status === IN_FORCE_STATUS && !superseded.has(e.id)).map((e) => e.id));
|
|
702
703
|
};
|
|
703
704
|
|
|
704
|
-
const NAV_FRONTMATTER = (today) =>
|
|
705
|
-
`---\ntype: reference\nlastUpdated: ${today}\nscope: permanent\nstaleAfter: never\nowner: none\nmaxLines: ${
|
|
705
|
+
const NAV_FRONTMATTER = (today, cap) =>
|
|
706
|
+
`---\ntype: reference\nlastUpdated: ${today}\nscope: permanent\nstaleAfter: never\nowner: none\nmaxLines: ${cap}\n---\n`;
|
|
706
707
|
|
|
707
708
|
// Build docs/ai/adr/log.md from the WHOLE corpus (HOT entries ∪ adr/ records). Governing heads
|
|
708
709
|
// (accepted ∧ not-superseded) sorted newest-first; superseded ADRs drop OUT (still reachable by
|
|
@@ -744,7 +745,9 @@ export const buildNavigator = (corpus, today) => {
|
|
|
744
745
|
'',
|
|
745
746
|
...recent,
|
|
746
747
|
].join('\n');
|
|
747
|
-
|
|
748
|
+
// The cap sits on one line whatever its value, so the count with the floor is the final count.
|
|
749
|
+
const count = lineCountOf(`${NAV_FRONTMATTER(today, NAV_MAXLINES)}\n${body}\n`);
|
|
750
|
+
return `${NAV_FRONTMATTER(today, Math.max(NAV_MAXLINES, count))}\n${body}\n`;
|
|
748
751
|
};
|
|
749
752
|
|
|
750
753
|
// ── snapshot (durable, pre-delete — Decision 5) ─────────────────────────────────────────
|
|
@@ -800,6 +800,47 @@ describe('1.5 navigator — governing heads (computed), superseded drop out but
|
|
|
800
800
|
});
|
|
801
801
|
});
|
|
802
802
|
|
|
803
|
+
// ── the navigator's stamped cap (docs/ai/specs/memory/adr-navigator.md) ─────────────────
|
|
804
|
+
//
|
|
805
|
+
// The frame is 38 lines (15 ADRs or more), so 162 governing heads make exactly 200, 163 make 201 and 170 make 208.
|
|
806
|
+
|
|
807
|
+
const capOf = (text) => Number(text.match(/^maxLines: (\d+)$/m)[1]);
|
|
808
|
+
const corpusOf = (n) =>
|
|
809
|
+
Array.from({ length: n }, (_, i) => {
|
|
810
|
+
const id = String(i + 1).padStart(3, '0');
|
|
811
|
+
return { id, idNum: i + 1, title: `T${id}`, status: 'accepted', supersedes: [], supersededBy: [], fileName: `AD-${id}-t.md` };
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
describe('the navigator stamps a cap it honours', () => {
|
|
815
|
+
it('200 while it fits, its own line count past it', () => {
|
|
816
|
+
// spec:adr-navigator/S1
|
|
817
|
+
for (const heads of [2, 162]) {
|
|
818
|
+
const nav = buildNavigator(corpusOf(heads), '2026-07-09');
|
|
819
|
+
assert.equal(capOf(nav), 200, `${heads} heads keep the 200 floor`);
|
|
820
|
+
}
|
|
821
|
+
assert.equal(lineCountOf(buildNavigator(corpusOf(162), '2026-07-09')), 200);
|
|
822
|
+
// 165 entries, two superseded: 163 heads, 201 lines — a stamp from the entry count would read 203.
|
|
823
|
+
const corpus = corpusOf(165).map((e, i) => (i < 2 ? { ...e, supersededBy: ['003'] } : e));
|
|
824
|
+
const big = buildNavigator(corpus, '2026-07-09');
|
|
825
|
+
assert.equal(lineCountOf(big), 201);
|
|
826
|
+
assert.equal(capOf(big), 201, 'past the floor the stamp is the file\'s own count');
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it('a written navigator past 200 lines fits its cap, --check is green, a hand-edited cap is stale', () => {
|
|
830
|
+
// spec:adr-navigator/S2
|
|
831
|
+
const root = makeRoot();
|
|
832
|
+
seedMigrated(root, { hotIds: ['901'], storeIds: corpusOf(169).map((e) => e.id) });
|
|
833
|
+
const nav = readFileSync(join(root, NAV_REL), 'utf8');
|
|
834
|
+
assert.ok(lineCountOf(nav) > 200, 'the fixture is past the floor');
|
|
835
|
+
assert.ok(lineCountOf(nav) <= capOf(nav), 'the docs gate accepts the written navigator');
|
|
836
|
+
assert.equal(run(['--check', '--today=2026-07-09'], root).code, 0);
|
|
837
|
+
writeFileSync(join(root, NAV_REL), nav.replace(/^maxLines: \d+$/m, 'maxLines: 999'));
|
|
838
|
+
const stale = run(['--check', '--today=2026-07-09'], root);
|
|
839
|
+
assert.equal(stale.code, 1);
|
|
840
|
+
assert.match(stale.errText, /log\.md is stale/);
|
|
841
|
+
});
|
|
842
|
+
});
|
|
843
|
+
|
|
803
844
|
// ── item (h) degrade branches on the REAL default regenerator (no injection) ───────────
|
|
804
845
|
|
|
805
846
|
describe('defaultRegenerateIndex — loud-degrade branches', () => {
|
|
@@ -21,7 +21,8 @@ revision: 1
|
|
|
21
21
|
## Contract
|
|
22
22
|
|
|
23
23
|
What the feature accepts and returns, stated as invariants a test can pin. A `draft` spec is authored
|
|
24
|
-
|
|
24
|
+
in the session that writes the plan landing the feature and exists at its spec review; approval of
|
|
25
|
+
that plan confirms the
|
|
25
26
|
contract (no separate stop). It becomes `live` on the plan's landing row; `revision` increments by one
|
|
26
27
|
per live contract change; `retired` on the removal row — never backwards.
|
|
27
28
|
|
|
@@ -80,15 +80,15 @@ Apply this as part of §2 before any user-facing summary:
|
|
|
80
80
|
### 2.6. Planning, review & process-fidelity invariants
|
|
81
81
|
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. This section is rendered from that canon and refreshed on upgrade; a custom edit is preserved verbatim, but flagged.)
|
|
82
82
|
- **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.
|
|
83
|
-
- **Finding scope (plan-execution) — name the invariant BEFORE the edit.** During EXECUTION only — a plan under authoring
|
|
83
|
+
- **Finding scope (plan-execution) — name the invariant BEFORE the edit.** During EXECUTION only — a spec and its plan under authoring have no shipped behaviour to call a live defect in, so the spec review carries none of this. Every finding names the invariant its fix would enforce, and where that invariant already lives decides the disposition: already an acceptance criterion of the phase → **fold here**; it would have to be ADDED → ship the **narrow fix** for the found site (red first, then green) and queue ONLY the generalization — a deferral row carries the invariant, the origin `file:line`, the narrow fix, its proof and a residual exposure declared NOT live; no correct narrow fix → **blocking**: the phase does not close, and it is **never queued**. Two bars declared before each round: a finding counts only if it changes a **WRITE/REMOVE decision** or is a false statement in shipped text; a repeat finding in one subarea **routes to SUBTRACTION**, not a fourth patch.
|
|
84
84
|
- **Right altitude.** Pin intent + invariants + acceptance criteria (named tests); leave fine code-mechanics to Execute, where prose cannot diverge from reality.
|
|
85
|
-
- **Spec-first.** A plan names its GOVERNING spec(s) — zero, one or many, one per touched spec-covered slice (the feature spec under `docs/ai/specs/`; page-only coverage governs as an ADOPTION SHIM, with Out of scope + Revision stated inline in the plan). A ZERO names the adoption state it relies on — `not adopted` (no store) or `adopting` (a store with no live contract), either with a recorded decline, or `nothing spec-covered touched` (a store with live contracts) — a bare zero is never a licence; the store's own state is what `status` and the upgrade advisor report. Each cited spec's Out of scope bounds that slice's work and the plan's non-goals restate it per slice — no global union; a cross-spec conflict is resolved by a spec revision BEFORE approval, never by silent precedence. A NEW feature's draft spec exists AT
|
|
85
|
+
- **Spec-first.** A plan names its GOVERNING spec(s) — zero, one or many, one per touched spec-covered slice (the feature spec under `docs/ai/specs/`; page-only coverage governs as an ADOPTION SHIM, with Out of scope + Revision stated inline in the plan). A ZERO names the adoption state it relies on — `not adopted` (no store) or `adopting` (a store with no live contract), either with a recorded decline, or `nothing spec-covered touched` (a store with live contracts) — a bare zero is never a licence; the store's own state is what `status` and the upgrade advisor report. Each cited spec's Out of scope bounds that slice's work and the plan's non-goals restate it per slice — no global union; a cross-spec conflict is resolved by a spec revision BEFORE approval, never by silent precedence. A NEW feature's draft spec exists AT the spec review (a `create` row); a change to a governed contract rides the plan as its proposed revision (a `modify` row); approval confirms plan and contract atomically, and the revision lands with the code. Scenario bindings are per scenario: a new scenario is `unbound` until its test lands in the same plan, and a status never regresses for an extension.
|
|
86
86
|
- **No code-mechanics in the plan.** A ledger row carries its path and anchor, and Verification carries the exact commands (the plan-shape 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.
|
|
87
87
|
- **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.
|
|
88
88
|
- **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.
|
|
89
89
|
- **State table BEFORE the guard — enumerate by PROOF, never by exclusion.** The subtraction rule above fires on a repeat finding, which is a LATE signal: by then the review has paid for each miss. The EARLY signal is structural — a decision whose input has **several independent state dimensions** (is it tracked? do the bytes still match the source? does the neighbouring file exist?). Write the table first, admit the write with **ONE conjunction of proven facts**, and funnel every other cell into a single refusal; the table is then the table-driven test. An exclusion list (`if (bad1) return; if (bad2) return;`) fails **OPEN** on the first state nobody enumerated — and "unreadable" is a state, distinct from "absent". A reviewer cannot save you here: it judges the patch in front of it and can only name the NEXT missing state, one round at a time.
|
|
90
|
-
- **Fold minimally — prose has no checker.**
|
|
91
|
-
- **Heavy review at the diff.**
|
|
90
|
+
- **Fold minimally — prose has no checker.** A spec under the spec review, like the ephemeral plan beside it, is prose with no executable checker; fold **minimally, in ONE place** and run a **self-consistency** read across the spec before every re-review — a fold that drifts several prose spots is what turns a 2-round review into churn.
|
|
91
|
+
- **Heavy review at the diff.** The spec review settles the contract and reads the plan's ledger in the same rounds (≤2 rounds, stop at the pre-existing→fold-induced crossover); the plan has no rounds of its own and is checked by its shape gate, that reading and the approval; the exhaustive review runs against the staged diff of real compiling code + the full suite, where a regression fails a gate immediately. **Backend divergence** (one backend grounded-ships while another keeps revising mechanics) IS that crossover — resolve at altitude, don't exhaust the strictest backend; route an all-mechanics/CI or prose-only artifact to a **thin plan + diff-review**.
|
|
92
92
|
- **Convergence bar.** A review loop is CLEAN only when one round returns **0 blockers + 0 majors** from EVERY backend the recipe names (nits + a ship verdict is the stop). Folding ≠ convergence — re-review after folding.
|
|
93
93
|
- **Per-round emission.** Every review round emits **{round N · finding-origin tally · per-backend verdict}** so the crossover is a computed, visible signal, not a remembered rule.
|
|
94
94
|
- **Recipe fidelity.** Council runs every backend the recipe names, **every round**; silently dropping a ready backend for quota/convenience is a forbidden downgrade — an unavailable backend is a LOUD, stated degrade, never a quiet drop.
|
|
@@ -96,7 +96,7 @@ Apply these when authoring a plan, reviewing, folding a finding, or editing code
|
|
|
96
96
|
- **Cost lanes.** Route every step to the **cheapest adequate executor** — L0 deterministic script (the batched gate matrix over `gates.json`, the rotation `--check`s) · L1 cheap subagent (extraction/drafting only; the orchestrator verifies) · L2 subscription bridge · L3 frontier judgment. A step with **no named guardrail does not move down** a lane, and the **red lines never move down** (council review models · real code · ADR/handover/changelog-entry wording · persuasive copy · go/no-go · the approval asks). Own-error repair: salvage recorded state first (L0/L1, batched), never frontier re-derivation. **Prompt economy:** read-only fan-out (research/sweeps/extraction) runs ONLY on restricted-tool vehicles — a full-tool subagent for read-only work is a forbidden lane downgrade (invisible prompt-flood + blast radius), and a subagent is never told to shell out for facts obtainable read-only; the orchestrator's own shell form is ONE plain pipeline per call (a `;`/`&&` chain or env-prefixed invocation never matches a prefix allow rule); a fan-out launcher that gates per call yields to the agent-spawn lane — capability-gated: without restricted-tool vehicles (generic full-tool spawning does not count), read-only research stays in the orchestrator's own context, never a vehicle mandate a host cannot satisfy. Judgment, code, synthesis stay at the frontier lane (a task that genuinely runs/writes keeps a full-tool subagent); honest limit: no deterministic gate classifies a dispatch — canon at the point of use + placed vehicles + the retro loop. **Writer economy:** a stage's repeated WRITER commands batch — evidence declarations ride consecutive plain invocations of ONE allow-listed tool, other stage writers combine via one launcher per stage; never an unbatched writer scatter (each gated write is its own prompt).
|
|
97
97
|
|
|
98
98
|
### 2.7. Story sessions
|
|
99
|
-
A story — one row of an epic's ledger, carried by one plan — runs as
|
|
99
|
+
A story — one row of an epic's ledger, carried by one plan — runs as four sessions, each step in them giving one checked result: **spec and plan** (every contract the story creates or revises and the plan's ledger beside them, checked by the spec gate, the plan-shape gate and one review of at most two rounds that reads both, then the plan's approval), **tests** (only tests, each new one failing for the reason its scenario or acceptance bullet states, or on the absence of a name the plan adds, imported inside the test; no code and no prototype), **code** (code until the tests and the pre-review gates pass; a wrong test is changed only back to what its scenario states), and **diff review, release and record** (every committable output, tracked records included, staged first, the review of the staged diff in at most two rounds, the final gates and the release where the story ships one, then the plan's Phase: Cleanup). Tests and code never share a session. A storyless plan runs the same four. **Exception — a split:** moving code and its existing cases into modules, with no new logic and no new case, is one session: a short spec (the Module list), the split, the diff review, the release, Cleanup. A bridge executor gets a task only when it writes new tests or code, from one prompt of the orchestrator; a file the orchestrator already wrote is laid down by the orchestrator, never relayed through a bridge.
|
|
100
100
|
|
|
101
101
|
---
|
|
102
102
|
|