@sabaiway/agent-workflow-memory 1.1.0 → 1.1.2
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 +29 -0
- package/README.md +4 -3
- package/SKILL.md +24 -21
- package/bin/install.mjs +33 -10
- package/bin/install.test.mjs +36 -3
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/templates/AGENTS.md +2 -3
- package/scripts/standalone-bootstrap.test.mjs +34 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@ All notable changes to the memory substrate. Versions are this **package's** npm
|
|
|
4
4
|
they are distinct from the **deployment-lineage** stamp written into a project's
|
|
5
5
|
`docs/ai/.memory-version` (which tracks the shared `agent-workflow` lineage, head `1.3.0`).
|
|
6
6
|
|
|
7
|
+
## 1.1.2 — Entry-point template headroom for the orchestration pointer
|
|
8
|
+
|
|
9
|
+
A **docs/prose** release (no new executable, the `1.1.1`/`1.9.1` precedent). The bundled entry-point
|
|
10
|
+
template (`references/templates/AGENTS.md`) ships a second empty marker pair — `workflow:orchestration`,
|
|
11
|
+
right under the methodology pair — which the family **composition root** fills live from the methodology
|
|
12
|
+
engine on deploy. To keep the deployed `AGENTS.md` inside its ≤100-line budget with **both** pointers
|
|
13
|
+
filled, the template trimmed non-essential slack (the Hard-Constraints intro blockquote + one
|
|
14
|
+
illustrative row a deploying agent adapts anyway). No behaviour change; the composition root remains the
|
|
15
|
+
only writer of the slots.
|
|
16
|
+
|
|
17
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file).
|
|
18
|
+
|
|
19
|
+
## 1.1.1 — Installer hardening (Issue-004 parity)
|
|
20
|
+
|
|
21
|
+
A patch release that applies the same two installer fixes shipped to the engine in 1.1.0, keeping the
|
|
22
|
+
two identical family installers in lockstep.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Containment check now accepts a legitimately-contained child literally named `..foo` (it wrongly
|
|
26
|
+
rejected anything starting with `..` before); `tildify` collapses only a **leading** `$HOME`, never
|
|
27
|
+
a mid-path occurrence (**Issue-004**).
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- The installer is importable without side effects (the `isDirectRun` guard) and exports its
|
|
31
|
+
path/format helpers for in-process tests. The installer's own + README bare `npx … init` strings now
|
|
32
|
+
use `@latest`.
|
|
33
|
+
|
|
34
|
+
The deployment-lineage head stays **`1.3.0`** (no `docs/ai` structural change; no migration file).
|
|
35
|
+
|
|
7
36
|
## 1.1.0 — Hidden mode writes project-local, not global, excludes
|
|
8
37
|
|
|
9
38
|
Memory's **hidden** visibility now targets the **project-local** `.git/info/exclude` (its own footprint
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ into a delimited slot in `AGENTS.md` by the family composition root, not by this
|
|
|
17
17
|
## Install
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx @sabaiway/agent-workflow-memory init
|
|
20
|
+
npx @sabaiway/agent-workflow-memory@latest init
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Installs/refreshes the skill at `~/.claude/skills/agent-workflow-memory` (override with
|
|
@@ -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.1.
|
|
6
|
+
version: '1.1.2'
|
|
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,7 @@ 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 |
|
|
48
50
|
|
|
49
51
|
---
|
|
50
52
|
|
|
@@ -90,8 +92,8 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
90
92
|
5. **Entry-point doc.** If `AGENTS.md` / `CLAUDE.md` already exist (recon), do **not**
|
|
91
93
|
overwrite — show the user and ask whether to merge or replace. Otherwise create `AGENTS.md`
|
|
92
94
|
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
|
|
95
|
+
(`ln -s AGENTS.md CLAUDE.md`). **Leave BOTH pointer slots (`workflow:methodology` +
|
|
96
|
+
`workflow:orchestration`) exactly as shipped — empty.** Filling them is the composition root's job.
|
|
95
97
|
6. **Deploy `docs/ai/`.** Create the files + `pages/` from
|
|
96
98
|
`${CLAUDE_SKILL_DIR}/references/templates/`. Keep each file's frontmatter.
|
|
97
99
|
7. **Fill templates** per the table below.
|
|
@@ -117,7 +119,7 @@ bootstrapping over a live system, but the user makes the final call.
|
|
|
117
119
|
> When the family composition root drives this substrate as part of a family bootstrap **or
|
|
118
120
|
> upgrade**, do the write steps (bootstrap 1–10 / upgrade 1–7: write `docs/ai/` + `AGENTS.md` +
|
|
119
121
|
> `.memory-version`) but **do NOT** run **any** commit gate and **do NOT** ask to commit — the
|
|
120
|
-
> composition root owns the **single** commit gate, raised after it injects the
|
|
122
|
+
> composition root owns the **single** commit gate, raised after it injects the two pointer slots.
|
|
121
123
|
> The three setup answers and the target dir are passed in by the composition root; you perform no
|
|
122
124
|
> commit and no slot injection. **Standalone** invocation keeps its own commit gate (bootstrap step
|
|
123
125
|
> 11 / upgrade step 7).
|
|
@@ -164,12 +166,12 @@ Fill strategy:
|
|
|
164
166
|
pre-1.1.0 deployment with no *Communication language* block, ask + insert
|
|
165
167
|
(`migrations/1.1.0-communication-language.md`); pre-1.2.0 with no *Attribution* block, ask +
|
|
166
168
|
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.
|
|
169
|
+
6. **Preserve BOTH pointer slots.** If `AGENTS.md` has the `workflow:methodology` and/or
|
|
170
|
+
`workflow:orchestration` markers, **never regenerate the file wholesale** — extract any bytes
|
|
171
|
+
between each pair and reinsert them unchanged. If a pair is absent (a legacy `AGENTS.md`),
|
|
172
|
+
gracefully **no-op** on that slot (adding a slot to already-deployed files is the composition
|
|
173
|
+
root's reconcile, not this substrate's job). On any malformed marker state (single, reversed,
|
|
174
|
+
nested, or duplicate pair), **no-op with an error** — never edit.
|
|
173
175
|
7. **Re-stamp** `docs/ai/.memory-version` to the lineage head (atomic write). Report changes;
|
|
174
176
|
**ask before committing** — **except in delegated mode** (see the *Delegated mode* note above),
|
|
175
177
|
where the composition root owns the single gate and this step raises none.
|
|
@@ -182,8 +184,9 @@ Fill strategy:
|
|
|
182
184
|
**working directory is the target project** — never write substrate files back into the skill.
|
|
183
185
|
- **Stamp = lineage head, not package version.** `.memory-version` carries `1.3.0` (the shared
|
|
184
186
|
`agent-workflow` lineage), not the npm `1.1.0`. They are independent axes.
|
|
185
|
-
- **
|
|
186
|
-
|
|
187
|
+
- **Both pointer slots ship empty and stay the user's.** Never author methodology or orchestration
|
|
188
|
+
text into them; on upgrade, preserve their content byte-for-byte. The composition root is their only
|
|
189
|
+
writer.
|
|
187
190
|
- **The `Co-Authored-By` trailer is added by the harness, not by prose.** When attribution is
|
|
188
191
|
`off` + Claude Code, also set `"includeCoAuthoredBy": false` in `.claude/settings.json`.
|
|
189
192
|
- **Hidden mode is project-local, never machine-global.** Memory's hide writes to the repo's
|
|
@@ -209,8 +212,8 @@ The three setup choices each have a full contract in
|
|
|
209
212
|
## References
|
|
210
213
|
|
|
211
214
|
- [`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
|
|
215
|
+
- [`references/templates/`](references/templates/) — stack-agnostic `AGENTS.md` (with the two empty
|
|
216
|
+
pointer slots — methodology + orchestration), `agent_rules.md`, and all `docs/ai/` files to deploy.
|
|
214
217
|
- [`references/scripts/`](references/scripts/) — the Node enforcement scripts (caps + staleness +
|
|
215
218
|
index-freshness gate, 3-tier archive, hook installer) and their unit tests.
|
|
216
219
|
- [`scripts/stamp-takeover.mjs`](scripts/stamp-takeover.mjs) — the upgrade-time lineage state
|
package/bin/install.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// One-shot installer for @sabaiway/agent-workflow-memory.
|
|
3
3
|
//
|
|
4
|
-
// npx @sabaiway/agent-workflow-memory init
|
|
4
|
+
// npx @sabaiway/agent-workflow-memory@latest init
|
|
5
5
|
//
|
|
6
6
|
// Copies the memory substrate into the canonical skill home
|
|
7
7
|
// (~/.claude/skills/agent-workflow-memory, override with --dir or
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// No telemetry, no phone-home. Dependency-free, Node >= 18.
|
|
17
17
|
|
|
18
18
|
import { readFile, mkdir, readdir, copyFile, lstat, readlink, symlink } from 'node:fs/promises';
|
|
19
|
-
import { existsSync, lstatSync } from 'node:fs';
|
|
19
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
20
20
|
import { dirname, join, resolve, relative, sep, isAbsolute } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { homedir } from 'node:os';
|
|
@@ -38,7 +38,11 @@ const PAYLOAD = [
|
|
|
38
38
|
'migrations',
|
|
39
39
|
];
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
// Collapse only a LEADING homedir() to "~" — anchored at the string start (boundary-checked with
|
|
42
|
+
// `sep`), never a mid-path occurrence. A naive `path.replace(homedir(), '~')` would rewrite the
|
|
43
|
+
// first match anywhere in the path (Issue-004). Exported so the regression can pin it in-process.
|
|
44
|
+
export const tildify = (path) =>
|
|
45
|
+
path === homedir() ? '~' : path.startsWith(homedir() + sep) ? `~${path.slice(homedir().length)}` : path;
|
|
42
46
|
|
|
43
47
|
const readVersion = async () => {
|
|
44
48
|
try {
|
|
@@ -65,9 +69,12 @@ const lstatNoFollow = (path) => {
|
|
|
65
69
|
// within `root`. Walks `root` plus each existing path component down to `dest`; if the
|
|
66
70
|
// root, an intermediate dir, or the leaf is a symlink (including a dangling one), a copy
|
|
67
71
|
// could escape the target — STOP rather than follow it. Also refuses a dest outside `root`.
|
|
68
|
-
const assertContainedRealPath = (root, dest) => {
|
|
72
|
+
export const assertContainedRealPath = (root, dest) => {
|
|
69
73
|
const rel = relative(root, dest);
|
|
70
|
-
|
|
74
|
+
// A true escape is `..` exactly or a `..`-prefixed PATH SEGMENT (`../x`) — NOT any string starting
|
|
75
|
+
// with the two chars "..": a legitimately-contained child literally named `..foo` has rel `..foo`,
|
|
76
|
+
// which the old `rel.startsWith('..')` wrongly rejected (Issue-004).
|
|
77
|
+
if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
71
78
|
throw new Error(`[agent-workflow-memory] refusing to write outside the target dir: ${dest}`);
|
|
72
79
|
}
|
|
73
80
|
if (lstatNoFollow(root)?.isSymbolicLink()) {
|
|
@@ -121,7 +128,7 @@ const printHelp = (version) => {
|
|
|
121
128
|
console.log(`agent-workflow-memory ${version}
|
|
122
129
|
|
|
123
130
|
Usage:
|
|
124
|
-
npx @sabaiway/agent-workflow-memory init [--dir <path>]
|
|
131
|
+
npx @sabaiway/agent-workflow-memory@latest init [--dir <path>]
|
|
125
132
|
npx @sabaiway/agent-workflow-memory --version
|
|
126
133
|
npx @sabaiway/agent-workflow-memory --help
|
|
127
134
|
|
|
@@ -179,7 +186,23 @@ This command only installs/updates the skill itself (in ${tildify(target)}).
|
|
|
179
186
|
To update it later, re-run: npx @sabaiway/agent-workflow-memory@latest init`);
|
|
180
187
|
};
|
|
181
188
|
|
|
182
|
-
main().
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
// Run main() only when executed directly (npx / node bin/install.mjs), never on import — so tests can
|
|
190
|
+
// import this module to unit-test its exported helpers with no side effects. Compare by REAL path:
|
|
191
|
+
// npx invokes the bin through a node_modules/.bin symlink, so process.argv[1] is that symlink while
|
|
192
|
+
// import.meta.url is the resolved real file — a raw string compare reads them as different and main()
|
|
193
|
+
// never runs. realpathSync collapses the symlink so both sides match. (Same idiom as the kit.)
|
|
194
|
+
const isDirectRun = (() => {
|
|
195
|
+
const invoked = process.argv[1];
|
|
196
|
+
if (!invoked) return false;
|
|
197
|
+
try {
|
|
198
|
+
return realpathSync(invoked) === realpathSync(fileURLToPath(import.meta.url));
|
|
199
|
+
} catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
})();
|
|
203
|
+
if (isDirectRun) {
|
|
204
|
+
main().catch((err) => {
|
|
205
|
+
console.error(err);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
|
208
|
+
}
|
package/bin/install.test.mjs
CHANGED
|
@@ -3,9 +3,10 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { mkdtemp, rm, mkdir, symlink, readdir } from 'node:fs/promises';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { dirname, join } from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { tmpdir, homedir } from 'node:os';
|
|
7
|
+
import { dirname, join, sep } from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
import { assertContainedRealPath, tildify } from './install.mjs';
|
|
9
10
|
|
|
10
11
|
const INSTALLER = join(dirname(fileURLToPath(import.meta.url)), 'install.mjs');
|
|
11
12
|
const runInstaller = (target) => spawnSync(process.execPath, [INSTALLER, '--dir', target], { encoding: 'utf8' });
|
|
@@ -74,3 +75,35 @@ describe('memory installer — payload + symlink-traversal hardening', () => {
|
|
|
74
75
|
assert.deepEqual(await readdir(real), [], 'nothing should be written through the symlinked target');
|
|
75
76
|
});
|
|
76
77
|
});
|
|
78
|
+
|
|
79
|
+
describe('memory installer — Issue-004 regressions (exported helpers, in-process)', () => {
|
|
80
|
+
it('assertContainedRealPath accepts a contained child literally named "..foo"', () => {
|
|
81
|
+
const root = join(tmpdir(), 'aw-mem-contain-root');
|
|
82
|
+
assert.doesNotThrow(() => assertContainedRealPath(root, join(root, '..foo')));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('assertContainedRealPath rejects a true `../` escape', () => {
|
|
86
|
+
const root = join(tmpdir(), 'aw-mem-contain-root');
|
|
87
|
+
assert.throws(() => assertContainedRealPath(root, join(root, '..', 'escape')), /outside the target dir/);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('tildify collapses only a LEADING homedir, never a mid-path occurrence', () => {
|
|
91
|
+
assert.equal(tildify(`${homedir()}${sep}skills${sep}x`), `~${sep}skills${sep}x`, 'leading home → ~');
|
|
92
|
+
const midPath = `${sep}tmp${homedir()}${sep}x`;
|
|
93
|
+
assert.equal(tildify(midPath), midPath, 'a mid-path home occurrence is left untouched');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('memory installer — module hygiene', () => {
|
|
98
|
+
it('importing install.mjs runs nothing (main() is guarded by isDirectRun)', () => {
|
|
99
|
+
const url = JSON.stringify(pathToFileURL(INSTALLER).href);
|
|
100
|
+
const res = spawnSync(
|
|
101
|
+
process.execPath,
|
|
102
|
+
['--input-type=module', '-e', `import(${url}).then(() => console.log('IMPORT_OK'));`],
|
|
103
|
+
{ encoding: 'utf8' },
|
|
104
|
+
);
|
|
105
|
+
assert.equal(res.status, 0, res.stderr);
|
|
106
|
+
assert.match(res.stdout, /IMPORT_OK/);
|
|
107
|
+
assert.doesNotMatch(res.stdout, /installed v|updated the substrate/);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/capability.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sabaiway/agent-workflow-memory",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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",
|
|
@@ -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 |
|
|
@@ -35,6 +35,17 @@ const ENFORCEMENT = join(SKILL_ROOT, 'references', 'scripts');
|
|
|
35
35
|
// from the composition root) so this substrate test stays self-contained and dependency-free.
|
|
36
36
|
const SLOT_START = '<!-- workflow:methodology:start -->';
|
|
37
37
|
const SLOT_END = '<!-- workflow:methodology:end -->';
|
|
38
|
+
const ORCH_START = '<!-- workflow:orchestration:start -->';
|
|
39
|
+
const ORCH_END = '<!-- workflow:orchestration:end -->';
|
|
40
|
+
// The deployed AGENTS.md line budget the composition root fills BOTH pointers inside (D-CAP). A
|
|
41
|
+
// representative single-line fragment per slot models what the composition root injects.
|
|
42
|
+
const AGENTS_MD_CAP = 100;
|
|
43
|
+
const lineCount = (text) => text.split('\n').length - (text.endsWith('\n') ? 1 : 0);
|
|
44
|
+
const extractPair = (text, start, end) => {
|
|
45
|
+
const a = text.indexOf(start);
|
|
46
|
+
const b = text.indexOf(end);
|
|
47
|
+
return a !== -1 && b !== -1 && b > a ? text.slice(a + start.length, b) : null;
|
|
48
|
+
};
|
|
38
49
|
|
|
39
50
|
const tempDirs = [];
|
|
40
51
|
const makeProject = () => {
|
|
@@ -98,14 +109,32 @@ describe('standalone substrate bootstrap (end-to-end, real temp project)', () =>
|
|
|
98
109
|
assert.ok(!existsSync(join(docsAi, '.workflow-version')), 'no composition-root stamp in a standalone bootstrap');
|
|
99
110
|
});
|
|
100
111
|
|
|
101
|
-
it('ships
|
|
112
|
+
it('ships BOTH pointer slots present-but-empty (methodology + orchestration)', () => {
|
|
102
113
|
const project = makeProject();
|
|
103
114
|
bootstrap(project);
|
|
104
115
|
|
|
105
116
|
const entry = readFileSync(join(project, 'AGENTS.md'), 'utf8');
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
assert.
|
|
109
|
-
assert.equal(
|
|
117
|
+
const meth = extractPair(entry, SLOT_START, SLOT_END);
|
|
118
|
+
const orch = extractPair(entry, ORCH_START, ORCH_END);
|
|
119
|
+
assert.notEqual(meth, null, 'an ordered methodology marker pair is present');
|
|
120
|
+
assert.equal(meth.trim(), '', 'the methodology slot is empty as shipped');
|
|
121
|
+
assert.notEqual(orch, null, 'an ordered orchestration marker pair is present');
|
|
122
|
+
assert.equal(orch.trim(), '', 'the orchestration slot is empty as shipped');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('stays ≤ the cap when the composition root fills BOTH pointer slots (D-CAP headroom)', () => {
|
|
126
|
+
const project = makeProject();
|
|
127
|
+
bootstrap(project);
|
|
128
|
+
const entry = readFileSync(join(project, 'AGENTS.md'), 'utf8');
|
|
129
|
+
assert.ok(lineCount(entry) <= AGENTS_MD_CAP, `shipped (empty) AGENTS.md is ${lineCount(entry)} lines (cap ${AGENTS_MD_CAP})`);
|
|
130
|
+
// Fill each one-line pointer the way the composition root does (replace the empty body), then re-count.
|
|
131
|
+
const fill = (text, start, end, body) => {
|
|
132
|
+
const a = text.indexOf(start);
|
|
133
|
+
const b = text.indexOf(end);
|
|
134
|
+
return `${text.slice(0, a + start.length)}\n${body}\n${text.slice(b)}`;
|
|
135
|
+
};
|
|
136
|
+
let filled = fill(entry, SLOT_START, SLOT_END, '> methodology pointer (one line)');
|
|
137
|
+
filled = fill(filled, ORCH_START, ORCH_END, '> orchestration recipes pointer (one line)');
|
|
138
|
+
assert.ok(lineCount(filled) <= AGENTS_MD_CAP, `dual-filled AGENTS.md is ${lineCount(filled)} lines (cap ${AGENTS_MD_CAP})`);
|
|
110
139
|
});
|
|
111
140
|
});
|