@sabaiway/agent-workflow-memory 1.1.1 → 1.2.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 +33 -0
- package/README.md +3 -2
- package/SKILL.md +41 -27
- package/bin/install.test.mjs +1 -0
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/scripts/check-docs-size.mjs +4 -1
- package/references/scripts/check-docs-size.test.mjs +24 -0
- package/references/templates/AGENTS.md +2 -3
- package/references/templates/orchestration.json +5 -0
- package/scripts/standalone-bootstrap.test.mjs +74 -5
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.2.0 — Seeds the per-project orchestration config
|
|
8
|
+
|
|
9
|
+
The substrate now seeds a new **per-project, user-editable recipe config** —
|
|
10
|
+
`references/templates/orchestration.json` — deployed into `docs/ai/orchestration.json` by the bootstrap
|
|
11
|
+
template loop. It declares the orchestration **recipe** each named activity/slot uses (the composition
|
|
12
|
+
root's read-only `procedures` advisor reads it); the recipe **canon** and the slot **vocabulary** still
|
|
13
|
+
live in the engine / composition root, never here (the substrate keeps knowing nobody — the seed's
|
|
14
|
+
self-documentation uses generic "composition root" phrasing, naming no sibling). The shipped default is
|
|
15
|
+
conservative: **`solo` everywhere**, with an onboarding `_README` explaining how to raise a slot.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`references/templates/orchestration.json`** — strict JSON (no comments), byte-identical to the kit's
|
|
19
|
+
fallback copy (kit↔memory template parity guard). Seeded on bootstrap; on upgrade it is **ensured
|
|
20
|
+
stamp-independently** (create-if-missing / **preserve-if-edited** — a user's edits are never clobbered),
|
|
21
|
+
so even an equal-head (`1.3.0`) deployment gains it **without a lineage-head bump or a migration file**.
|
|
22
|
+
- An **ownership-table** row distinguishing the seeded, editable recipe **CONFIG** (memory) from the
|
|
23
|
+
recipe **CANON** + slot vocabulary (engine / composition root).
|
|
24
|
+
|
|
25
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file). The
|
|
26
|
+
npm package version is a separate axis.
|
|
27
|
+
|
|
28
|
+
## 1.1.2 — Entry-point template headroom for the orchestration pointer
|
|
29
|
+
|
|
30
|
+
A **docs/prose** release (no new executable, the `1.1.1`/`1.9.1` precedent). The bundled entry-point
|
|
31
|
+
template (`references/templates/AGENTS.md`) ships a second empty marker pair — `workflow:orchestration`,
|
|
32
|
+
right under the methodology pair — which the family **composition root** fills live from the methodology
|
|
33
|
+
engine on deploy. To keep the deployed `AGENTS.md` inside its ≤100-line budget with **both** pointers
|
|
34
|
+
filled, the template trimmed non-essential slack (the Hard-Constraints intro blockquote + one
|
|
35
|
+
illustrative row a deploying agent adapts anyway). No behaviour change; the composition root remains the
|
|
36
|
+
only writer of the slots.
|
|
37
|
+
|
|
38
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file).
|
|
39
|
+
|
|
7
40
|
## 1.1.1 — Installer hardening (Issue-004 parity)
|
|
8
41
|
|
|
9
42
|
A patch release that applies the same two installer fixes shipped to the engine in 1.1.0, keeping the
|
package/README.md
CHANGED
|
@@ -33,10 +33,11 @@ Open your agent inside a project and run the skill:
|
|
|
33
33
|
- **`/agent-workflow-memory`** — bootstrap a new or empty project. Asks the three setup
|
|
34
34
|
questions (visibility, conversational language, agent attribution), then writes `docs/ai/`,
|
|
35
35
|
`AGENTS.md`, the enforcement scripts + pre-commit hook, and stamps
|
|
36
|
-
`docs/ai/.memory-version`. The
|
|
36
|
+
`docs/ai/.memory-version`. The two pointer slots in `AGENTS.md` (the **methodology** and
|
|
37
|
+
**orchestration** pointers, filled later by the family composition root) are left **empty**.
|
|
37
38
|
- **`/agent-workflow-memory upgrade`** — migrate an existing deployment to the skill's current
|
|
38
39
|
version. Reads `docs/ai/.memory-version`, runs only newer migrations in semver order, and
|
|
39
|
-
**preserves** anything already in
|
|
40
|
+
**preserves** anything already in either pointer slot (extract-and-reinsert — it never
|
|
40
41
|
regenerates `AGENTS.md` wholesale).
|
|
41
42
|
|
|
42
43
|
## Stamps & lineage
|
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.2.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-memory
|
|
@@ -20,11 +20,12 @@ manually).
|
|
|
20
20
|
|
|
21
21
|
This skill is the **memory layer** of the `agent-workflow` family. It **knows nobody else** in
|
|
22
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
review vocabulary, lifecycle, `docs/plans/queue.md`, mandatory Cleanup, plan-then-execute) **or the
|
|
24
|
+
orchestration recipes** — those are injected into **two delimited pointer slots** in `AGENTS.md`
|
|
25
|
+
(`workflow:methodology` + `workflow:orchestration`) by the family **composition root**, never by this
|
|
26
|
+
skill. (This substrate does not name or depend on any specific sibling — it only honours the slot
|
|
27
|
+
contract.) This skill only ever ships **both** slots **empty** and **preserves** whatever is already
|
|
28
|
+
in them on upgrade.
|
|
28
29
|
|
|
29
30
|
The substrate **artifacts** (this skill, the templates, the deployed `docs/ai/` files) stay in
|
|
30
31
|
their **source language** — for cross-agent and cross-team portability. That is separate from the
|
|
@@ -35,8 +36,8 @@ contents).
|
|
|
35
36
|
|
|
36
37
|
## Ownership table
|
|
37
38
|
|
|
38
|
-
What this substrate owns vs what it only points at. The methodology
|
|
39
|
-
the composition root fills — never author
|
|
39
|
+
What this substrate owns vs what it only points at. The methodology + orchestration pointers are the
|
|
40
|
+
**two empty slots** the composition root fills — never author that text here.
|
|
40
41
|
|
|
41
42
|
| Concern | Owner | In the deployed `AGENTS.md` |
|
|
42
43
|
|---|---|---|
|
|
@@ -45,6 +46,8 @@ the composition root fills — never author methodology text here.
|
|
|
45
46
|
| Visibility / conversational-language / agent-attribution contracts | **memory** | the three `AGENTS.md` blocks |
|
|
46
47
|
| Deployment-lineage stamp | **memory** | `docs/ai/.memory-version` |
|
|
47
48
|
| Plan→Phase→Step vocabulary, lifecycle, `queue.md`, mandatory Cleanup | **methodology** (not this skill) | the empty `workflow:methodology` slot — filled by the composition root |
|
|
49
|
+
| Orchestration recipes (Solo / Reviewed / Council / Delegated) | **methodology engine** (not this skill) | the empty `workflow:orchestration` slot — filled by the composition root |
|
|
50
|
+
| Per-project recipe **CONFIG** (which recipe each activity/slot uses) | **memory** seeds an *editable default* | `docs/ai/orchestration.json` (hand-edited; the recipe **canon** + the slot **vocabulary** live in the engine / composition root, never here) |
|
|
48
51
|
|
|
49
52
|
---
|
|
50
53
|
|
|
@@ -90,10 +93,12 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
90
93
|
5. **Entry-point doc.** If `AGENTS.md` / `CLAUDE.md` already exist (recon), do **not**
|
|
91
94
|
overwrite — show the user and ask whether to merge or replace. Otherwise create `AGENTS.md`
|
|
92
95
|
from `${CLAUDE_SKILL_DIR}/references/templates/AGENTS.md` and symlink `CLAUDE.md -> AGENTS.md`
|
|
93
|
-
(`ln -s AGENTS.md CLAUDE.md`). **Leave
|
|
94
|
-
empty.** Filling
|
|
96
|
+
(`ln -s AGENTS.md CLAUDE.md`). **Leave BOTH pointer slots (`workflow:methodology` +
|
|
97
|
+
`workflow:orchestration`) exactly as shipped — empty.** Filling them is the composition root's job.
|
|
95
98
|
6. **Deploy `docs/ai/`.** Create the files + `pages/` from
|
|
96
|
-
`${CLAUDE_SKILL_DIR}/references/templates
|
|
99
|
+
`${CLAUDE_SKILL_DIR}/references/templates/` (every non-`AGENTS.md` template, including the seeded,
|
|
100
|
+
**user-editable** `docs/ai/orchestration.json` config — strict JSON, the per-project recipe defaults
|
|
101
|
+
the composition root's `procedures` advisor reads). Keep each file's frontmatter.
|
|
97
102
|
7. **Fill templates** per the table below.
|
|
98
103
|
8. **Install enforcement (Node projects).** Copy `${CLAUDE_SKILL_DIR}/references/scripts/*.mjs`
|
|
99
104
|
(+ `*.test.mjs`) into the project's `scripts/`. **No Node runtime** → skip this + the hook;
|
|
@@ -108,7 +113,7 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
108
113
|
10. **Stamp the deployment lineage.** Write the **deployment-lineage head** into
|
|
109
114
|
`docs/ai/.memory-version` (one semver line). The lineage head is **`1.3.0`** (the
|
|
110
115
|
`LINEAGE_HEAD` constant in `scripts/stamp-takeover.mjs`) — the shared `agent-workflow`
|
|
111
|
-
lineage, **not** this package's npm version (`1.
|
|
116
|
+
lineage, **not** this package's npm version (`1.2.0`). Use the atomic writer in
|
|
112
117
|
`scripts/stamp-takeover.mjs` (write-temp + rename).
|
|
113
118
|
11. **Report & ask.** Show `tree docs/ai/`, 2–3 lines on filled-vs-TODO, then **ask before
|
|
114
119
|
committing** — never auto-commit. **Exception — delegated mode (below): skip this gate.**
|
|
@@ -116,8 +121,9 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
116
121
|
> **Delegated mode (invoked by the composition root) — applies to BOTH bootstrap and upgrade.**
|
|
117
122
|
> When the family composition root drives this substrate as part of a family bootstrap **or
|
|
118
123
|
> upgrade**, do the write steps (bootstrap 1–10 / upgrade 1–7: write `docs/ai/` + `AGENTS.md` +
|
|
119
|
-
> `.memory-version
|
|
120
|
-
>
|
|
124
|
+
> `.memory-version`, **including seeding / stamp-independently ensuring `docs/ai/orchestration.json`**)
|
|
125
|
+
> but **do NOT** run **any** commit gate and **do NOT** ask to commit — the
|
|
126
|
+
> composition root owns the **single** commit gate, raised after it injects the two pointer slots.
|
|
121
127
|
> The three setup answers and the target dir are passed in by the composition root; you perform no
|
|
122
128
|
> commit and no slot injection. **Standalone** invocation keeps its own commit gate (bootstrap step
|
|
123
129
|
> 11 / upgrade step 7).
|
|
@@ -154,8 +160,14 @@ Fill strategy:
|
|
|
154
160
|
block and removes it only after the user's explicit consent — never by default); if it is
|
|
155
161
|
**untracked AND not ignored** → **AMBIGUOUS** → **ASK** the user before writing. This visibility check
|
|
156
162
|
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. **
|
|
158
|
-
|
|
163
|
+
**behind** the never-downgrade STOP above. **Also stamp-independent (same gate, before the equal-head
|
|
164
|
+
short-circuit): ensure `docs/ai/orchestration.json`** — **create it from
|
|
165
|
+
`${CLAUDE_SKILL_DIR}/references/templates/orchestration.json` if missing**, **preserve it byte-for-byte
|
|
166
|
+
if it already exists** (a user may have edited it; never clobber it). This is why an equal-head (`1.3.0`)
|
|
167
|
+
deployment still gains the config seed **without a lineage-head bump or a migration file** (the
|
|
168
|
+
stamp-independent-reconcile precedent — like the pointer slots + the hidden-mode footprint). **Then**,
|
|
169
|
+
if the stamp **equals** the head → report "up to date" (plus any footprint move / config seed just
|
|
170
|
+
made) and stop.
|
|
159
171
|
3. Show the relevant `${CLAUDE_SKILL_DIR}/CHANGELOG.md` context (entries newer than the stamp).
|
|
160
172
|
4. Apply `${CLAUDE_SKILL_DIR}/migrations/<version>-<slug>.md` in **semver order**, only those
|
|
161
173
|
newer than the stamp. Migrations are **idempotent**.
|
|
@@ -164,12 +176,12 @@ Fill strategy:
|
|
|
164
176
|
pre-1.1.0 deployment with no *Communication language* block, ask + insert
|
|
165
177
|
(`migrations/1.1.0-communication-language.md`); pre-1.2.0 with no *Attribution* block, ask +
|
|
166
178
|
insert defaulting to `off` (`migrations/1.2.0-agent-attribution.md`).
|
|
167
|
-
6. **Preserve
|
|
168
|
-
**never regenerate the file wholesale** — extract any bytes
|
|
169
|
-
them unchanged. If
|
|
170
|
-
slot (adding
|
|
171
|
-
malformed marker state (single, reversed,
|
|
172
|
-
— never edit.
|
|
179
|
+
6. **Preserve BOTH pointer slots.** If `AGENTS.md` has the `workflow:methodology` and/or
|
|
180
|
+
`workflow:orchestration` markers, **never regenerate the file wholesale** — extract any bytes
|
|
181
|
+
between each pair and reinsert them unchanged. If a pair is absent (a legacy `AGENTS.md`),
|
|
182
|
+
gracefully **no-op** on that slot (adding a slot to already-deployed files is the composition
|
|
183
|
+
root's reconcile, not this substrate's job). On any malformed marker state (single, reversed,
|
|
184
|
+
nested, or duplicate pair), **no-op with an error** — never edit.
|
|
173
185
|
7. **Re-stamp** `docs/ai/.memory-version` to the lineage head (atomic write). Report changes;
|
|
174
186
|
**ask before committing** — **except in delegated mode** (see the *Delegated mode* note above),
|
|
175
187
|
where the composition root owns the single gate and this step raises none.
|
|
@@ -181,9 +193,10 @@ Fill strategy:
|
|
|
181
193
|
- **Source vs target directory.** Templates/scripts are read from the skill's own dir; the
|
|
182
194
|
**working directory is the target project** — never write substrate files back into the skill.
|
|
183
195
|
- **Stamp = lineage head, not package version.** `.memory-version` carries `1.3.0` (the shared
|
|
184
|
-
`agent-workflow` lineage), not the npm `1.
|
|
185
|
-
- **
|
|
186
|
-
|
|
196
|
+
`agent-workflow` lineage), not the npm `1.2.0`. They are independent axes.
|
|
197
|
+
- **Both pointer slots ship empty and stay the user's.** Never author methodology or orchestration
|
|
198
|
+
text into them; on upgrade, preserve their content byte-for-byte. The composition root is their only
|
|
199
|
+
writer.
|
|
187
200
|
- **The `Co-Authored-By` trailer is added by the harness, not by prose.** When attribution is
|
|
188
201
|
`off` + Claude Code, also set `"includeCoAuthoredBy": false` in `.claude/settings.json`.
|
|
189
202
|
- **Hidden mode is project-local, never machine-global.** Memory's hide writes to the repo's
|
|
@@ -209,8 +222,9 @@ The three setup choices each have a full contract in
|
|
|
209
222
|
## References
|
|
210
223
|
|
|
211
224
|
- [`references/contracts.md`](references/contracts.md) — the three setup contracts in full.
|
|
212
|
-
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md` (with the empty
|
|
213
|
-
methodology
|
|
225
|
+
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md` (with the two empty
|
|
226
|
+
pointer slots — methodology + orchestration), `agent_rules.md`, the seeded user-editable
|
|
227
|
+
`orchestration.json` config, and all `docs/ai/` files to deploy.
|
|
214
228
|
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness +
|
|
215
229
|
index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
216
230
|
- [`scripts/stamp-takeover.mjs`](scripts/stamp-takeover.mjs) — the upgrade-time lineage state
|
package/bin/install.test.mjs
CHANGED
|
@@ -33,6 +33,7 @@ describe('memory installer — payload + symlink-traversal hardening', () => {
|
|
|
33
33
|
'scripts/stamp-takeover.mjs',
|
|
34
34
|
'references/contracts.md',
|
|
35
35
|
'references/templates/AGENTS.md',
|
|
36
|
+
'references/templates/orchestration.json',
|
|
36
37
|
'migrations/legacy-stamp-takeover.md',
|
|
37
38
|
]) {
|
|
38
39
|
assert.ok(existsSync(join(target, f)), `missing installed entry: ${f}`);
|
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.2.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",
|
|
@@ -139,7 +139,10 @@ export const parseStaleAfter = (value) => {
|
|
|
139
139
|
return Number(m[1]);
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
// Discover the docs to validate: ONLY `*.md` files (recursively). Non-`.md` files — e.g. a hand-edited
|
|
143
|
+
// `docs/ai/orchestration.json` config — are inherently skipped, so they are never subject to the
|
|
144
|
+
// frontmatter / maxLines caps. Exported so that skip is pinned by a regression test.
|
|
145
|
+
export const walkMarkdownFiles = async (dir) => {
|
|
143
146
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
144
147
|
const files = [];
|
|
145
148
|
for (const entry of entries) {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
inspectFile,
|
|
11
11
|
buildIndex,
|
|
12
12
|
checkIndexFreshness,
|
|
13
|
+
walkMarkdownFiles,
|
|
13
14
|
} from './check-docs-size.mjs';
|
|
14
15
|
|
|
15
16
|
describe('parseFrontmatter', () => {
|
|
@@ -126,6 +127,29 @@ describe('inspectFile', () => {
|
|
|
126
127
|
});
|
|
127
128
|
});
|
|
128
129
|
|
|
130
|
+
// The cap-validator discovers ONLY `*.md` files, so a non-`.md` doc — e.g. a hand-edited
|
|
131
|
+
// `docs/ai/orchestration.json` config — is inherently skipped: never validated for frontmatter / caps.
|
|
132
|
+
// This belt-and-suspenders regression pins that skip so adding a config `.json` under docs/ai can never
|
|
133
|
+
// start failing the docs gate.
|
|
134
|
+
describe('walkMarkdownFiles — only *.md is discovered (a config .json is skipped)', () => {
|
|
135
|
+
let dir;
|
|
136
|
+
beforeEach(async () => {
|
|
137
|
+
dir = await mkdtemp(join(tmpdir(), 'walk-md-test-'));
|
|
138
|
+
});
|
|
139
|
+
afterEach(async () => {
|
|
140
|
+
await rm(dir, { recursive: true, force: true });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('returns the .md file and NOT a sibling orchestration.json', async () => {
|
|
144
|
+
await writeFile(join(dir, 'doc.md'), '---\ntype: reference\nmaxLines: 100\n---\n\nbody.\n');
|
|
145
|
+
await writeFile(join(dir, 'orchestration.json'), '{ "plan-authoring": { "review": "reviewed" } }\n');
|
|
146
|
+
const found = await walkMarkdownFiles(dir);
|
|
147
|
+
expect(found.some((f) => f.endsWith('doc.md'))).toBe(true);
|
|
148
|
+
expect(found.some((f) => f.endsWith('.json'))).toBe(false);
|
|
149
|
+
expect(found.some((f) => f.endsWith('orchestration.json'))).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
129
153
|
// Synthetic row matching the shape produced by `inspectFile` + `formatRow`.
|
|
130
154
|
const makeRow = (path, overrides = {}) => ({
|
|
131
155
|
path,
|
|
@@ -55,19 +55,18 @@ Start-of-session, during-work, and task-completion procedures live in [`docs/ai/
|
|
|
55
55
|
|
|
56
56
|
<!-- workflow:methodology:start -->
|
|
57
57
|
<!-- workflow:methodology:end -->
|
|
58
|
+
<!-- workflow:orchestration:start -->
|
|
59
|
+
<!-- workflow:orchestration:end -->
|
|
58
60
|
|
|
59
61
|
---
|
|
60
62
|
|
|
61
63
|
## 🚫 Hard Constraints
|
|
62
64
|
|
|
63
|
-
> Adapt to this stack — remove rows that don't apply. Style rules should be linter-enforced, not prose.
|
|
64
|
-
|
|
65
65
|
| Rule | Enforcement |
|
|
66
66
|
|------|-------------|
|
|
67
67
|
| No `export default` (named exports only) | Linter |
|
|
68
68
|
| No `any` / no unsafe casts | Linter / type-checker |
|
|
69
69
|
| Functional style (no mutation in app code) | Linter |
|
|
70
|
-
| No single-letter variables | Code review |
|
|
71
70
|
| Interactive elements semantic (button/link, not div+onClick) | Linter / a11y |
|
|
72
71
|
| No hardcoded colors — design tokens only | Code review |
|
|
73
72
|
| No business logic in components → hooks/services | Architecture review |
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_README": "Per-project orchestration config: the recipe used at each step (slot) of each named activity. Hand-edit this file — it is never written for you. Each activity is configured independently (e.g. plan-authoring, plan-execution), and so is each slot within it. A slot's value is a recipe: a 'review' slot accepts solo | reviewed | council (you self-review / one backend reviews / both review and you synthesize); an 'execute' slot accepts solo | delegated (you implement / a backend runs a bounded sub-task). The default below is 'solo' everywhere — no execution backend required. Raise a slot to reviewed or council for a second opinion, or to delegated to hand off execution; those need an execution backend set up first. Remove a slot's line to fall back to the computed default (reviewed when a review backend is ready, otherwise solo). Run the read-only procedures advisor to see an activity's steps plus the recipe resolved for your environment, and pass a per-run override to change one slot just once. Strict JSON — no comments.",
|
|
3
|
+
"plan-authoring": { "review": "solo" },
|
|
4
|
+
"plan-execution": { "execute": "solo", "review": "solo" }
|
|
5
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
cpSync,
|
|
19
19
|
readdirSync,
|
|
20
20
|
readFileSync,
|
|
21
|
+
writeFileSync,
|
|
21
22
|
existsSync,
|
|
22
23
|
symlinkSync,
|
|
23
24
|
} from 'node:fs';
|
|
@@ -35,6 +36,17 @@ const ENFORCEMENT = join(SKILL_ROOT, 'references', 'scripts');
|
|
|
35
36
|
// from the composition root) so this substrate test stays self-contained and dependency-free.
|
|
36
37
|
const SLOT_START = '<!-- workflow:methodology:start -->';
|
|
37
38
|
const SLOT_END = '<!-- workflow:methodology:end -->';
|
|
39
|
+
const ORCH_START = '<!-- workflow:orchestration:start -->';
|
|
40
|
+
const ORCH_END = '<!-- workflow:orchestration:end -->';
|
|
41
|
+
// The deployed AGENTS.md line budget the composition root fills BOTH pointers inside (D-CAP). A
|
|
42
|
+
// representative single-line fragment per slot models what the composition root injects.
|
|
43
|
+
const AGENTS_MD_CAP = 100;
|
|
44
|
+
const lineCount = (text) => text.split('\n').length - (text.endsWith('\n') ? 1 : 0);
|
|
45
|
+
const extractPair = (text, start, end) => {
|
|
46
|
+
const a = text.indexOf(start);
|
|
47
|
+
const b = text.indexOf(end);
|
|
48
|
+
return a !== -1 && b !== -1 && b > a ? text.slice(a + start.length, b) : null;
|
|
49
|
+
};
|
|
38
50
|
|
|
39
51
|
const tempDirs = [];
|
|
40
52
|
const makeProject = () => {
|
|
@@ -98,14 +110,71 @@ describe('standalone substrate bootstrap (end-to-end, real temp project)', () =>
|
|
|
98
110
|
assert.ok(!existsSync(join(docsAi, '.workflow-version')), 'no composition-root stamp in a standalone bootstrap');
|
|
99
111
|
});
|
|
100
112
|
|
|
101
|
-
it('ships
|
|
113
|
+
it('ships BOTH pointer slots present-but-empty (methodology + orchestration)', () => {
|
|
102
114
|
const project = makeProject();
|
|
103
115
|
bootstrap(project);
|
|
104
116
|
|
|
105
117
|
const entry = readFileSync(join(project, 'AGENTS.md'), 'utf8');
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
assert.
|
|
109
|
-
assert.equal(
|
|
118
|
+
const meth = extractPair(entry, SLOT_START, SLOT_END);
|
|
119
|
+
const orch = extractPair(entry, ORCH_START, ORCH_END);
|
|
120
|
+
assert.notEqual(meth, null, 'an ordered methodology marker pair is present');
|
|
121
|
+
assert.equal(meth.trim(), '', 'the methodology slot is empty as shipped');
|
|
122
|
+
assert.notEqual(orch, null, 'an ordered orchestration marker pair is present');
|
|
123
|
+
assert.equal(orch.trim(), '', 'the orchestration slot is empty as shipped');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('seeds docs/ai/orchestration.json from the template (the bootstrap loop deploys it)', () => {
|
|
127
|
+
const project = makeProject();
|
|
128
|
+
const docsAi = bootstrap(project);
|
|
129
|
+
const seeded = join(docsAi, 'orchestration.json');
|
|
130
|
+
assert.ok(existsSync(seeded), 'the orchestration.json config is seeded into docs/ai');
|
|
131
|
+
assert.equal(
|
|
132
|
+
readFileSync(seeded, 'utf8'),
|
|
133
|
+
readFileSync(join(TEMPLATES, 'orchestration.json'), 'utf8'),
|
|
134
|
+
'the seeded config is byte-identical to the template',
|
|
135
|
+
);
|
|
136
|
+
// strict JSON valid + the conservative all-solo default the maintainer chose.
|
|
137
|
+
const config = JSON.parse(readFileSync(seeded, 'utf8'));
|
|
138
|
+
assert.equal(typeof config._README, 'string', 'an onboarding _README is present');
|
|
139
|
+
assert.equal(config['plan-authoring'].review, 'solo', 'default review recipe is solo');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// The stamp-independent upgrade "ensure" (SKILL.md upgrade step 2): create-if-missing /
|
|
143
|
+
// preserve-if-edited. Modeled here the way the documented prose performs it — so an equal-head
|
|
144
|
+
// re-run never clobbers a user's edited config, and a deleted one is re-seeded.
|
|
145
|
+
it('the upgrade ensure preserves an edited config and re-creates a deleted one', () => {
|
|
146
|
+
const project = makeProject();
|
|
147
|
+
const docsAi = bootstrap(project);
|
|
148
|
+
const dest = join(docsAi, 'orchestration.json');
|
|
149
|
+
const ensureConfig = () => {
|
|
150
|
+
if (!existsSync(dest)) cpSync(join(TEMPLATES, 'orchestration.json'), dest);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// A user edits the deployed config.
|
|
154
|
+
writeFileSync(dest, '{ "plan-authoring": { "review": "council" } }\n');
|
|
155
|
+
ensureConfig(); // an equal-head upgrade re-runs the ensure
|
|
156
|
+
assert.match(readFileSync(dest, 'utf8'), /council/, 'an edited config is preserved (never clobbered)');
|
|
157
|
+
|
|
158
|
+
// A missing config is re-seeded.
|
|
159
|
+
rmSync(dest);
|
|
160
|
+
ensureConfig();
|
|
161
|
+
assert.ok(existsSync(dest), 'a missing config is re-created from the template');
|
|
162
|
+
assert.equal(readFileSync(dest, 'utf8'), readFileSync(join(TEMPLATES, 'orchestration.json'), 'utf8'));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('stays ≤ the cap when the composition root fills BOTH pointer slots (D-CAP headroom)', () => {
|
|
166
|
+
const project = makeProject();
|
|
167
|
+
bootstrap(project);
|
|
168
|
+
const entry = readFileSync(join(project, 'AGENTS.md'), 'utf8');
|
|
169
|
+
assert.ok(lineCount(entry) <= AGENTS_MD_CAP, `shipped (empty) AGENTS.md is ${lineCount(entry)} lines (cap ${AGENTS_MD_CAP})`);
|
|
170
|
+
// Fill each one-line pointer the way the composition root does (replace the empty body), then re-count.
|
|
171
|
+
const fill = (text, start, end, body) => {
|
|
172
|
+
const a = text.indexOf(start);
|
|
173
|
+
const b = text.indexOf(end);
|
|
174
|
+
return `${text.slice(0, a + start.length)}\n${body}\n${text.slice(b)}`;
|
|
175
|
+
};
|
|
176
|
+
let filled = fill(entry, SLOT_START, SLOT_END, '> methodology pointer (one line)');
|
|
177
|
+
filled = fill(filled, ORCH_START, ORCH_END, '> orchestration recipes pointer (one line)');
|
|
178
|
+
assert.ok(lineCount(filled) <= AGENTS_MD_CAP, `dual-filled AGENTS.md is ${lineCount(filled)} lines (cap ${AGENTS_MD_CAP})`);
|
|
110
179
|
});
|
|
111
180
|
});
|