@sabaiway/agent-workflow-memory 1.0.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 +40 -0
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/SKILL.md +201 -0
- package/bin/install.mjs +185 -0
- package/bin/install.test.mjs +76 -0
- package/capability.json +27 -0
- package/migrations/1.1.0-communication-language.md +51 -0
- package/migrations/1.2.0-agent-attribution.md +57 -0
- package/migrations/README.md +46 -0
- package/migrations/legacy-stamp-takeover.md +72 -0
- package/package.json +53 -0
- package/references/contracts.md +62 -0
- package/references/scripts/_expect-shim.mjs +41 -0
- package/references/scripts/archive-changelog.mjs +438 -0
- package/references/scripts/archive-changelog.test.mjs +212 -0
- package/references/scripts/archive-issues.mjs +179 -0
- package/references/scripts/archive-issues.test.mjs +95 -0
- package/references/scripts/check-docs-size.mjs +353 -0
- package/references/scripts/check-docs-size.test.mjs +180 -0
- package/references/scripts/install-git-hooks.mjs +83 -0
- package/references/templates/AGENTS.md +96 -0
- package/references/templates/active_plan.md +31 -0
- package/references/templates/agent_rules.md +85 -0
- package/references/templates/architecture.md +49 -0
- package/references/templates/changelog.md +24 -0
- package/references/templates/current_state.md +36 -0
- package/references/templates/decisions.md +44 -0
- package/references/templates/env_commands.md +41 -0
- package/references/templates/handover.md +37 -0
- package/references/templates/known_issues.md +33 -0
- package/references/templates/pages/PAGE_TEMPLATE.md +53 -0
- package/references/templates/pages/index.md +23 -0
- package/references/templates/pages/shared-patterns.md +30 -0
- package/references/templates/tech_reference.md +34 -0
- package/references/templates/technical_specification.md +37 -0
- package/scripts/package-content.test.mjs +78 -0
- package/scripts/stamp-takeover.mjs +181 -0
- package/scripts/stamp-takeover.test.mjs +166 -0
- package/scripts/standalone-bootstrap.test.mjs +111 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog — @sabaiway/agent-workflow-memory
|
|
2
|
+
|
|
3
|
+
All notable changes to the memory substrate. Versions are this **package's** npm versions;
|
|
4
|
+
they are distinct from the **deployment-lineage** stamp written into a project's
|
|
5
|
+
`docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `1.3.0`).
|
|
6
|
+
|
|
7
|
+
## 1.0.0
|
|
8
|
+
|
|
9
|
+
Initial standalone release. The **memory substrate** extracted into its own package as Plan 1
|
|
10
|
+
of the agent-workflow family refactor (deployment-lineage head `1.3.0`). Additive: the family
|
|
11
|
+
composition root keeps its own bundled copy as a fallback, so nothing breaks for existing users.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Standalone npm package + `bin/install.mjs` installer targeting
|
|
15
|
+
`~/.claude/skills/agent-workflow-memory` (`AGENT_WORKFLOW_MEMORY_DIR`), with a
|
|
16
|
+
symlink-traversal guard — the installer never writes *through* a destination symlink
|
|
17
|
+
(root / intermediate / leaf).
|
|
18
|
+
- `capability.json` — the `agent-workflow` family manifest (`kind: memory-substrate`,
|
|
19
|
+
`provides: ["context"]`). The package ships **no** family-wide schema/validator tooling
|
|
20
|
+
(owned by the composition root) — it knows nobody.
|
|
21
|
+
- An **empty** delimited methodology slot in `templates/AGENTS.md`
|
|
22
|
+
(`<!-- workflow:methodology:start -->` / `:end`); the substrate only ever ships it empty
|
|
23
|
+
and preserves any filled content on upgrade. The composition root injects the methodology.
|
|
24
|
+
- `scripts/stamp-takeover.mjs` — a pure, unit-tested state machine for the legacy
|
|
25
|
+
`.workflow-version` → `.memory-version` lineage takeover, with **atomic** (write-temp +
|
|
26
|
+
rename) stamp writes, plus `migrations/legacy-stamp-takeover.md` (the no-Node manual
|
|
27
|
+
fallback).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- The deployment stamp is `docs/ai/.memory-version` (the legacy `.workflow-version` is never
|
|
31
|
+
deleted; both track the shared lineage). Hidden-mode ignore lists now include
|
|
32
|
+
`.memory-version`.
|
|
33
|
+
- `references/templates/agent_rules.md` no longer embeds the planning-methodology vocabulary —
|
|
34
|
+
it points at the methodology owner. `contracts.md` attribution wiring is de-attributed from
|
|
35
|
+
"the kit" to "the bootstrap".
|
|
36
|
+
|
|
37
|
+
### Carried over from the original substrate (deployment-lineage 1.3.0)
|
|
38
|
+
- `docs/ai/` templates, the Node enforcement scripts (caps + index freshness + 3-tier archive
|
|
39
|
+
+ hook installer) and their tests, the three setup contracts, and migrations `1.1.0`
|
|
40
|
+
(conversational language) and `1.2.0` (agent attribution).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sabaiway
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @sabaiway/agent-workflow-memory
|
|
2
|
+
|
|
3
|
+
**Portable, cross-agent memory substrate for AI coding agents.** Deploys an `AGENTS.md`
|
|
4
|
+
entry point (+ a `CLAUDE.md` symlink for Claude Code) and a structured `docs/ai/` context
|
|
5
|
+
store with cap / archive / index-freshness enforcement into any repo. After it runs, any
|
|
6
|
+
future agent — including a fresh session of yourself — can reconstruct project context in
|
|
7
|
+
~60 seconds, find the current task, and avoid repeating past mistakes.
|
|
8
|
+
|
|
9
|
+
This is the **memory layer** of the `agent-workflow` family.
|
|
10
|
+
It owns the substrate only — `docs/ai/`, the entry point, caps / 3-tier archive / index gate,
|
|
11
|
+
the Node enforcement scripts, the pre-commit hook, the templates, and the three setup
|
|
12
|
+
contracts (visibility / conversational language / agent attribution). It deliberately **knows
|
|
13
|
+
nobody else** in the `agent-workflow` family: the **workflow methodology** (plan → execute →
|
|
14
|
+
review vocabulary, lifecycle, `queue.md`, mandatory Cleanup) lives elsewhere and is injected
|
|
15
|
+
into a delimited slot in `AGENTS.md` by the family composition root, not by this package.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @sabaiway/agent-workflow-memory init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Installs/refreshes the skill at `~/.claude/skills/agent-workflow-memory` (override with
|
|
24
|
+
`--dir <path>` or `AGENT_WORKFLOW_MEMORY_DIR`). `init` is additive — it never deletes your
|
|
25
|
+
settings and never writes through a symlink. Re-running updates the skill's own files; that is
|
|
26
|
+
how you upgrade the *skill*. Migrating a *project's* deployment is a separate in-agent step
|
|
27
|
+
(below).
|
|
28
|
+
|
|
29
|
+
## Use
|
|
30
|
+
|
|
31
|
+
Open your agent inside a project and run the skill:
|
|
32
|
+
|
|
33
|
+
- **`/agent-workflow-memory`** — bootstrap a new or empty project. Asks the three setup
|
|
34
|
+
questions (visibility, conversational language, agent attribution), then writes `docs/ai/`,
|
|
35
|
+
`AGENTS.md`, the enforcement scripts + pre-commit hook, and stamps
|
|
36
|
+
`docs/ai/.memory-version`. The methodology slot in `AGENTS.md` is left **empty**.
|
|
37
|
+
- **`/agent-workflow-memory upgrade`** — migrate an existing deployment to the skill's current
|
|
38
|
+
version. Reads `docs/ai/.memory-version`, runs only newer migrations in semver order, and
|
|
39
|
+
**preserves** anything already in the methodology slot (extract-and-reinsert — it never
|
|
40
|
+
regenerates `AGENTS.md` wholesale).
|
|
41
|
+
|
|
42
|
+
## Stamps & lineage
|
|
43
|
+
|
|
44
|
+
Two independent axes: this package's **npm version** vs the **deployment-lineage** stamp the
|
|
45
|
+
substrate writes into a project. They are not the same number. `.memory-version` tracks the
|
|
46
|
+
shared `agent-workflow` deployment lineage (head `1.3.0` today), **not** this package's
|
|
47
|
+
version. A project bootstrapped by the kit's fallback carries a `.workflow-version`; when this
|
|
48
|
+
substrate is later installed, `migrations/legacy-stamp-takeover.md` copies that value verbatim
|
|
49
|
+
into `.memory-version` and never deletes the legacy stamp. See `migrations/README.md`.
|
|
50
|
+
|
|
51
|
+
## What this package ships
|
|
52
|
+
|
|
53
|
+
`SKILL.md` (procedure + ownership table), `references/` (templates + the deployable Node
|
|
54
|
+
enforcement scripts + the setup contracts), `scripts/stamp-takeover.mjs` (the upgrade-time
|
|
55
|
+
lineage state machine + tests), `migrations/`, `capability.json` (the family manifest), and
|
|
56
|
+
this installer. It ships **no** family-wide tooling (the schema/validator are owned by the
|
|
57
|
+
composition root) — preserving "knows nobody".
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT — see [LICENSE](./LICENSE).
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-workflow-memory
|
|
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
|
+
disable-model-invocation: true
|
|
5
|
+
metadata:
|
|
6
|
+
version: '1.0.0'
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# agent-workflow-memory
|
|
10
|
+
|
|
11
|
+
Deploys a **portable AI-agent memory substrate** into a project, and upgrades it as the
|
|
12
|
+
substrate evolves. After it runs, any future agent (including a fresh session of yourself) can
|
|
13
|
+
reconstruct project context in ~60 seconds, find the current task, and avoid repeating past
|
|
14
|
+
mistakes.
|
|
15
|
+
|
|
16
|
+
The substrate is **stack-agnostic memory** — `docs/ai/` structure, entry-point doc, session
|
|
17
|
+
protocols, frontmatter caps, 3-tier archive, index-freshness gate. Enforcement ships as **Node
|
|
18
|
+
`.mjs` scripts** (the reference implementation; non-Node stacks follow the same policy
|
|
19
|
+
manually).
|
|
20
|
+
|
|
21
|
+
This skill is the **memory layer** of the `agent-workflow` family. It **knows nobody else** in
|
|
22
|
+
the family. In particular it does **not** own the **workflow methodology** (plan → execute →
|
|
23
|
+
review vocabulary, lifecycle, `docs/plans/queue.md`, mandatory Cleanup, plan-then-execute) —
|
|
24
|
+
that is injected into a delimited slot in `AGENTS.md` by the family **composition root**, never by
|
|
25
|
+
this skill. (This substrate does not name or depend on any specific sibling — it only honours the
|
|
26
|
+
slot contract.) This skill only ever ships the slot **empty** and **preserves** whatever is already
|
|
27
|
+
in it on upgrade.
|
|
28
|
+
|
|
29
|
+
The substrate **artifacts** (this skill, the templates, the deployed `docs/ai/` files) stay in
|
|
30
|
+
their **source language** — for cross-agent and cross-team portability. That is separate from the
|
|
31
|
+
**conversational language** chosen at bootstrap (which governs dialogue only, never file
|
|
32
|
+
contents).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Ownership table
|
|
37
|
+
|
|
38
|
+
What this substrate owns vs what it only points at. The methodology pointer is the empty slot
|
|
39
|
+
the composition root fills — never author methodology text here.
|
|
40
|
+
|
|
41
|
+
| Concern | Owner | In the deployed `AGENTS.md` |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Entry point, Memory Map, session protocols (Start / During / Complete) | **memory** (this skill) | authored from templates |
|
|
44
|
+
| `docs/ai/` files, frontmatter caps, 3-tier archive, index-freshness gate | **memory** | `docs/ai/*` + scripts + hook |
|
|
45
|
+
| Visibility / conversational-language / agent-attribution contracts | **memory** | the three `AGENTS.md` blocks |
|
|
46
|
+
| Deployment-lineage stamp | **memory** | `docs/ai/.memory-version` |
|
|
47
|
+
| Plan→Phase→Step vocabulary, lifecycle, `queue.md`, mandatory Cleanup | **methodology** (not this skill) | the empty `workflow:methodology` slot — filled by the composition root |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Two modes
|
|
52
|
+
|
|
53
|
+
Pick the mode from the user's invocation. Auto-detect an existing `docs/ai/` to guard against
|
|
54
|
+
bootstrapping over a live system, but the user makes the final call.
|
|
55
|
+
|
|
56
|
+
- **`/agent-workflow-memory`** (default) — bootstrap a new or empty project. If `docs/ai/`
|
|
57
|
+
already exists, stop and ask whether they meant `upgrade`.
|
|
58
|
+
- **`/agent-workflow-memory upgrade`** — upgrade an existing deployment.
|
|
59
|
+
|
|
60
|
+
### Mode: bootstrap
|
|
61
|
+
|
|
62
|
+
> Bundled sources below (templates, scripts) live in **this skill's own directory** —
|
|
63
|
+
> `${CLAUDE_SKILL_DIR}/` in Claude Code, or the folder containing this `SKILL.md` elsewhere.
|
|
64
|
+
> Use that as the copy/read source; the working directory is the **target project**.
|
|
65
|
+
|
|
66
|
+
> The three setup questions (steps 2–4) are decisions only the user can make and are hard to
|
|
67
|
+
> reverse after a commit. Ask each as a **structured multiple-choice prompt where supported**
|
|
68
|
+
> (`AskUserQuestion` in Claude Code, recommended option first), otherwise in prose — and
|
|
69
|
+
> **wait for the answer before writing anything**.
|
|
70
|
+
|
|
71
|
+
1. **Recon (read-only).** Before writing anything: `package.json` / `pyproject.toml` / `go.mod`
|
|
72
|
+
/ `Cargo.toml` → stack, package manager, scripts; `ls -la` root → README, existing
|
|
73
|
+
`AGENTS.md`/`CLAUDE.md`, CI / linter configs; `git log --oneline -30` + `git status`; `src/`
|
|
74
|
+
2–3 levels deep; tests + linter rules. Record stack, package manager, daily commands, layers.
|
|
75
|
+
2. **Choose visibility — ASK explicitly and wait.** `visible` (committed — canonical,
|
|
76
|
+
recommended) or `hidden` (in-tree, hidden via `~/.gitignore_global`). See
|
|
77
|
+
[Visibility contract](references/contracts.md#visibility-contract).
|
|
78
|
+
3. **Choose conversational language — ASK explicitly and wait.** Which language the agent
|
|
79
|
+
*talks to them* in. Offer the language they're already writing in as the default. Carry it
|
|
80
|
+
into the `{{COMM_LANGUAGE}}` slot of the *Communication language* block (step 5). See
|
|
81
|
+
[Communication contract](references/contracts.md#communication-contract). Dialogue only —
|
|
82
|
+
never the files.
|
|
83
|
+
4. **Choose agent attribution — ASK explicitly and wait.** May the agent attribute work to
|
|
84
|
+
itself / AI — `Co-Authored-By` trailers, "Generated with …" footers, AI/agent/model mentions?
|
|
85
|
+
**Default `off`.** Carry it into `{{AGENT_ATTRIBUTION}}` (step 5). **If `off` and the project
|
|
86
|
+
uses Claude Code**, also set `"includeCoAuthoredBy": false` in the project's
|
|
87
|
+
`.claude/settings.json` (create it if absent). See
|
|
88
|
+
[Attribution contract](references/contracts.md#attribution-contract).
|
|
89
|
+
5. **Entry-point doc.** If `AGENTS.md` / `CLAUDE.md` already exist (recon), do **not**
|
|
90
|
+
overwrite — show the user and ask whether to merge or replace. Otherwise create `AGENTS.md`
|
|
91
|
+
from `${CLAUDE_SKILL_DIR}/references/templates/AGENTS.md` and symlink `CLAUDE.md -> AGENTS.md`
|
|
92
|
+
(`ln -s AGENTS.md CLAUDE.md`). **Leave the `workflow:methodology` slot exactly as shipped —
|
|
93
|
+
empty.** Filling it is the composition root's job.
|
|
94
|
+
6. **Deploy `docs/ai/`.** Create the files + `pages/` from
|
|
95
|
+
`${CLAUDE_SKILL_DIR}/references/templates/`. Keep each file's frontmatter.
|
|
96
|
+
7. **Fill templates** per the table below.
|
|
97
|
+
8. **Install enforcement (Node projects).** Copy `${CLAUDE_SKILL_DIR}/references/scripts/*.mjs`
|
|
98
|
+
(+ `*.test.mjs`) into the project's `scripts/`. **No Node runtime** → skip this + the hook;
|
|
99
|
+
follow the cap/archive/index policy manually.
|
|
100
|
+
9. **Wire / hide** per visibility (see contract). Install the pre-commit hook (Node projects):
|
|
101
|
+
`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
|
+
10. **Stamp the deployment lineage.** Write the **deployment-lineage head** into
|
|
104
|
+
`docs/ai/.memory-version` (one semver line). The lineage head is **`1.3.0`** (the
|
|
105
|
+
`LINEAGE_HEAD` constant in `scripts/stamp-takeover.mjs`) — the shared `agent-workflow`
|
|
106
|
+
lineage, **not** this package's npm version (`1.0.0`). Use the atomic writer in
|
|
107
|
+
`scripts/stamp-takeover.mjs` (write-temp + rename).
|
|
108
|
+
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on filled-vs-TODO, then **ask before
|
|
109
|
+
committing** — never auto-commit. **Exception — delegated mode (below): skip this gate.**
|
|
110
|
+
|
|
111
|
+
> **Delegated mode (invoked by the composition root) — applies to BOTH bootstrap and upgrade.**
|
|
112
|
+
> When the family composition root drives this substrate as part of a family bootstrap **or
|
|
113
|
+
> upgrade**, do the write steps (bootstrap 1–10 / upgrade 1–7: write `docs/ai/` + `AGENTS.md` +
|
|
114
|
+
> `.memory-version`) but **do NOT** run **any** commit gate and **do NOT** ask to commit — the
|
|
115
|
+
> composition root owns the **single** commit gate, raised after it injects the methodology slot.
|
|
116
|
+
> The three setup answers and the target dir are passed in by the composition root; you perform no
|
|
117
|
+
> commit and no slot injection. **Standalone** invocation keeps its own commit gate (bootstrap step
|
|
118
|
+
> 11 / upgrade step 7).
|
|
119
|
+
|
|
120
|
+
Fill strategy:
|
|
121
|
+
|
|
122
|
+
| File | Strategy |
|
|
123
|
+
|------|----------|
|
|
124
|
+
| `current_state.md`, `architecture.md`, `env_commands.md`, `technical_specification.md`, `pages/index.md` | Fill with **real** recon data. |
|
|
125
|
+
| `tech_reference.md` | Carry over real configs/patterns found in deps. |
|
|
126
|
+
| `active_plan.md`, `handover.md` | TODO seed. |
|
|
127
|
+
| `decisions.md` | Seed `AD-001` (adopting this memory system). |
|
|
128
|
+
| `known_issues.md`, `changelog.md`, `pages/shared-patterns.md` | Empty template / first bootstrap entry. |
|
|
129
|
+
|
|
130
|
+
### Mode: upgrade
|
|
131
|
+
|
|
132
|
+
1. **Resolve the stamp.** Read `docs/ai/.memory-version`. If it is absent but a legacy
|
|
133
|
+
`docs/ai/.workflow-version` exists (a kit fallback bootstrap), run
|
|
134
|
+
`migrations/legacy-stamp-takeover.md` **first** — it copies that value verbatim into
|
|
135
|
+
`.memory-version` (and never deletes the legacy stamp). The pure state machine in
|
|
136
|
+
`scripts/stamp-takeover.mjs` decides the action per state; the Markdown migration is the
|
|
137
|
+
no-Node manual fallback. If **no** stamp exists at all, offer a conservative re-bootstrap.
|
|
138
|
+
2. **Compare** the stamp to the **deployment-lineage head** (`LINEAGE_HEAD`, `1.3.0`). Equal →
|
|
139
|
+
report "up to date" and stop. **Greater than the head, or unparseable → STOP and report**
|
|
140
|
+
(never downgrade or guess).
|
|
141
|
+
3. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` context (entries newer than the stamp).
|
|
142
|
+
4. Apply `${CLAUDE_SKILL_DIR}/migrations/<version>-<slug>.md` in **semver order**, only those
|
|
143
|
+
newer than the stamp. Migrations are **idempotent**.
|
|
144
|
+
5. Reconcile drift: add any substrate files/scripts the project is missing; **never clobber
|
|
145
|
+
project-authored content** (their `decisions.md`, `known_issues.md`, page specs stay). For a
|
|
146
|
+
pre-1.1.0 deployment with no *Communication language* block, ask + insert
|
|
147
|
+
(`migrations/1.1.0-communication-language.md`); pre-1.2.0 with no *Attribution* block, ask +
|
|
148
|
+
insert defaulting to `off` (`migrations/1.2.0-agent-attribution.md`).
|
|
149
|
+
6. **Preserve the methodology slot.** If `AGENTS.md` has the `workflow:methodology` markers,
|
|
150
|
+
**never regenerate the file wholesale** — extract any bytes between the markers and reinsert
|
|
151
|
+
them unchanged. If the markers are absent (a legacy `AGENTS.md`), gracefully **no-op** on the
|
|
152
|
+
slot (adding the slot to already-deployed files is a separate methodology migration). On any
|
|
153
|
+
malformed marker state (single, reversed, nested, or duplicate pair), **no-op with an error**
|
|
154
|
+
— never edit.
|
|
155
|
+
7. **Re-stamp** `docs/ai/.memory-version` to the lineage head (atomic write). Report changes;
|
|
156
|
+
**ask before committing** — **except in delegated mode** (see the *Delegated mode* note above),
|
|
157
|
+
where the composition root owns the single gate and this step raises none.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Gotchas
|
|
162
|
+
|
|
163
|
+
- **Source vs target directory.** Templates/scripts are read from the skill's own dir; the
|
|
164
|
+
**working directory is the target project** — never write substrate files back into the skill.
|
|
165
|
+
- **Stamp = lineage head, not package version.** `.memory-version` carries `1.3.0` (the shared
|
|
166
|
+
`agent-workflow` lineage), not the npm `1.0.0`. They are independent axes.
|
|
167
|
+
- **The methodology slot ships empty and stays the user's.** Never author methodology text into
|
|
168
|
+
it; on upgrade, preserve its content byte-for-byte. The composition root is its only writer.
|
|
169
|
+
- **The `Co-Authored-By` trailer is added by the harness, not by prose.** When attribution is
|
|
170
|
+
`off` + Claude Code, also set `"includeCoAuthoredBy": false` in `.claude/settings.json`.
|
|
171
|
+
- **Hidden mode must never touch `package.json`.** It also ignores `docs/ai/.memory-version`
|
|
172
|
+
(and any kit-fallback `.workflow-version`). After hiding, verify `git status` shows the
|
|
173
|
+
artifacts as ignored.
|
|
174
|
+
- **`CLAUDE.md` is a symlink, not a copy.** `ln -s AGENTS.md CLAUDE.md`.
|
|
175
|
+
- **Never overwrite an existing entry point or hook.** Stop and ask to merge vs replace.
|
|
176
|
+
- **No Node runtime → skip enforcement** (scripts + hook); follow the policy manually.
|
|
177
|
+
- **Never auto-commit.** Report quality-gate results and wait for explicit approval — both modes.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Setup contracts
|
|
182
|
+
|
|
183
|
+
The three setup choices each have a full contract in
|
|
184
|
+
[`references/contracts.md`](references/contracts.md). Defaults: visibility = `visible`; language
|
|
185
|
+
= whatever the user is already writing in; attribution = `off`.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## References
|
|
190
|
+
|
|
191
|
+
- [`references/contracts.md`](references/contracts.md) — the three setup contracts in full.
|
|
192
|
+
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md` (with the empty
|
|
193
|
+
methodology slot), `agent_rules.md`, and all `docs/ai/` files to deploy.
|
|
194
|
+
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness +
|
|
195
|
+
index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
196
|
+
- [`scripts/stamp-takeover.mjs`](scripts/stamp-takeover.mjs) — the upgrade-time lineage state
|
|
197
|
+
machine (`LINEAGE_HEAD`, atomic stamp writes) + tests.
|
|
198
|
+
- [`migrations/`](migrations/) — per-version upgrade steps, incl. `legacy-stamp-takeover.md`;
|
|
199
|
+
see `migrations/README.md`.
|
|
200
|
+
- [`capability.json`](capability.json) — the `agent-workflow` family manifest.
|
|
201
|
+
- [`CHANGELOG.md`](CHANGELOG.md) — version history of this substrate.
|
package/bin/install.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One-shot installer for @sabaiway/agent-workflow-memory.
|
|
3
|
+
//
|
|
4
|
+
// npx @sabaiway/agent-workflow-memory init
|
|
5
|
+
//
|
|
6
|
+
// Copies the memory substrate into the canonical skill home
|
|
7
|
+
// (~/.claude/skills/agent-workflow-memory, override with --dir or
|
|
8
|
+
// AGENT_WORKFLOW_MEMORY_DIR). Re-running refreshes the skill to this package's
|
|
9
|
+
// version — that is how you upgrade the *skill files* themselves:
|
|
10
|
+
//
|
|
11
|
+
// npx @sabaiway/agent-workflow-memory@latest init
|
|
12
|
+
//
|
|
13
|
+
// That is distinct from `/agent-workflow-memory upgrade`, which migrates a
|
|
14
|
+
// *project's* docs/ai deployment — see README "Use".
|
|
15
|
+
//
|
|
16
|
+
// No telemetry, no phone-home. Dependency-free, Node >= 18.
|
|
17
|
+
|
|
18
|
+
import { readFile, mkdir, readdir, copyFile, lstat, readlink, symlink } from 'node:fs/promises';
|
|
19
|
+
import { existsSync, lstatSync } from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve, relative, sep, isAbsolute } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const PKG_ROOT = resolve(__dirname, '..');
|
|
26
|
+
|
|
27
|
+
// The deployable skill = everything except the npm wrapper (package.json, bin/).
|
|
28
|
+
// Enumerated explicitly (mirrors package.json "files") so a missing payload entry
|
|
29
|
+
// is a loud failure, not a silent partial install.
|
|
30
|
+
const PAYLOAD = [
|
|
31
|
+
'SKILL.md',
|
|
32
|
+
'README.md',
|
|
33
|
+
'CHANGELOG.md',
|
|
34
|
+
'LICENSE',
|
|
35
|
+
'capability.json',
|
|
36
|
+
'references',
|
|
37
|
+
'scripts',
|
|
38
|
+
'migrations',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const tildify = (path) => path.replace(homedir(), '~');
|
|
42
|
+
|
|
43
|
+
const readVersion = async () => {
|
|
44
|
+
try {
|
|
45
|
+
const pkg = JSON.parse(await readFile(resolve(PKG_ROOT, 'package.json'), 'utf8'));
|
|
46
|
+
return pkg.version ?? 'unknown';
|
|
47
|
+
} catch {
|
|
48
|
+
return 'unknown';
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// lstat without following symlinks; null when the path does not exist. Using lstat (not
|
|
53
|
+
// existsSync, which FOLLOWS symlinks and so reports a *dangling* symlink as absent) is what
|
|
54
|
+
// makes the guard below catch a dangling destination symlink too.
|
|
55
|
+
const lstatNoFollow = (path) => {
|
|
56
|
+
try {
|
|
57
|
+
return lstatSync(path);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err && err.code === 'ENOENT') return null;
|
|
60
|
+
throw err; // EACCES/EIO etc. must NOT fail open (be read as "not a symlink")
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Symlink-traversal guard: refuse to write *through* any symlink at or above `dest`
|
|
65
|
+
// within `root`. Walks `root` plus each existing path component down to `dest`; if the
|
|
66
|
+
// root, an intermediate dir, or the leaf is a symlink (including a dangling one), a copy
|
|
67
|
+
// could escape the target — STOP rather than follow it. Also refuses a dest outside `root`.
|
|
68
|
+
const assertContainedRealPath = (root, dest) => {
|
|
69
|
+
const rel = relative(root, dest);
|
|
70
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
71
|
+
throw new Error(`[agent-workflow-memory] refusing to write outside the target dir: ${dest}`);
|
|
72
|
+
}
|
|
73
|
+
if (lstatNoFollow(root)?.isSymbolicLink()) {
|
|
74
|
+
throw new Error(`[agent-workflow-memory] refusing to install into a symlinked target dir: ${root}`);
|
|
75
|
+
}
|
|
76
|
+
const walk = (acc, part) => {
|
|
77
|
+
const cur = join(acc, part);
|
|
78
|
+
if (lstatNoFollow(cur)?.isSymbolicLink()) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[agent-workflow-memory] refusing to write through a symlink at ${cur} (would escape ${root}).`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return cur;
|
|
84
|
+
};
|
|
85
|
+
rel.split(sep).filter(Boolean).reduce(walk, root);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const copyRecursive = async (src, dest, root) => {
|
|
89
|
+
assertContainedRealPath(root, dest); // never write through a dest symlink (root/intermediate/leaf)
|
|
90
|
+
const stat = await lstat(src);
|
|
91
|
+
if (stat.isSymbolicLink()) {
|
|
92
|
+
if (existsSync(dest)) return; // additive: never delete/replace an existing entry
|
|
93
|
+
const linkTarget = await readlink(src);
|
|
94
|
+
await symlink(linkTarget, dest);
|
|
95
|
+
} else if (stat.isDirectory()) {
|
|
96
|
+
await mkdir(dest, { recursive: true });
|
|
97
|
+
const entries = await readdir(src);
|
|
98
|
+
await Promise.all(entries.map((entry) => copyRecursive(join(src, entry), join(dest, entry), root)));
|
|
99
|
+
} else {
|
|
100
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
101
|
+
await copyFile(src, dest);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const parseArgs = (argv) => {
|
|
106
|
+
const dirFlag = argv.indexOf('--dir');
|
|
107
|
+
return {
|
|
108
|
+
help: argv.includes('--help') || argv.includes('-h'),
|
|
109
|
+
version: argv.includes('--version') || argv.includes('-v'),
|
|
110
|
+
dir: dirFlag >= 0 ? argv[dirFlag + 1] : undefined,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const resolveTarget = (dirArg) => {
|
|
115
|
+
if (dirArg) return resolve(dirArg);
|
|
116
|
+
if (process.env.AGENT_WORKFLOW_MEMORY_DIR) return resolve(process.env.AGENT_WORKFLOW_MEMORY_DIR);
|
|
117
|
+
return resolve(homedir(), '.claude/skills/agent-workflow-memory');
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const printHelp = (version) => {
|
|
121
|
+
console.log(`agent-workflow-memory ${version}
|
|
122
|
+
|
|
123
|
+
Usage:
|
|
124
|
+
npx @sabaiway/agent-workflow-memory init [--dir <path>]
|
|
125
|
+
npx @sabaiway/agent-workflow-memory --version
|
|
126
|
+
npx @sabaiway/agent-workflow-memory --help
|
|
127
|
+
|
|
128
|
+
Installs/refreshes the memory substrate at ~/.claude/skills/agent-workflow-memory
|
|
129
|
+
(override with --dir <path> or AGENT_WORKFLOW_MEMORY_DIR). init is additive —
|
|
130
|
+
it never deletes your settings and never writes through a symlink.
|
|
131
|
+
|
|
132
|
+
After install, invoke the skill in your agent, inside a project:
|
|
133
|
+
first time in the project -> /agent-workflow-memory
|
|
134
|
+
project already has it -> /agent-workflow-memory upgrade
|
|
135
|
+
|
|
136
|
+
Re-running this npx command updates the skill's own files; /agent-workflow-memory
|
|
137
|
+
upgrade then migrates a project's deployment to that version.`);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const main = async () => {
|
|
141
|
+
const version = await readVersion();
|
|
142
|
+
const args = parseArgs(process.argv.slice(2));
|
|
143
|
+
|
|
144
|
+
if (args.help) return printHelp(version);
|
|
145
|
+
if (args.version) return console.log(version);
|
|
146
|
+
|
|
147
|
+
const missing = PAYLOAD.filter((entry) => !existsSync(resolve(PKG_ROOT, entry)));
|
|
148
|
+
if (missing.includes('SKILL.md')) {
|
|
149
|
+
console.error('[agent-workflow-memory] package payload missing (no SKILL.md) — corrupt install?');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
if (missing.length > 0) {
|
|
153
|
+
console.error(`[agent-workflow-memory] package payload incomplete — missing: ${missing.join(', ')}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const target = resolveTarget(args.dir);
|
|
158
|
+
const wasPresent = existsSync(resolve(target, 'SKILL.md'));
|
|
159
|
+
// If the target dir itself is a symlink (incl. dangling), refuse before writing through it.
|
|
160
|
+
// (copyRecursive re-checks per entry; this fails fast with a clear message.)
|
|
161
|
+
if (lstatNoFollow(target)?.isSymbolicLink()) {
|
|
162
|
+
console.error(`[agent-workflow-memory] target dir is a symlink — refusing to write through it: ${tildify(target)}`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
await mkdir(target, { recursive: true });
|
|
166
|
+
await Promise.all(
|
|
167
|
+
PAYLOAD.map((entry) => copyRecursive(resolve(PKG_ROOT, entry), resolve(target, entry), target)),
|
|
168
|
+
);
|
|
169
|
+
console.log(
|
|
170
|
+
`[agent-workflow-memory] ${wasPresent ? 'updated the substrate to' : 'installed'} v${version} -> ${tildify(target)}`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
console.log(`
|
|
174
|
+
Next — open your agent inside a project and run the skill:
|
|
175
|
+
• first time in this project -> /agent-workflow-memory
|
|
176
|
+
• project already has it -> /agent-workflow-memory upgrade
|
|
177
|
+
|
|
178
|
+
This command only installs/updates the skill itself (in ${tildify(target)}).
|
|
179
|
+
To update it later, re-run: npx @sabaiway/agent-workflow-memory@latest init`);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
main().catch((err) => {
|
|
183
|
+
console.error(err);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { mkdtemp, rm, mkdir, symlink, readdir } from 'node:fs/promises';
|
|
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';
|
|
9
|
+
|
|
10
|
+
const INSTALLER = join(dirname(fileURLToPath(import.meta.url)), 'install.mjs');
|
|
11
|
+
const runInstaller = (target) => spawnSync(process.execPath, [INSTALLER, '--dir', target], { encoding: 'utf8' });
|
|
12
|
+
|
|
13
|
+
describe('memory installer — payload + symlink-traversal hardening', () => {
|
|
14
|
+
let dir;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
dir = await mkdtemp(join(tmpdir(), 'aw-memory-install-'));
|
|
17
|
+
});
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await rm(dir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('installs the full enumerated payload (capability.json, LICENSE, stamp-takeover.mjs) and not the npm wrapper', () => {
|
|
23
|
+
const target = join(dir, 'agent-workflow-memory');
|
|
24
|
+
const res = runInstaller(target);
|
|
25
|
+
assert.equal(res.status, 0, res.stderr);
|
|
26
|
+
for (const f of [
|
|
27
|
+
'SKILL.md',
|
|
28
|
+
'capability.json',
|
|
29
|
+
'LICENSE',
|
|
30
|
+
'README.md',
|
|
31
|
+
'CHANGELOG.md',
|
|
32
|
+
'scripts/stamp-takeover.mjs',
|
|
33
|
+
'references/contracts.md',
|
|
34
|
+
'references/templates/AGENTS.md',
|
|
35
|
+
'migrations/legacy-stamp-takeover.md',
|
|
36
|
+
]) {
|
|
37
|
+
assert.ok(existsSync(join(target, f)), `missing installed entry: ${f}`);
|
|
38
|
+
}
|
|
39
|
+
assert.equal(existsSync(join(target, 'bin/install.mjs')), false, 'the npm wrapper must not be copied into the skill dir');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('refuses to write through a symlinked INTERMEDIATE dest component (no leak)', async () => {
|
|
43
|
+
const target = join(dir, 'target');
|
|
44
|
+
const evil = join(dir, 'evil');
|
|
45
|
+
await mkdir(target, { recursive: true });
|
|
46
|
+
await mkdir(evil, { recursive: true });
|
|
47
|
+
await symlink(evil, join(target, 'references'));
|
|
48
|
+
const res = runInstaller(target);
|
|
49
|
+
assert.notEqual(res.status, 0);
|
|
50
|
+
assert.match(res.stderr, /symlink/i);
|
|
51
|
+
assert.deepEqual(await readdir(evil), [], 'nothing should be written through the symlink');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('refuses a DANGLING destination symlink (existsSync would have followed it to absent)', async () => {
|
|
55
|
+
const target = join(dir, 'target');
|
|
56
|
+
await mkdir(target, { recursive: true });
|
|
57
|
+
// references -> a path that does not exist (dangling). copyFile through it would create
|
|
58
|
+
// the file at the dangling target, outside `target`.
|
|
59
|
+
await symlink(join(dir, 'nowhere'), join(target, 'references'));
|
|
60
|
+
const res = runInstaller(target);
|
|
61
|
+
assert.notEqual(res.status, 0);
|
|
62
|
+
assert.match(res.stderr, /symlink/i);
|
|
63
|
+
assert.equal(existsSync(join(dir, 'nowhere')), false, 'dangling target must not be materialised');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('refuses a symlinked TARGET root (no leak)', async () => {
|
|
67
|
+
const real = join(dir, 'real');
|
|
68
|
+
const root = join(dir, 'root');
|
|
69
|
+
await mkdir(real, { recursive: true });
|
|
70
|
+
await symlink(real, root);
|
|
71
|
+
const res = runInstaller(root);
|
|
72
|
+
assert.notEqual(res.status, 0);
|
|
73
|
+
assert.match(res.stderr, /symlink/i);
|
|
74
|
+
assert.deepEqual(await readdir(real), [], 'nothing should be written through the symlinked target');
|
|
75
|
+
});
|
|
76
|
+
});
|
package/capability.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"family": "agent-workflow",
|
|
3
|
+
"schema": 1,
|
|
4
|
+
"name": "agent-workflow-memory",
|
|
5
|
+
"kind": "memory-substrate",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"provides": ["context"],
|
|
8
|
+
"roles": {},
|
|
9
|
+
"detect": {
|
|
10
|
+
"installed": {
|
|
11
|
+
"env": "AGENT_WORKFLOW_MEMORY_DIR",
|
|
12
|
+
"default": "~/.claude/skills/agent-workflow-memory",
|
|
13
|
+
"file": "SKILL.md"
|
|
14
|
+
},
|
|
15
|
+
"deployed": {
|
|
16
|
+
"file": "docs/ai/.memory-version"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"install": { "npm": "@sabaiway/agent-workflow-memory" },
|
|
20
|
+
"uninstall": {
|
|
21
|
+
"removeResolved": "detect.installed",
|
|
22
|
+
"note": "init copies into the skill dir; uninstall removes the resolved detect.installed dir — not an npm dependency"
|
|
23
|
+
},
|
|
24
|
+
"cost": "none",
|
|
25
|
+
"quota": null,
|
|
26
|
+
"provenance": { "author": "sabaiway", "source": "github:sabaiway/agent-workflow" }
|
|
27
|
+
}
|