@limina-labs/momentum 0.35.0 → 0.36.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/adapters/antigravity/instructions/AGENTS.md +6 -2
- package/adapters/antigravity/instructions/manifest.json +8 -0
- package/adapters/claude-code/instructions/manifest.json +8 -0
- package/adapters/codex/instructions/AGENTS.md +6 -2
- package/adapters/codex/instructions/manifest.json +8 -0
- package/adapters/opencode/instructions/AGENTS.md +6 -2
- package/adapters/opencode/instructions/manifest.json +8 -0
- package/bin/ecosystem.js +11 -1
- package/bin/momentum.js +56 -10
- package/core/instructions/README.md +69 -0
- package/core/instructions/rules-body.md +2 -2
- package/core/lib/instruction-compose.js +175 -0
- package/core/specs-templates/CLAUDE.md +7 -3
- package/package.json +1 -1
- package/adapters/antigravity/instructions/header.md +0 -3
- package/adapters/antigravity/instructions/vars.json +0 -1
- package/adapters/claude-code/instructions/header.md +0 -3
- package/adapters/claude-code/instructions/vars.json +0 -1
- package/adapters/codex/instructions/header.md +0 -3
- package/adapters/codex/instructions/vars.json +0 -1
- package/adapters/opencode/instructions/header.md +0 -3
- package/adapters/opencode/instructions/vars.json +0 -1
|
@@ -176,6 +176,10 @@ lives in this AGENTS.md primary-instruction text as a belt-and-braces
|
|
|
176
176
|
fallback: if a `.momentum/inbox/handoff-NNN.md` file exists at session
|
|
177
177
|
start, read it and acknowledge before continuing.
|
|
178
178
|
|
|
179
|
+
## In-Session Task Tool
|
|
180
|
+
|
|
181
|
+
When a rule mentions tracking in-session progress (Rule 2), Antigravity's task tool is the native **task list** artifact (task.md) — in-session only; the durable record is `specs/phases/<phase>/tasks.md`.
|
|
182
|
+
|
|
179
183
|
## Autonomous Behaviors (Always-On Rules)
|
|
180
184
|
|
|
181
185
|
### Rule 1: Always Orient First
|
|
@@ -197,7 +201,7 @@ After completing ANY meaningful work, automatically update:
|
|
|
197
201
|
2. **`specs/status.md`** — if phase progress, blockers, or P0 items changed (touch only your own lane's row — Rule 15)
|
|
198
202
|
3. **`specs/changelog/YYYY-MM.md`** — log what changed (one line per change, append-only)
|
|
199
203
|
|
|
200
|
-
Use
|
|
204
|
+
Use your in-session task-tracking tool (named in the "In-Session Task Tool" note above) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
|
|
201
205
|
|
|
202
206
|
#### Why
|
|
203
207
|
Tracking debt compounds invisibly. A task list one day stale is recoverable; one week stale is fiction. Status drift is how phases silently lose direction.
|
|
@@ -215,7 +219,7 @@ Tracking debt compounds invisibly. A task list one day stale is recoverable; one
|
|
|
215
219
|
#### Anti-Rationalization Counters
|
|
216
220
|
|
|
217
221
|
- "It's faster to do the work first and track at the end" — wrong: reconstruction takes 2-3× longer than real-time logging.
|
|
218
|
-
- "
|
|
222
|
+
- "Your in-session task-tracking tool is enough" — it is in-session only; `tasks.md` is the durable record.
|
|
219
223
|
- "Mid-task tracking interrupts flow" — a one-line update costs <30s; reconstructing a day later costs 30 minutes.
|
|
220
224
|
|
|
221
225
|
### Rule 3: Auto-Track Discoveries
|
|
@@ -230,6 +230,10 @@ This is the documented escalation point referenced in Phase 18 G1
|
|
|
230
230
|
plan.md; if the MCP shim is widely broken, momentum will ship a
|
|
231
231
|
minimal `core/swarm/mcp-cwd-server.js` in a follow-up.
|
|
232
232
|
|
|
233
|
+
## In-Session Task Tool
|
|
234
|
+
|
|
235
|
+
When a rule mentions tracking in-session progress (Rule 2), Codex's task tool is the built-in **plan** tool — in-session only; the durable record is `specs/phases/<phase>/tasks.md`.
|
|
236
|
+
|
|
233
237
|
## Autonomous Behaviors (Always-On Rules)
|
|
234
238
|
|
|
235
239
|
### Rule 1: Always Orient First
|
|
@@ -251,7 +255,7 @@ After completing ANY meaningful work, automatically update:
|
|
|
251
255
|
2. **`specs/status.md`** — if phase progress, blockers, or P0 items changed (touch only your own lane's row — Rule 15)
|
|
252
256
|
3. **`specs/changelog/YYYY-MM.md`** — log what changed (one line per change, append-only)
|
|
253
257
|
|
|
254
|
-
Use
|
|
258
|
+
Use your in-session task-tracking tool (named in the "In-Session Task Tool" note above) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
|
|
255
259
|
|
|
256
260
|
#### Why
|
|
257
261
|
Tracking debt compounds invisibly. A task list one day stale is recoverable; one week stale is fiction. Status drift is how phases silently lose direction.
|
|
@@ -269,7 +273,7 @@ Tracking debt compounds invisibly. A task list one day stale is recoverable; one
|
|
|
269
273
|
#### Anti-Rationalization Counters
|
|
270
274
|
|
|
271
275
|
- "It's faster to do the work first and track at the end" — wrong: reconstruction takes 2-3× longer than real-time logging.
|
|
272
|
-
- "
|
|
276
|
+
- "Your in-session task-tracking tool is enough" — it is in-session only; `tasks.md` is the durable record.
|
|
273
277
|
- "Mid-task tracking interrupts flow" — a one-line update costs <30s; reconstructing a day later costs 30 minutes.
|
|
274
278
|
|
|
275
279
|
### Rule 3: Auto-Track Discoveries
|
|
@@ -143,6 +143,10 @@ skill paths (`.claude/skills/`, `.agents/skills/`); momentum's opencode
|
|
|
143
143
|
surfaces live under `.opencode/` so multi-adapter installs stay
|
|
144
144
|
side-by-side without collisions.
|
|
145
145
|
|
|
146
|
+
## In-Session Task Tool
|
|
147
|
+
|
|
148
|
+
When a rule mentions tracking in-session progress (Rule 2), opencode's task tool is the built-in **task** tool (subagent fan-out) — in-session only; the durable record is `specs/phases/<phase>/tasks.md`.
|
|
149
|
+
|
|
146
150
|
## Autonomous Behaviors (Always-On Rules)
|
|
147
151
|
|
|
148
152
|
### Rule 1: Always Orient First
|
|
@@ -164,7 +168,7 @@ After completing ANY meaningful work, automatically update:
|
|
|
164
168
|
2. **`specs/status.md`** — if phase progress, blockers, or P0 items changed (touch only your own lane's row — Rule 15)
|
|
165
169
|
3. **`specs/changelog/YYYY-MM.md`** — log what changed (one line per change, append-only)
|
|
166
170
|
|
|
167
|
-
Use
|
|
171
|
+
Use your in-session task-tracking tool (named in the "In-Session Task Tool" note above) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
|
|
168
172
|
|
|
169
173
|
#### Why
|
|
170
174
|
Tracking debt compounds invisibly. A task list one day stale is recoverable; one week stale is fiction. Status drift is how phases silently lose direction.
|
|
@@ -182,7 +186,7 @@ Tracking debt compounds invisibly. A task list one day stale is recoverable; one
|
|
|
182
186
|
#### Anti-Rationalization Counters
|
|
183
187
|
|
|
184
188
|
- "It's faster to do the work first and track at the end" — wrong: reconstruction takes 2-3× longer than real-time logging.
|
|
185
|
-
- "
|
|
189
|
+
- "Your in-session task-tracking tool is enough" — it is in-session only; `tasks.md` is the durable record.
|
|
186
190
|
- "Mid-task tracking interrupts flow" — a one-line update costs <30s; reconstructing a day later costs 30 minutes.
|
|
187
191
|
|
|
188
192
|
### Rule 3: Auto-Track Discoveries
|
package/bin/ecosystem.js
CHANGED
|
@@ -777,9 +777,18 @@ function detectMemberAgent(repoPath) {
|
|
|
777
777
|
const m = JSON.parse(
|
|
778
778
|
fs.readFileSync(path.join(repoPath, '.momentum', 'installed.json'), 'utf8'),
|
|
779
779
|
);
|
|
780
|
-
|
|
780
|
+
// ADR-0007: the lock is `{ agents: { <id>: … } }`; the pre-0007 lock was
|
|
781
|
+
// `{ agent }`. Read the map first (else every AGENTS.md agent — opencode,
|
|
782
|
+
// Antigravity, Codex — falls through to the `AGENTS.md ⇒ codex` heuristic
|
|
783
|
+
// below and is misidentified). ADR-0011.
|
|
784
|
+
if (m && m.agents && typeof m.agents === 'object') {
|
|
785
|
+
const ids = Object.keys(m.agents);
|
|
786
|
+
if (ids.length) return ids.includes('claude-code') ? 'claude-code' : ids.sort()[0];
|
|
787
|
+
}
|
|
788
|
+
if (m && m.agent) return m.agent; // legacy single-agent lock
|
|
781
789
|
} catch (_e) { /* fall through to heuristic */ }
|
|
782
790
|
if (fs.existsSync(path.join(repoPath, '.codex'))) return 'codex';
|
|
791
|
+
if (fs.existsSync(path.join(repoPath, '.opencode'))) return 'opencode';
|
|
783
792
|
if (fs.existsSync(path.join(repoPath, '.claude'))) return 'claude-code';
|
|
784
793
|
if (
|
|
785
794
|
fs.existsSync(path.join(repoPath, '.agents', 'hooks.json')) ||
|
|
@@ -1297,6 +1306,7 @@ module.exports = {
|
|
|
1297
1306
|
ensurePointerInjected,
|
|
1298
1307
|
stripPointer,
|
|
1299
1308
|
findPrimaryInstructionFile,
|
|
1309
|
+
detectMemberAgent,
|
|
1300
1310
|
sanitizeId,
|
|
1301
1311
|
POINTER_BEGIN,
|
|
1302
1312
|
POINTER_END,
|
package/bin/momentum.js
CHANGED
|
@@ -719,7 +719,46 @@ function resolveAdapterSource(srcRoot, adapterDir, fileSpec) {
|
|
|
719
719
|
return path.join(base, ...fileSpec.source);
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
|
|
722
|
+
/**
|
|
723
|
+
* ADR-0011 — the raw primary-instruction template content for `agent`.
|
|
724
|
+
* A claude-md agent (and a lone installed agents-md agent) gets the static
|
|
725
|
+
* committed template. When MORE THAN ONE agents-md agent is installed, AGENTS.md
|
|
726
|
+
* is COMPOSED — the neutral spine once + one integration section per installed
|
|
727
|
+
* AGENTS.md agent — so switching or running several agents never drops an
|
|
728
|
+
* integration (fixes the same-path collision). Project name is not yet rendered.
|
|
729
|
+
*/
|
|
730
|
+
function resolvePrimaryInstructionContent(srcRoot, targetDir, agent, srcPath) {
|
|
731
|
+
const readStatic = () => fs.readFileSync(srcPath, 'utf8');
|
|
732
|
+
let compose;
|
|
733
|
+
try {
|
|
734
|
+
compose = require('../core/lib/instruction-compose');
|
|
735
|
+
} catch (_e) {
|
|
736
|
+
return readStatic();
|
|
737
|
+
}
|
|
738
|
+
let manifest;
|
|
739
|
+
try {
|
|
740
|
+
manifest = compose.readManifest(srcRoot, agent);
|
|
741
|
+
} catch (_e) {
|
|
742
|
+
return readStatic(); // unknown / manifest-less agent → static template
|
|
743
|
+
}
|
|
744
|
+
if (manifest.surface !== 'agents-md') return readStatic(); // claude-md → static
|
|
745
|
+
|
|
746
|
+
// The set of installed AGENTS.md agents ∪ the one being (re)installed.
|
|
747
|
+
const valid = new Set(compose.discoverAgents(srcRoot));
|
|
748
|
+
const ids = new Set(valid.has(agent) ? [agent] : []);
|
|
749
|
+
const state = loadInstalledState(targetDir);
|
|
750
|
+
for (const id of Object.keys(state.agents || {})) {
|
|
751
|
+
if (!valid.has(id)) continue;
|
|
752
|
+
try {
|
|
753
|
+
if (compose.readManifest(srcRoot, id).surface === 'agents-md') ids.add(id);
|
|
754
|
+
} catch (_e) { /* skip an agent we can't read */ }
|
|
755
|
+
}
|
|
756
|
+
const sorted = [...ids].sort();
|
|
757
|
+
if (sorted.length <= 1) return readStatic(); // N=1 → static committed template
|
|
758
|
+
return compose.composeInstruction(srcRoot, sorted); // N>1 → composed
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function installPrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstruction, agent) {
|
|
723
762
|
if (!primaryInstruction) return null;
|
|
724
763
|
const srcPath = resolveAdapterSource(srcRoot, adapterDir, primaryInstruction);
|
|
725
764
|
const destPath = path.join(targetDir, ...primaryInstruction.destination);
|
|
@@ -733,7 +772,7 @@ function installPrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstru
|
|
|
733
772
|
return 'added';
|
|
734
773
|
}
|
|
735
774
|
const rendered = renderProjectName(
|
|
736
|
-
|
|
775
|
+
resolvePrimaryInstructionContent(srcRoot, targetDir, agent, srcPath),
|
|
737
776
|
getProjectName(targetDir)
|
|
738
777
|
);
|
|
739
778
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
@@ -745,7 +784,7 @@ function installPrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstru
|
|
|
745
784
|
return 'skipped';
|
|
746
785
|
}
|
|
747
786
|
|
|
748
|
-
function upgradePrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstruction) {
|
|
787
|
+
function upgradePrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstruction, agent) {
|
|
749
788
|
if (!primaryInstruction) return null;
|
|
750
789
|
const srcPath = resolveAdapterSource(srcRoot, adapterDir, primaryInstruction);
|
|
751
790
|
const destPath = path.join(targetDir, ...primaryInstruction.destination);
|
|
@@ -753,12 +792,15 @@ function upgradePrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstru
|
|
|
753
792
|
const projectName = getProjectName(targetDir);
|
|
754
793
|
recordManaged(destPath); // managed (marker-owned)
|
|
755
794
|
|
|
795
|
+
// ADR-0011: compose AGENTS.md across all installed AGENTS.md agents.
|
|
796
|
+
const rawContent = resolvePrimaryInstructionContent(srcRoot, targetDir, agent, srcPath);
|
|
797
|
+
|
|
756
798
|
console.log(`→ Upgrading ${label}...`);
|
|
757
799
|
if (primaryInstruction.markerAware) {
|
|
758
|
-
return upgradeMarkedFile(srcPath, destPath, label, targetDir, projectName);
|
|
800
|
+
return upgradeMarkedFile(srcPath, destPath, label, targetDir, projectName, rawContent);
|
|
759
801
|
}
|
|
760
802
|
|
|
761
|
-
const srcContent = renderProjectName(
|
|
803
|
+
const srcContent = renderProjectName(rawContent, projectName);
|
|
762
804
|
const relDest = path.relative(targetDir, destPath);
|
|
763
805
|
if (!fileExists(destPath)) {
|
|
764
806
|
if (_dryRun) {
|
|
@@ -895,11 +937,14 @@ function partitionByMarker(content) {
|
|
|
895
937
|
*
|
|
896
938
|
* Returns one of: 'added', 'updated', 'unchanged', 'migrated'.
|
|
897
939
|
*/
|
|
898
|
-
function upgradeMarkedFile(srcPath, destPath, label, root, projectName) {
|
|
940
|
+
function upgradeMarkedFile(srcPath, destPath, label, root, projectName, srcContentRaw) {
|
|
899
941
|
const rel = path.relative(root || process.cwd(), destPath);
|
|
900
942
|
recordManaged(destPath); // managed (marker-owned) in all branches
|
|
901
943
|
const render = (content) =>
|
|
902
944
|
projectName ? renderProjectName(content, projectName) : content;
|
|
945
|
+
// ADR-0011: callers may pass composed content (multi-agent AGENTS.md); else
|
|
946
|
+
// read the static committed template.
|
|
947
|
+
const rawSrc = srcContentRaw != null ? srcContentRaw : fs.readFileSync(srcPath, 'utf8');
|
|
903
948
|
|
|
904
949
|
if (!fileExists(destPath)) {
|
|
905
950
|
if (_dryRun) {
|
|
@@ -907,12 +952,12 @@ function upgradeMarkedFile(srcPath, destPath, label, root, projectName) {
|
|
|
907
952
|
return 'added';
|
|
908
953
|
}
|
|
909
954
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
910
|
-
fs.writeFileSync(destPath, render(
|
|
955
|
+
fs.writeFileSync(destPath, render(rawSrc));
|
|
911
956
|
console.log(` + added: ${rel}`);
|
|
912
957
|
return 'added';
|
|
913
958
|
}
|
|
914
959
|
|
|
915
|
-
const srcContent = render(
|
|
960
|
+
const srcContent = render(rawSrc);
|
|
916
961
|
const destContent = fs.readFileSync(destPath, 'utf8');
|
|
917
962
|
const destParts = partitionByMarker(destContent);
|
|
918
963
|
|
|
@@ -1215,7 +1260,7 @@ function init(targetDir, agent, opts = {}) {
|
|
|
1215
1260
|
console.log('→ Inferring project config...');
|
|
1216
1261
|
installConfig(target, { dryRun: _dryRun });
|
|
1217
1262
|
|
|
1218
|
-
installPrimaryInstruction(src, target, adapterDir, adapter.primaryInstruction);
|
|
1263
|
+
installPrimaryInstruction(src, target, adapterDir, adapter.primaryInstruction, agent);
|
|
1219
1264
|
|
|
1220
1265
|
// Adapter overlay — per-agent commands/agent-rules/scripts (additive)
|
|
1221
1266
|
applyOverlay(adapterDir, target, dests, { backup: true, root: target });
|
|
@@ -1381,7 +1426,8 @@ function upgrade(targetDir, agent, opts = {}) {
|
|
|
1381
1426
|
src,
|
|
1382
1427
|
target,
|
|
1383
1428
|
adapterDir,
|
|
1384
|
-
adapter.primaryInstruction
|
|
1429
|
+
adapter.primaryInstruction,
|
|
1430
|
+
agent
|
|
1385
1431
|
);
|
|
1386
1432
|
|
|
1387
1433
|
if (hadPointerBlock && !_dryRun) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Instruction sources — the three-tier variation model (ADR-0011)
|
|
2
|
+
|
|
3
|
+
The primary instruction file each agent auto-loads (`CLAUDE.md` for Claude Code,
|
|
4
|
+
`AGENTS.md` for every AGENTS.md-standard agent) is **generated**, never
|
|
5
|
+
hand-edited. It is a projection of **(principal constants × per-agent variation
|
|
6
|
+
manifest)**. Regenerate with `npm run generate-instructions`; the suite's drift
|
|
7
|
+
guard fails if a committed template diverges from its sources.
|
|
8
|
+
|
|
9
|
+
## The three tiers
|
|
10
|
+
|
|
11
|
+
| Tier | What | Where | Rule |
|
|
12
|
+
|------|------|-------|------|
|
|
13
|
+
| **1 — Principal constants** (the neutral spine) | Navigation table, Rules 1–15, Naming, Constraints, Project Extensions pointer | `core/instructions/navigation.md`, `core/instructions/rules-body.md` | **Byte-identical** across every agent + surface (`CLAUDE.md` ≡ `AGENTS.md` body) |
|
|
14
|
+
| **2 — Variation manifest** | Everything that varies per agent | `adapters/<agent>/instructions/manifest.json` | One declarative record; generator auto-discovers it (OCP) |
|
|
15
|
+
| **3 — Surface delta** | Irreducibly agent-specific integration prose + the task-tool mapping line | `adapters/<agent>/instructions/surfaces.md` (optional) | A scoped delta, never the file's whole identity (SRP) |
|
|
16
|
+
|
|
17
|
+
The rules body carries **no agent-specific token** — the task tool is named only
|
|
18
|
+
in the Tier-3 delta, which every agent still loads. That is what makes the spine
|
|
19
|
+
byte-identical (and therefore a testable invariant, not a claim).
|
|
20
|
+
|
|
21
|
+
## `manifest.json` schema
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"id": "opencode",
|
|
26
|
+
"displayName": "opencode",
|
|
27
|
+
"surface": "agents-md",
|
|
28
|
+
"taskTool": "the built-in **task** tool (subagent fan-out)",
|
|
29
|
+
"taskToolName": "The task tool",
|
|
30
|
+
"hasSurfaceDelta": true
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| Field | Type | Meaning |
|
|
35
|
+
|-------|------|---------|
|
|
36
|
+
| `id` | string | Adapter id — must match the `adapters/<id>/` directory name |
|
|
37
|
+
| `displayName` | string | Human name used in the header scaffold + integration heading |
|
|
38
|
+
| `surface` | `"claude-md"` \| `"agents-md"` | Which instruction file this agent reads; drives the install destination |
|
|
39
|
+
| `taskTool` | string | Rendered into the Tier-3 task-tool mapping line (may contain markdown) |
|
|
40
|
+
| `taskToolName` | string | Capitalized short form for prose references |
|
|
41
|
+
| `hasSurfaceDelta` | boolean | Whether a `surfaces.md` integration delta exists for this agent |
|
|
42
|
+
|
|
43
|
+
## Assembly order
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
header (scaffold, display name from manifest)
|
|
47
|
+
→ navigation (Tier 1)
|
|
48
|
+
→ surfaces delta (Tier 3, if hasSurfaceDelta)
|
|
49
|
+
→ rules body (Tier 1, agent-neutral)
|
|
50
|
+
→ Project Extensions pointer tail (ADR-0010)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Adding a new agent (the OCP path)
|
|
54
|
+
|
|
55
|
+
1. Create `adapters/<id>/instructions/manifest.json`.
|
|
56
|
+
2. Optionally add `adapters/<id>/instructions/surfaces.md` (integration delta).
|
|
57
|
+
3. Run `npm run generate-instructions`.
|
|
58
|
+
|
|
59
|
+
No edit to `scripts/generate-instructions.js` is required — it discovers
|
|
60
|
+
adapters by scanning `adapters/*/instructions/`. A suite test proves this by
|
|
61
|
+
generating for a synthetic adapter with zero generator edits.
|
|
62
|
+
|
|
63
|
+
## AGENTS.md when multiple AGENTS.md agents are installed
|
|
64
|
+
|
|
65
|
+
`AGENTS.md` is a shared standard, so one repo can host several AGENTS.md agents.
|
|
66
|
+
The committed per-agent template is the single-agent (N=1) projection. At
|
|
67
|
+
install/upgrade, when `installed.json.agents` holds more than one `agents-md`
|
|
68
|
+
agent, `AGENTS.md` is **composed**: the neutral spine once, plus one integration
|
|
69
|
+
section per installed AGENTS.md agent. `CLAUDE.md` is unaffected (Claude-only).
|
|
@@ -19,7 +19,7 @@ After completing ANY meaningful work, automatically update:
|
|
|
19
19
|
2. **`specs/status.md`** — if phase progress, blockers, or P0 items changed (touch only your own lane's row — Rule 15)
|
|
20
20
|
3. **`specs/changelog/YYYY-MM.md`** — log what changed (one line per change, append-only)
|
|
21
21
|
|
|
22
|
-
Use
|
|
22
|
+
Use your in-session task-tracking tool (named in the "In-Session Task Tool" note above) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
|
|
23
23
|
|
|
24
24
|
#### Why
|
|
25
25
|
Tracking debt compounds invisibly. A task list one day stale is recoverable; one week stale is fiction. Status drift is how phases silently lose direction.
|
|
@@ -37,7 +37,7 @@ Tracking debt compounds invisibly. A task list one day stale is recoverable; one
|
|
|
37
37
|
#### Anti-Rationalization Counters
|
|
38
38
|
|
|
39
39
|
- "It's faster to do the work first and track at the end" — wrong: reconstruction takes 2-3× longer than real-time logging.
|
|
40
|
-
- "
|
|
40
|
+
- "Your in-session task-tracking tool is enough" — it is in-session only; `tasks.md` is the durable record.
|
|
41
41
|
- "Mid-task tracking interrupts flow" — a one-line update costs <30s; reconstructing a day later costs 30 minutes.
|
|
42
42
|
|
|
43
43
|
### Rule 3: Auto-Track Discoveries
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ADR-0011 — the instruction file is a projection of
|
|
4
|
+
// (principal constants × per-agent variation manifest).
|
|
5
|
+
//
|
|
6
|
+
// Pure assembly logic, shipped in core/ so BOTH build-time generation
|
|
7
|
+
// (scripts/generate-instructions.js) and install-time multi-agent AGENTS.md
|
|
8
|
+
// composition (bin/momentum.js) use ONE code path. Every function takes
|
|
9
|
+
// `root` = the momentum package/repo root that holds core/ + adapters/.
|
|
10
|
+
//
|
|
11
|
+
// Three tiers:
|
|
12
|
+
// 1. Principal constants (neutral spine): core/instructions/navigation.md +
|
|
13
|
+
// rules-body.md — byte-identical across every agent + surface.
|
|
14
|
+
// 2. Variation manifest: adapters/<id>/instructions/manifest.json.
|
|
15
|
+
// 3. Surface delta: adapters/<id>/instructions/surfaces.md (optional).
|
|
16
|
+
//
|
|
17
|
+
// Assembly:
|
|
18
|
+
// marker → header → navigation → (per agent: surfaces? + task-tool note)
|
|
19
|
+
// → neutral rules body → Project Extensions pointer tail (ADR-0010)
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
const MARKER_COMMENT = [
|
|
25
|
+
'<!-- momentum-managed (generated) — regenerate with `npm run generate-instructions`',
|
|
26
|
+
' in the momentum repo; sources: core/instructions/ + adapters/<agent>/instructions/.',
|
|
27
|
+
" Everything above '## Project Extensions' may be replaced by `momentum upgrade`. -->",
|
|
28
|
+
].join('\n');
|
|
29
|
+
|
|
30
|
+
// ADR-0010: `## Project Extensions` is a managed POINTER to specs/project-rules.md,
|
|
31
|
+
// not an authoring surface. Kept byte-identical to core/lib/project-rules.js.
|
|
32
|
+
const EXTENSIONS_TAIL = [
|
|
33
|
+
'---',
|
|
34
|
+
'',
|
|
35
|
+
'## Project Extensions',
|
|
36
|
+
'',
|
|
37
|
+
'<!-- momentum:project-rules-pointer -->',
|
|
38
|
+
'> **Project-specific rules live in `specs/project-rules.md`** — read it now.',
|
|
39
|
+
'> Session-start self-audits, project constraints, and any project-specific',
|
|
40
|
+
'> guidance are there, shared identically by every agent (ADR-0010). This',
|
|
41
|
+
'> section is a momentum-managed pointer; edit `specs/project-rules.md`, not',
|
|
42
|
+
'> this file.',
|
|
43
|
+
].join('\n');
|
|
44
|
+
|
|
45
|
+
function readFragment(root, rel) {
|
|
46
|
+
return fs.readFileSync(path.join(root, ...rel), 'utf8');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Discover every adapter that ships an instruction manifest (OCP: add a dir, no edits). */
|
|
50
|
+
function discoverAgents(root) {
|
|
51
|
+
const dir = path.join(root, 'adapters');
|
|
52
|
+
return fs
|
|
53
|
+
.readdirSync(dir)
|
|
54
|
+
.filter((id) => fs.existsSync(path.join(dir, id, 'instructions', 'manifest.json')))
|
|
55
|
+
.sort();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readManifest(root, id) {
|
|
59
|
+
const m = JSON.parse(readFragment(root, ['adapters', id, 'instructions', 'manifest.json']));
|
|
60
|
+
for (const key of ['id', 'displayName', 'surface', 'taskTool', 'taskToolName']) {
|
|
61
|
+
if (typeof m[key] !== 'string' || !m[key]) {
|
|
62
|
+
throw new Error(`adapters/${id}/instructions/manifest.json: missing/invalid "${key}"`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (m.id !== id) throw new Error(`manifest id "${m.id}" != adapter dir "${id}"`);
|
|
66
|
+
if (m.surface !== 'claude-md' && m.surface !== 'agents-md') {
|
|
67
|
+
throw new Error(`adapters/${id}: surface "${m.surface}" — expected claude-md | agents-md`);
|
|
68
|
+
}
|
|
69
|
+
return m;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The install destination filename implied by the surface. */
|
|
73
|
+
function surfaceDestination(surface) {
|
|
74
|
+
if (surface === 'claude-md') return 'CLAUDE.md';
|
|
75
|
+
if (surface === 'agents-md') return 'AGENTS.md';
|
|
76
|
+
throw new Error(`unknown surface "${surface}"`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Where this agent's committed (N=1) template lives, derived from its surface. */
|
|
80
|
+
function templatePath(id, surface) {
|
|
81
|
+
if (surface === 'claude-md') return ['core', 'specs-templates', 'CLAUDE.md'];
|
|
82
|
+
if (surface === 'agents-md') return ['adapters', id, 'instructions', 'AGENTS.md'];
|
|
83
|
+
throw new Error(`unknown surface "${surface}" for adapter "${id}"`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Header: single scaffold constant, agent display name(s) the only variable. */
|
|
87
|
+
function renderHeader(manifests) {
|
|
88
|
+
const title = '# Project Rules: <Project Name>';
|
|
89
|
+
if (manifests.length === 1) {
|
|
90
|
+
return `${title}\n\n> ${manifests[0].displayName} configuration for this momentum-managed project.`;
|
|
91
|
+
}
|
|
92
|
+
const names = manifests.map((m) => m.displayName).join(', ');
|
|
93
|
+
return (
|
|
94
|
+
`${title}\n\n> Configuration for this momentum-managed project. This shared ` +
|
|
95
|
+
`AGENTS.md serves ${manifests.length} agents (${names}); each has its own ` +
|
|
96
|
+
`integration section below.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The one agent-specific line lifted out of the neutral spine (SRP). */
|
|
101
|
+
function renderTaskToolNote(m) {
|
|
102
|
+
return [
|
|
103
|
+
'## In-Session Task Tool',
|
|
104
|
+
'',
|
|
105
|
+
`When a rule mentions tracking in-session progress (Rule 2), ${m.displayName}'s ` +
|
|
106
|
+
`task tool is ${m.taskTool} — in-session only; the durable record is ` +
|
|
107
|
+
'`specs/phases/<phase>/tasks.md`.',
|
|
108
|
+
].join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readSurfaces(root, m) {
|
|
112
|
+
if (!m.hasSurfaceDelta) return null;
|
|
113
|
+
const p = path.join(root, 'adapters', m.id, 'instructions', 'surfaces.md');
|
|
114
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function finalize(parts) {
|
|
118
|
+
const content = parts.map((p) => p.replace(/\s+$/, '')).join('\n\n') + '\n';
|
|
119
|
+
const leftover = content.match(/\{\{[A-Z_]+\}\}/);
|
|
120
|
+
if (leftover) throw new Error(`unrendered placeholder ${leftover[0]}`);
|
|
121
|
+
return content;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Compose an instruction file for one or more agents.
|
|
126
|
+
* - Single id → the committed N=1 projection (a static template).
|
|
127
|
+
* - Multiple ids → neutral spine + one integration section per agent
|
|
128
|
+
* (install-time AGENTS.md when several AGENTS.md agents are installed).
|
|
129
|
+
*/
|
|
130
|
+
function composeInstruction(root, ids) {
|
|
131
|
+
if (!ids.length) throw new Error('composeInstruction: no agents');
|
|
132
|
+
const manifests = ids.map((id) => readManifest(root, id));
|
|
133
|
+
const navigation = readFragment(root, ['core', 'instructions', 'navigation.md']);
|
|
134
|
+
const body = readFragment(root, ['core', 'instructions', 'rules-body.md']);
|
|
135
|
+
|
|
136
|
+
const parts = [MARKER_COMMENT, renderHeader(manifests), navigation];
|
|
137
|
+
for (const m of manifests) {
|
|
138
|
+
const surfaces = readSurfaces(root, m);
|
|
139
|
+
if (surfaces) parts.push(surfaces);
|
|
140
|
+
parts.push(renderTaskToolNote(m));
|
|
141
|
+
}
|
|
142
|
+
parts.push(body, EXTENSIONS_TAIL);
|
|
143
|
+
return finalize(parts);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** One committed template descriptor for a single agent. */
|
|
147
|
+
function generateFor(root, id) {
|
|
148
|
+
const m = readManifest(root, id);
|
|
149
|
+
const target = templatePath(id, m.surface);
|
|
150
|
+
return {
|
|
151
|
+
agent: id,
|
|
152
|
+
targetRel: target.join('/'),
|
|
153
|
+
targetPath: path.join(root, ...target),
|
|
154
|
+
content: composeInstruction(root, [id]),
|
|
155
|
+
manifest: m,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function generateAll(root) {
|
|
160
|
+
return discoverAgents(root).map((id) => generateFor(root, id));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
MARKER_COMMENT,
|
|
165
|
+
EXTENSIONS_TAIL,
|
|
166
|
+
discoverAgents,
|
|
167
|
+
readManifest,
|
|
168
|
+
surfaceDestination,
|
|
169
|
+
templatePath,
|
|
170
|
+
renderHeader,
|
|
171
|
+
renderTaskToolNote,
|
|
172
|
+
composeInstruction,
|
|
173
|
+
generateFor,
|
|
174
|
+
generateAll,
|
|
175
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Project Rules: <Project Name>
|
|
6
6
|
|
|
7
|
-
> Claude Code configuration for this project.
|
|
7
|
+
> Claude Code configuration for this momentum-managed project.
|
|
8
8
|
|
|
9
9
|
## Navigation (Where to Find Things)
|
|
10
10
|
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## In-Session Task Tool
|
|
26
|
+
|
|
27
|
+
When a rule mentions tracking in-session progress (Rule 2), Claude Code's task tool is the built-in **TodoWrite** tool — in-session only; the durable record is `specs/phases/<phase>/tasks.md`.
|
|
28
|
+
|
|
25
29
|
## Autonomous Behaviors (Always-On Rules)
|
|
26
30
|
|
|
27
31
|
### Rule 1: Always Orient First
|
|
@@ -43,7 +47,7 @@ After completing ANY meaningful work, automatically update:
|
|
|
43
47
|
2. **`specs/status.md`** — if phase progress, blockers, or P0 items changed (touch only your own lane's row — Rule 15)
|
|
44
48
|
3. **`specs/changelog/YYYY-MM.md`** — log what changed (one line per change, append-only)
|
|
45
49
|
|
|
46
|
-
Use
|
|
50
|
+
Use your in-session task-tracking tool (named in the "In-Session Task Tool" note above) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
|
|
47
51
|
|
|
48
52
|
#### Why
|
|
49
53
|
Tracking debt compounds invisibly. A task list one day stale is recoverable; one week stale is fiction. Status drift is how phases silently lose direction.
|
|
@@ -61,7 +65,7 @@ Tracking debt compounds invisibly. A task list one day stale is recoverable; one
|
|
|
61
65
|
#### Anti-Rationalization Counters
|
|
62
66
|
|
|
63
67
|
- "It's faster to do the work first and track at the end" — wrong: reconstruction takes 2-3× longer than real-time logging.
|
|
64
|
-
- "
|
|
68
|
+
- "Your in-session task-tracking tool is enough" — it is in-session only; `tasks.md` is the durable record.
|
|
65
69
|
- "Mid-task tracking interrupts flow" — a one-line update costs <30s; reconstructing a day later costs 30 minutes.
|
|
66
70
|
|
|
67
71
|
### Rule 3: Auto-Track Discoveries
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "TASK_TOOL": "the native **task list** artifact (task.md)", "TASK_TOOL_NAME": "The task-list artifact" }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "TASK_TOOL": "the built-in **TodoWrite** tool", "TASK_TOOL_NAME": "TodoWrite" }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "TASK_TOOL": "the built-in **plan** tool", "TASK_TOOL_NAME": "The plan tool" }
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "TASK_TOOL": "the built-in **task** tool (subagent fan-out)", "TASK_TOOL_NAME": "The task tool" }
|