@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.
@@ -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 the native **task list** artifact (task.md) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
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
- - "The task-list artifact is enough" — The task-list artifact is in-session only; `tasks.md` is the durable record.
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "antigravity",
3
+ "displayName": "Antigravity",
4
+ "surface": "agents-md",
5
+ "taskTool": "the native **task list** artifact (task.md)",
6
+ "taskToolName": "The task-list artifact",
7
+ "hasSurfaceDelta": true
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "claude-code",
3
+ "displayName": "Claude Code",
4
+ "surface": "claude-md",
5
+ "taskTool": "the built-in **TodoWrite** tool",
6
+ "taskToolName": "TodoWrite",
7
+ "hasSurfaceDelta": false
8
+ }
@@ -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 the built-in **plan** tool to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
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
- - "The plan tool is enough" — The plan tool is in-session only; `tasks.md` is the durable record.
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "codex",
3
+ "displayName": "Codex",
4
+ "surface": "agents-md",
5
+ "taskTool": "the built-in **plan** tool",
6
+ "taskToolName": "The plan tool",
7
+ "hasSurfaceDelta": true
8
+ }
@@ -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 the built-in **task** tool (subagent fan-out) to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
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
- - "The task tool is enough" — The task tool is in-session only; `tasks.md` is the durable record.
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "opencode",
3
+ "displayName": "opencode",
4
+ "surface": "agents-md",
5
+ "taskTool": "the built-in **task** tool (subagent fan-out)",
6
+ "taskToolName": "The task tool",
7
+ "hasSurfaceDelta": true
8
+ }
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
- if (m && m.agent) return m.agent;
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
- function installPrimaryInstruction(srcRoot, targetDir, adapterDir, primaryInstruction) {
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
- fs.readFileSync(srcPath, 'utf8'),
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(fs.readFileSync(srcPath, 'utf8'), projectName);
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(fs.readFileSync(srcPath, 'utf8')));
955
+ fs.writeFileSync(destPath, render(rawSrc));
911
956
  console.log(` + added: ${rel}`);
912
957
  return 'added';
913
958
  }
914
959
 
915
- const srcContent = render(fs.readFileSync(srcPath, 'utf8'));
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 {{TASK_TOOL}} to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
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
- - "{{TASK_TOOL_NAME}} is enough" — {{TASK_TOOL_NAME}} is in-session only; `tasks.md` is the durable record.
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 the built-in **TodoWrite** tool to track in-session task progress. Do NOT wait for the user to ask you to update tracking.
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
- - "TodoWrite is enough" — TodoWrite is in-session only; `tasks.md` is the durable record.
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@limina-labs/momentum",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "Agent-agnostic, specs-driven development framework — single project or multi-project ecosystem",
5
5
  "homepage": "https://trymomentum.github.io",
6
6
  "bin": {
@@ -1,3 +0,0 @@
1
- # Project Rules: <Project Name>
2
-
3
- > Antigravity configuration for this momentum-managed project.
@@ -1 +0,0 @@
1
- { "TASK_TOOL": "the native **task list** artifact (task.md)", "TASK_TOOL_NAME": "The task-list artifact" }
@@ -1,3 +0,0 @@
1
- # Project Rules: <Project Name>
2
-
3
- > Claude Code configuration for this project.
@@ -1 +0,0 @@
1
- { "TASK_TOOL": "the built-in **TodoWrite** tool", "TASK_TOOL_NAME": "TodoWrite" }
@@ -1,3 +0,0 @@
1
- # Project Rules: <Project Name>
2
-
3
- > Codex configuration for this momentum-managed project.
@@ -1 +0,0 @@
1
- { "TASK_TOOL": "the built-in **plan** tool", "TASK_TOOL_NAME": "The plan tool" }
@@ -1,3 +0,0 @@
1
- # Project Rules: <Project Name>
2
-
3
- > opencode configuration for this momentum-managed project.
@@ -1 +0,0 @@
1
- { "TASK_TOOL": "the built-in **task** tool (subagent fan-out)", "TASK_TOOL_NAME": "The task tool" }