@limina-labs/momentum 0.30.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.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +214 -0
  3. package/adapters/antigravity/adapter.js +196 -0
  4. package/adapters/antigravity/hooks.json +39 -0
  5. package/adapters/antigravity/instructions/AGENTS.md +638 -0
  6. package/adapters/antigravity/instructions/header.md +3 -0
  7. package/adapters/antigravity/instructions/surfaces.md +153 -0
  8. package/adapters/antigravity/instructions/vars.json +1 -0
  9. package/adapters/antigravity/scripts/antigravity-hook-adapter.sh +147 -0
  10. package/adapters/antigravity/skills/momentum-orient/SKILL.md +29 -0
  11. package/adapters/antigravity/skills/momentum-reviewer-architecture/SKILL.md +39 -0
  12. package/adapters/antigravity/skills/momentum-reviewer-qa/SKILL.md +38 -0
  13. package/adapters/antigravity/skills/momentum-reviewer-security/SKILL.md +42 -0
  14. package/adapters/antigravity/skills/swarm-supervisor/SKILL.md +75 -0
  15. package/adapters/antigravity/workflows/continue.md +39 -0
  16. package/adapters/antigravity/workflows/dispatch.md +49 -0
  17. package/adapters/antigravity/workflows/handoff.md +38 -0
  18. package/adapters/antigravity/workflows/review-code.md +84 -0
  19. package/adapters/antigravity/workflows/scout.md +42 -0
  20. package/adapters/antigravity/workflows/swarm.md +203 -0
  21. package/adapters/claude-code/adapter.js +148 -0
  22. package/adapters/claude-code/commands/continue.md +82 -0
  23. package/adapters/claude-code/commands/dispatch.md +154 -0
  24. package/adapters/claude-code/commands/handoff.md +85 -0
  25. package/adapters/claude-code/commands/review-code.md +194 -0
  26. package/adapters/claude-code/commands/scout.md +119 -0
  27. package/adapters/claude-code/commands/swarm.md +242 -0
  28. package/adapters/claude-code/instructions/header.md +3 -0
  29. package/adapters/claude-code/instructions/vars.json +1 -0
  30. package/adapters/claude-code/settings.json +36 -0
  31. package/adapters/codex/adapter.js +225 -0
  32. package/adapters/codex/agents/momentum-reviewer-architecture.toml +57 -0
  33. package/adapters/codex/agents/momentum-reviewer-qa.toml +52 -0
  34. package/adapters/codex/agents/momentum-reviewer-security.toml +57 -0
  35. package/adapters/codex/agents/swarm-supervisor.toml +76 -0
  36. package/adapters/codex/commands/continue.md +40 -0
  37. package/adapters/codex/commands/dispatch.md +111 -0
  38. package/adapters/codex/commands/handoff.md +57 -0
  39. package/adapters/codex/commands/scout.md +90 -0
  40. package/adapters/codex/commands/swarm.md +334 -0
  41. package/adapters/codex/hooks.json +36 -0
  42. package/adapters/codex/instructions/AGENTS.md +691 -0
  43. package/adapters/codex/instructions/header.md +3 -0
  44. package/adapters/codex/instructions/surfaces.md +206 -0
  45. package/adapters/codex/instructions/vars.json +1 -0
  46. package/adapters/codex/skills/momentum-orient/SKILL.md +29 -0
  47. package/adapters/opencode/adapter.js +224 -0
  48. package/adapters/opencode/agents/momentum-reviewer-architecture.md +54 -0
  49. package/adapters/opencode/agents/momentum-reviewer-qa.md +48 -0
  50. package/adapters/opencode/agents/momentum-reviewer-security.md +48 -0
  51. package/adapters/opencode/agents/swarm-supervisor.md +51 -0
  52. package/adapters/opencode/commands/continue.md +40 -0
  53. package/adapters/opencode/commands/dispatch.md +111 -0
  54. package/adapters/opencode/commands/handoff.md +57 -0
  55. package/adapters/opencode/commands/review-code.md +194 -0
  56. package/adapters/opencode/commands/scout.md +90 -0
  57. package/adapters/opencode/commands/swarm.md +334 -0
  58. package/adapters/opencode/instructions/AGENTS.md +604 -0
  59. package/adapters/opencode/instructions/header.md +3 -0
  60. package/adapters/opencode/instructions/surfaces.md +119 -0
  61. package/adapters/opencode/instructions/vars.json +1 -0
  62. package/adapters/opencode/plugins/momentum.js +219 -0
  63. package/adapters/opencode/skills/momentum-lanes/SKILL.md +30 -0
  64. package/adapters/opencode/skills/momentum-orient/SKILL.md +29 -0
  65. package/adapters/opencode/skills/momentum-track/SKILL.md +28 -0
  66. package/adapters/opencode/skills/momentum-validate/SKILL.md +31 -0
  67. package/bin/antigravity.js +112 -0
  68. package/bin/ecosystem.js +1302 -0
  69. package/bin/lanes.js +300 -0
  70. package/bin/momentum.js +1857 -0
  71. package/bin/okf.js +72 -0
  72. package/bin/orchestration-commands.js +305 -0
  73. package/bin/state-commands.js +218 -0
  74. package/bin/swarm.js +1104 -0
  75. package/bin/waves.js +147 -0
  76. package/core/adapter-capabilities.md +112 -0
  77. package/core/adapter-parity-matrix.md +170 -0
  78. package/core/commands/brainstorm-idea.md +98 -0
  79. package/core/commands/brainstorm-phase.md +129 -0
  80. package/core/commands/complete-phase.md +130 -0
  81. package/core/commands/ecosystem.md +161 -0
  82. package/core/commands/hotfix.md +64 -0
  83. package/core/commands/initiative.md +119 -0
  84. package/core/commands/lanes.md +94 -0
  85. package/core/commands/log.md +45 -0
  86. package/core/commands/migrate.md +70 -0
  87. package/core/commands/review.md +31 -0
  88. package/core/commands/session.md +79 -0
  89. package/core/commands/start-phase.md +161 -0
  90. package/core/commands/start-project.md +142 -0
  91. package/core/commands/sync-docs.md +81 -0
  92. package/core/commands/systematic-debug.md +46 -0
  93. package/core/commands/track.md +54 -0
  94. package/core/commands/validate.md +96 -0
  95. package/core/ecosystem/layout.md +128 -0
  96. package/core/ecosystem/lib/index.js +245 -0
  97. package/core/ecosystem/lib/initiative.js +258 -0
  98. package/core/ecosystem/lib/pointer.js +198 -0
  99. package/core/ecosystem/lib/state.js +295 -0
  100. package/core/ecosystem/schema/ecosystem.schema.json +85 -0
  101. package/core/ecosystem/schema/initiative.schema.json +61 -0
  102. package/core/ecosystem/scripts/session-append.sh +167 -0
  103. package/core/ecosystem/templates/ecosystem-agents.md +79 -0
  104. package/core/ecosystem/templates/ecosystem-claude.md +79 -0
  105. package/core/ecosystem/templates/ecosystem-settings-claude.json +14 -0
  106. package/core/ecosystem/templates/initiative-template.md +50 -0
  107. package/core/engines/subagent-dispatch.md +80 -0
  108. package/core/git-hooks/commit-msg +14 -0
  109. package/core/git-hooks/contract.js +176 -0
  110. package/core/git-hooks/pre-push +15 -0
  111. package/core/git-hooks/run-check.js +160 -0
  112. package/core/instructions/legacy-project-md-hashes.json +12 -0
  113. package/core/instructions/navigation.md +14 -0
  114. package/core/instructions/rules-body.md +454 -0
  115. package/core/lanes/lib/board.js +161 -0
  116. package/core/lanes/lib/land.js +301 -0
  117. package/core/lanes/lib/signals.js +207 -0
  118. package/core/lanes/lib/state.js +325 -0
  119. package/core/lib/frontmatter.js +163 -0
  120. package/core/lib/okf-migrate.js +374 -0
  121. package/core/lib/okf-types.js +61 -0
  122. package/core/lifecycle-contract.md +59 -0
  123. package/core/orchestration/capability-routing.js +120 -0
  124. package/core/orchestration/continue.js +102 -0
  125. package/core/orchestration/dispatch.js +366 -0
  126. package/core/orchestration/events.js +197 -0
  127. package/core/orchestration/handoff.js +317 -0
  128. package/core/orchestration/index.js +50 -0
  129. package/core/orchestration/run-artifact.js +93 -0
  130. package/core/orchestration/scout.js +327 -0
  131. package/core/orchestration/session-log.js +123 -0
  132. package/core/orchestration/tracking.js +180 -0
  133. package/core/orchestration/types.js +131 -0
  134. package/core/scripts/brainstorm-gate.sh +157 -0
  135. package/core/scripts/check-history-reminder.sh +110 -0
  136. package/core/scripts/sessionstart-handoff.sh +180 -0
  137. package/core/specs-templates/CLAUDE.md +484 -0
  138. package/core/specs-templates/README.md +32 -0
  139. package/core/specs-templates/specs/README.md +30 -0
  140. package/core/specs-templates/specs/adhoc/README.md +31 -0
  141. package/core/specs-templates/specs/adhoc/_TEMPLATE.md +29 -0
  142. package/core/specs-templates/specs/architecture/ecosystem.md +214 -0
  143. package/core/specs-templates/specs/backlog/backlog.md +46 -0
  144. package/core/specs-templates/specs/backlog/details/.gitkeep +0 -0
  145. package/core/specs-templates/specs/changelog/.gitkeep +0 -0
  146. package/core/specs-templates/specs/decisions/0000-template.md +33 -0
  147. package/core/specs-templates/specs/decisions/README.md +29 -0
  148. package/core/specs-templates/specs/decisions/impact-map.md +14 -0
  149. package/core/specs-templates/specs/decisions/index.md +4 -0
  150. package/core/specs-templates/specs/index.md +16 -0
  151. package/core/specs-templates/specs/phases/README.md +41 -0
  152. package/core/specs-templates/specs/phases/index.md +6 -0
  153. package/core/specs-templates/specs/planning/roadmap.md +23 -0
  154. package/core/specs-templates/specs/status.md +70 -0
  155. package/core/specs-templates/specs/vision/principles.md +16 -0
  156. package/core/specs-templates/specs/vision/project-charter.md +35 -0
  157. package/core/specs-templates/specs/vision/success-criteria.md +19 -0
  158. package/core/swarm/absorb.js +294 -0
  159. package/core/swarm/conductor.js +499 -0
  160. package/core/swarm/focus.js +151 -0
  161. package/core/swarm/inbox.js +222 -0
  162. package/core/swarm/join.js +138 -0
  163. package/core/swarm/lib/board.js +178 -0
  164. package/core/swarm/lib/brief.js +157 -0
  165. package/core/swarm/lib/git-sha-cache.js +71 -0
  166. package/core/swarm/lib/incremental-log.js +117 -0
  167. package/core/swarm/lib/manifest.js +578 -0
  168. package/core/swarm/lib/pre-merge.js +112 -0
  169. package/core/swarm/lib/saga.js +146 -0
  170. package/core/swarm/lib/sessions.js +83 -0
  171. package/core/swarm/lib/tokens.js +255 -0
  172. package/core/swarm/lib/wave-ordering.js +55 -0
  173. package/core/swarm/schema/board.schema.json +54 -0
  174. package/core/swarm/schema/contract.schema.json +56 -0
  175. package/core/swarm/schema/dispatch-run.schema.json +70 -0
  176. package/core/swarm/schema/manifest.schema.json +234 -0
  177. package/core/swarm/schema/signal.schema.json +61 -0
  178. package/core/swarm/signals.js +296 -0
  179. package/core/swarm/supervise.md +81 -0
  180. package/core/waves/lib/plan-graph.js +175 -0
  181. package/core/waves/lib/waves.js +83 -0
  182. package/package.json +46 -0
@@ -0,0 +1,81 @@
1
+ Sync all relevant documents based on the active phase's history log.
2
+ Token-efficient: reads history + 2 tiny indexes first, then only targeted files.
3
+
4
+ > **Which phase is yours (Rule 15):** the phase bound to your branch — sync
5
+ > from YOUR lane's history log. `status.md` is the fallback and the
6
+ > cross-lane overview. Never sync another active lane's history from here.
7
+
8
+ ## Steps
9
+
10
+ ### Step 1: Load history and indexes (cheap reads)
11
+ - Resolve your phase from the current branch: a `phase-*` branch with a
12
+ matching `specs/phases/<branch>/` directory binds to that phase; fallback =
13
+ the `specs/status.md` Active Phase table
14
+ - Read the history log → extract all entries:
15
+ - **Phase lane**: `specs/phases/<phase-bound-to-your-branch>/history.md`
16
+ - **No active phase** (ad-hoc work just shipped): the relevant
17
+ `specs/adhoc/<id>/record.md` (and `specs/adhoc/history.md` if present).
18
+ If there is no phase and no ad-hoc record, there is nothing to sync —
19
+ report "nothing to sync" and stop.
20
+ - Read the `tags:` frontmatter of each `specs/phases/*/overview.md` (OKF bundle)
21
+ - Read `specs/decisions/impact-map.md` (~3KB)
22
+
23
+ ### Step 2: Build targeted file list
24
+ For each history entry:
25
+ - Extract Topics list
26
+ - Match each topic against phase `tags:` frontmatter → collect affected phase tasks.md files
27
+ - Look up each topic in the impact-map.md table → collect affected spec files/sections
28
+ - Deduplicate the combined list
29
+ - For monorepo: EXCLUDE files in `specs/architecture/`
30
+ (constitution — never auto-synced, only amended via formal process)
31
+ - **Multi-repo: PARTITION OUT cross-repo entries.** If a history entry has an
32
+ `Affects-specs:` path starting with `../` (or otherwise outside this repo),
33
+ add it to a separate "cross-repo" list. NEVER edit those files — you only
34
+ own this repo's docs.
35
+
36
+ ### Step 3: Show sync plan (ALWAYS show before touching anything)
37
+ Present to user:
38
+ "Based on phase history, I will check and potentially update:
39
+ - [list of files]
40
+ Proceed? (yes/no)"
41
+
42
+ **If any cross-repo entries were partitioned out in Step 2:** also show them
43
+ under a "Cross-repo impact (NOT touching — sync the other repo manually)"
44
+ heading, listing each `Affects-specs: ../...` path. Tell the user which repo
45
+ they need to edit and which entries pointed there. Do NOT prompt for approval
46
+ on cross-repo paths — they're informational only.
47
+
48
+ If user says no → stop.
49
+
50
+ ### Step 4: Read and assess (targeted reads only)
51
+ For each file in the list:
52
+ - Read only the relevant section
53
+ - Assess: does it need updating based on history entries?
54
+
55
+ ### Step 5: Make updates (one at a time, fully visible)
56
+ For each file that needs updating:
57
+ - Show the user what will change
58
+ - Use the Edit tool to make the change
59
+
60
+ ### Step 6: Update phase metadata if scope changed
61
+ If any [SCOPE_CHANGE] entries exist:
62
+ - Update the active phase's `overview.md` frontmatter `tags:` list
63
+ - Run `momentum okf index` to refresh the bundle listings
64
+
65
+ ### Step 7: Commit all changes
66
+ ```bash
67
+ git add <all modified spec files>
68
+ git commit -m "docs(phase-N): sync specs from phase history"
69
+ ```
70
+
71
+ ### Step 8: Confirm completion
72
+ "Spec sync complete. Updated N files:
73
+ - [list]
74
+ Ready to run /complete-phase."
75
+
76
+ ## Safeguards
77
+ - NEVER update files not in the targeted list
78
+ - NEVER update `specs/architecture/` (monorepo only — constitution is read-only)
79
+ - NEVER update files in another repo (paths starting with `../`) — you only own this repo's docs. Flag cross-repo entries to the user instead.
80
+ - ALWAYS show the plan (Step 3) before making any edits
81
+ - History entries are NEVER modified — only read
@@ -0,0 +1,46 @@
1
+ Systematically isolate, reproduce, and resolve task execution failures.
2
+
3
+ This recipe enforces the **3-strikes systematic debugging/retry budget** defined under the Autonomous Execution Contract. If validation fails for a task, execute the strikes sequentially:
4
+
5
+ ---
6
+
7
+ ## Strike 1 — Analyze and Adjust
8
+
9
+ 1. **Analyze validation output**: Look closely at the stdout/stderr, stack trace, and exit code.
10
+ 2. **Hypothesize the cause**: Identify if the failure is due to a simple syntax error, a missing import, incorrect pathing, or a basic logical gap.
11
+ 3. **Adjust**: Implement the simple correction directly.
12
+ 4. **Re-run validation**: Execute the group's verification command.
13
+ * *Passes* → Check task `[x]` and proceed.
14
+ * *Fails* → Proceed to **Strike 2**.
15
+
16
+ ---
17
+
18
+ ## Strike 2 — Reproduce and Isolate
19
+
20
+ 1. **Write reproduction test**: Create a minimal unit test or a temporary scratch script that isolates the failing code path and reproduces the exact failure.
21
+ 2. **Run isolation**: Run the reproduction case to confirm it fails in isolation.
22
+ 3. **Implement isolation fix**: Fix the code under the reproduced test case.
23
+ 4. **Verify isolation**: Confirm the reproduction case now passes.
24
+ 5. **Re-run validation**: Execute the main group verification command.
25
+ * *Passes* → Check task `[x]` and proceed. Clean up any temporary scratch scripts.
26
+ * *Fails* → Proceed to **Strike 3**.
27
+
28
+ ---
29
+
30
+ ## Strike 3 — Re-Read and Reframe
31
+
32
+ 1. **Re-read specs**: Completely stop coding. Re-read:
33
+ * `specs/status.md` (to check active context)
34
+ * `specs/phases/phase-N-*/overview.md` (to re-read goals and deliverables)
35
+ * `specs/phases/phase-N-*/plan.md` (to review architectural choices)
36
+ 2. **Identify gaps**: Figure out if the implementation approach is fundamentally incompatible with a spec constraint.
37
+ 3. **Reframe**: Redesign the approach to align with specs.
38
+ 4. **Draft proposed change**: Discuss the re-framed approach with the user in conversation. Do not write changes to specs.
39
+ 5. **Obtain Approval**: Wait for user review before implementing the reframed code.
40
+
41
+ ---
42
+
43
+ ## See Also
44
+
45
+ * **Rule 12 (Verify Before Claim)**: Always run the actual verification command and capture output.
46
+ * **start-phase**: Automates end-to-end execution utilizing this debugging recipe on task failures.
@@ -0,0 +1,54 @@
1
+ Track a backlog item — bug, feature, tech debt, or enhancement.
2
+
3
+ ## Steps
4
+
5
+ 1. Read `specs/backlog/backlog.md` to find next available ID
6
+
7
+ 2. Determine item type:
8
+ - Bug → `BUG-NNN`
9
+ - Feature → `FEAT-NNN`
10
+ - Tech Debt → `TD-NNN`
11
+ - Enhancement → `ENH-NNN`
12
+
13
+ 3. Add row to appropriate table in `specs/backlog/backlog.md`:
14
+ - Priority: infer from urgency (default P2)
15
+ - Status: `open`
16
+ - Phase: target phase or `unscheduled`
17
+ - Detail: `—` for one-liners, `[→](details/{ID}.md)` if a detail file is created
18
+
19
+ 4. Decide: one-liner or detail file?
20
+
21
+ **One-liner (no detail file needed):**
22
+ - Clear, self-contained issue (< 2 sentences to describe)
23
+ - No design choices to make
24
+ - Single file or single function affected
25
+ - Fix is obvious from the description
26
+
27
+ **Detail file required (`specs/backlog/details/{ID}.md`):**
28
+ - Requires design or multiple options to evaluate
29
+ - Touches multiple files, commands, or systems
30
+ - Has a non-obvious implementation path
31
+ - Cross-cutting impact (affects other phases or specs)
32
+ - Estimated > 30 min of work
33
+
34
+ If a detail file is needed, create it now with: context, options considered, open questions.
35
+
36
+ 5. If P0, also update Critical Items table in `specs/status.md`
37
+
38
+ 6. Commit:
39
+ ```bash
40
+ git add specs/backlog/
41
+ git commit -m "docs(backlog): add {ID} - {short title}"
42
+ ```
43
+
44
+ ## Cross-repo backlog items
45
+
46
+ `/track` operates on the current repo's `specs/backlog/`. The ecosystem
47
+ layer (Phase 9) does NOT aggregate backlogs across member repos in
48
+ Tier 1 — each repo's backlog stays authoritative for its own work. If
49
+ an item's scope genuinely spans repos, file it in the repo that owns
50
+ the most surface area and add a one-line cross-link from the others.
51
+
52
+ For cross-repo features as a whole (vs. individual backlog items), use
53
+ `/initiative create <slug>` — see the `/ecosystem` and `/initiative`
54
+ command recipes.
@@ -0,0 +1,96 @@
1
+ Check the spec structure health of the current momentum project.
2
+
3
+ Run with no arguments for a fast index-first check, or pass `--deep` for a full scan.
4
+
5
+ > **Which phase is yours (Rule 15):** the phase bound to your branch;
6
+ > `status.md` is the fallback and the cross-lane overview. Validation checks
7
+ > the whole board, not just your lane.
8
+
9
+ ## Default Mode (index-first)
10
+
11
+ 1. Read `specs/status.md` — verify required fields are present:
12
+ - `Last Updated`, `Current Phase`, `Latest Release`, `Health`
13
+ - Active Phase table exists. **"No active phase" is a VALID state** — a
14
+ single row reading `_(none)_` / `no active phase` (e.g. between phases, or
15
+ during ad-hoc `/hotfix` work) is NOT a failure. **Multiple rows are also
16
+ valid** — one row per active lane (Rule 15, concurrent workstreams). Only
17
+ flag a structurally missing/empty table.
18
+ - Report any missing fields as failures
19
+
20
+ 2. Read `specs/backlog/backlog.md` — verify all 4 section tables present:
21
+ - Bugs, Features, Tech Debt, Enhancements
22
+ - Report any missing table as a failure
23
+
24
+ 3. For each phase directory under `specs/phases/` (OKF bundle, ADR-0005):
25
+ - Verify all 4 files present: `overview.md`, `plan.md`, `tasks.md`, `history.md`
26
+ - Verify `overview.md` frontmatter carries `type: Phase` and a `status:`
27
+ (legacy projects may still have `specs/phases/index.json` instead —
28
+ recommend `momentum upgrade` to migrate)
29
+ - Report missing files or missing status frontmatter as failures
30
+
31
+ 4. Cross-check active phase consistency:
32
+ - If an active phase is named, its directory must exist and its
33
+ `overview.md` frontmatter must say `status: in-progress` — report
34
+ mismatch as a failure. With multiple active lanes (Rule 15), check EVERY
35
+ row: each row's branch must bind to an existing phase directory.
36
+ - If `Current Phase` is "none" / no active phase, **skip this check** (valid
37
+ between-phases state).
38
+
39
+ 4b. OKF conformance (delegates to the CLI):
40
+ - Run `momentum okf check` — report its violations as failures verbatim
41
+ - Run `momentum okf index` if listings are stale (safe — regenerates only
42
+ `specs/index.md`, `specs/phases/index.md`, `specs/decisions/index.md`)
43
+
44
+ 5. Check `.claude/commands/` for standard momentum commands:
45
+ - Required: `brainstorm-idea`, `brainstorm-phase`, `start-project`, `start-phase`,
46
+ `complete-phase`, `log`, `sync-docs`, `track`, `migrate`, `validate`, `hotfix`
47
+ - Report any missing commands as warnings (not failures — project may predate them)
48
+
49
+ 6. Report results:
50
+ ```
51
+ ✓ N checks passed
52
+ ✗ N issues found:
53
+ - specs/phases/phase-3-gap-fixes/overview.md: missing `status:` frontmatter
54
+ - specs/status.md: missing field "Latest Release"
55
+ ```
56
+
57
+ ## `--deep` Flag (full scan)
58
+
59
+ Run all default mode checks, then additionally:
60
+
61
+ 7. Walk ALL directories under `specs/phases/` — flag any directory whose
62
+ `overview.md` lacks `status:` frontmatter as an untracked phase
63
+
64
+ 8. For each phase directory, read `tasks.md`:
65
+ - Extract all backlog ID references: `BUG-NNN`, `FEAT-NNN`, `TD-NNN`, `ENH-NNN`
66
+ - Verify each ID exists as a row in `specs/backlog/backlog.md`
67
+ - Report unresolved IDs as failures
68
+
69
+ 9. For each phase directory, read `history.md`:
70
+ - Verify each entry has all required fields: type tag `[TYPE]`, date in `YYYY-MM-DD`
71
+ format, `Topics:`, `Affects-phases:`, `Affects-specs:`, `Detail:`
72
+ - Report malformed entries (missing fields) as warnings
73
+
74
+ 10. Check `specs/changelog/` — for each phase whose `overview.md` frontmatter
75
+ says `status: complete`, verify at least one changelog file exists under
76
+ `specs/changelog/`
77
+ - Report completely absent changelog as a warning
78
+
79
+ 11. Swarm member integrity (Phase 17+, only when ecosystem context exists):
80
+ - For each `<ecosystem-root>/swarms/<id>/manifest.json`:
81
+ - Validate against `core/swarm/schema/manifest.schema.json` (load + structural check)
82
+ - Every `manifest.repos.<id>` must reference a real `ecosystem.json` member
83
+ - Each repo's `phase_slug` must point at an existing phase directory
84
+ in that member repo
85
+ - For every phase brief that carries swarm frontmatter (parsed via
86
+ `core/swarm/lib/brief.js`):
87
+ - `swarm:` must resolve to a real swarm manifest at the ecosystem root
88
+ - `wave:` must match the wave that swarm has assigned this repo
89
+ - `initiative:` must match the swarm manifest's `initiative` field
90
+ - Report mismatch as a failure
91
+ - For every swarm with `status: complete`, verify the initiative's
92
+ `Per-repo contributions` section lists all swarm members.
93
+ - Report unresolved swarm references as failures; report orphaned
94
+ brief frontmatter (swarm: refers to non-existent swarm) as failures.
95
+
96
+ 12. Append deep-scan results to the report before printing.
@@ -0,0 +1,128 @@
1
+ # Ecosystem Root: On-Disk Layout
2
+
3
+ > A momentum **ecosystem** is a sibling git repo that coordinates a set
4
+ > of related momentum-installed repos as one product. Reading this
5
+ > file tells you everything about what lives where.
6
+
7
+ ## Layout
8
+
9
+ ```
10
+ <ecosystem-root>/
11
+ ├── ecosystem.json ← manifest (members, roles, dep edges)
12
+ ├── README.md ← human-readable description + worked example
13
+ ├── initiatives/ ← cross-repo feature files
14
+ │ ├── README.md
15
+ │ ├── 0001-<slug>.md
16
+ │ └── 0002-<slug>.md
17
+ ├── sessions/ ← daily activity logs
18
+ │ ├── .gitkeep
19
+ │ ├── 2026-06-07.md
20
+ │ └── 2026-06-08.md
21
+ ├── .state/ ← runtime state (gitignored)
22
+ │ ├── active-initiative ← contents = slug or empty
23
+ │ └── last-session ← contents = ISO date
24
+ └── .gitignore ← `.state/` plus standard noise
25
+ ```
26
+
27
+ ## File contracts
28
+
29
+ ### `ecosystem.json`
30
+ Single source of truth for the ecosystem's membership and inter-repo
31
+ edges. Schema: `core/ecosystem/schema/ecosystem.schema.json`. Validated
32
+ on read.
33
+
34
+ ### `initiatives/NNNN-<slug>.md`
35
+ Cross-repo feature record. Top YAML frontmatter block conforms to
36
+ `core/ecosystem/schema/initiative.schema.json`. Body is markdown
37
+ sections from the initiative template
38
+ (`core/ecosystem/templates/initiative-template.md`):
39
+ - **Why** — one paragraph
40
+ - **Per-repo contributions** — per-member bullets
41
+ - **Linked decisions** — ADR pointers (cross-repo OK)
42
+ - **Deploy chronology** — timestamps, repos, SHAs
43
+ - **Close** — populated by `momentum ecosystem initiative close`
44
+
45
+ Numbering is monotonically increasing across the ecosystem. The
46
+ filename slug is for human readability; the `id` integer is canonical.
47
+
48
+ ### `sessions/YYYY-MM-DD.md`
49
+ One file per UTC date. Appended to atomically by the PostToolUse hook
50
+ in each member repo. Header line (first write of the day) names the
51
+ active initiative if any. Subsequent lines have the format:
52
+
53
+ ```
54
+ HH:MMZ [<member-id>] <event-summary> (<context>)
55
+ ```
56
+
57
+ Examples:
58
+ ```
59
+ 03:24Z [sapience] commit 8a086117 — fix(memory): caller-accessible fanout
60
+ 04:30Z [sapience] deploy success — image_tag=8a086117
61
+ 09:50Z [sapience] PR #42 merged — list[T] query binding fix
62
+ ```
63
+
64
+ ### `.state/active-initiative`
65
+ A single line, either empty or containing the slug of the currently
66
+ active initiative. Set by `momentum ecosystem initiative create`;
67
+ cleared by `… close`. The session-log hook reads this to inject the
68
+ "Active initiative: …" header line.
69
+
70
+ ### `.state/last-session`
71
+ The ISO date of the most recent session file. Cheap cache so the hook
72
+ doesn't re-stat every directory entry.
73
+
74
+ ## Discovery & limits
75
+
76
+ The ecosystem root is **discovered** by walking up from `$PWD` (max 5
77
+ parents by default) until a directory containing `ecosystem.json` is
78
+ found. The resolution is memoized per session.
79
+
80
+ **Configurable depth (ENH-022).** Override the parent-walk limit via
81
+ the `MOMENTUM_MAX_PARENT_WALK` environment variable. Honored in both
82
+ the JS path (`core/ecosystem/lib/index.js`, `core/ecosystem/lib/state.js`)
83
+ and the shell path (`core/ecosystem/scripts/session-append.sh`).
84
+ Non-numeric or negative values fall back to the default of 5.
85
+
86
+ ```
87
+ MOMENTUM_MAX_PARENT_WALK=10 momentum ecosystem status
88
+ ```
89
+
90
+ **Location-agnostic CLI (ENH-021).** `momentum ecosystem add`,
91
+ `remove`, and `status` resolve the ecosystem root from CWD by default
92
+ (via the walk-up above). Use `--ecosystem <path>` to override
93
+ explicitly:
94
+
95
+ ```
96
+ momentum ecosystem add ./client --ecosystem ../my-eco
97
+ ```
98
+
99
+ **Concurrency (BUG-004).** Session-log writes are serialized via a
100
+ `mkdir`-based lock per session file (atomic on POSIX filesystems,
101
+ portable across macOS and Linux without depending on `flock`). Lock
102
+ acquisition has a ~5s budget; on timeout the event is dropped
103
+ silently rather than risking file corruption.
104
+
105
+ ## Conventions
106
+
107
+ - The ecosystem layer **never writes** into a member repo's `specs/`.
108
+ It only writes to its own directories above. The single touch on a
109
+ member is one fenced line in its `CLAUDE.md` / `AGENTS.md`:
110
+ ```html
111
+ <!-- ecosystem:begin -->
112
+ > Member of `<name>` ecosystem at `../<name>`. See ecosystem.json.
113
+ <!-- ecosystem:end -->
114
+ ```
115
+ - The ecosystem root is a normal git repo. `momentum ecosystem init`
116
+ runs `git init` and produces the initial commit; subsequent changes
117
+ are user-managed (the ecosystem doesn't enforce a workflow on itself
118
+ beyond the schema check).
119
+
120
+ ## What lives where
121
+
122
+ | Question | File |
123
+ |----------|------|
124
+ | What repos are in this ecosystem? | `ecosystem.json` |
125
+ | What are we shipping right now? | `.state/active-initiative` + `initiatives/<slug>.md` |
126
+ | What happened today? | `sessions/<today>.md` |
127
+ | What's the cross-repo dependency graph? | `ecosystem.json` `dependencies` |
128
+ | Where does a member repo's per-repo planning live? | Inside that member's own `specs/` (unchanged) |
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Ecosystem helpers — pure, dependency-free.
5
+ *
6
+ * Provides four building blocks the rest of Phase 9 depends on:
7
+ *
8
+ * - findRoot(startPath) — locate the ecosystem root by walking up
9
+ * - loadManifest(rootPath) — read + parse ecosystem.json
10
+ * - listMembers(manifest) — convenience accessor
11
+ * - validateManifest(obj) — minimal schema check (no external deps)
12
+ *
13
+ * Schema validation is deliberately hand-rolled: momentum has a
14
+ * zero-dependency posture for the CLI (no ajv, no joi). The check
15
+ * is structural — type + required fields — and surfaces the first
16
+ * violation with a human-readable path. The full JSON Schema in
17
+ * `../schema/ecosystem.schema.json` remains the authoritative
18
+ * contract; this validator is its operational counterpart.
19
+ */
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ // Default upper bound on parent-directory walk-up. Honored by both this
25
+ // module's findRoot and core/ecosystem/scripts/session-append.sh. The
26
+ // authoritative export lives in core/ecosystem/lib/state.js as of
27
+ // Phase 10 (ENH-022); we keep MAX_WALK_DEPTH as a backward-compat alias.
28
+ const MAX_PARENT_WALK_DEFAULT = 5;
29
+ const MAX_WALK_DEPTH = MAX_PARENT_WALK_DEFAULT;
30
+ const MANIFEST_FILENAME = 'ecosystem.json';
31
+
32
+ function resolveMaxParentWalk() {
33
+ const raw = process.env.MOMENTUM_MAX_PARENT_WALK;
34
+ if (raw === undefined || raw === '') return MAX_PARENT_WALK_DEFAULT;
35
+ const n = Number(raw);
36
+ if (!Number.isFinite(n) || n < 0) return MAX_PARENT_WALK_DEFAULT;
37
+ return Math.floor(n);
38
+ }
39
+
40
+ // Per-process cache: absolute-startPath-prefix → resolved root (or null)
41
+ const rootCache = new Map();
42
+
43
+ /**
44
+ * Walk up from `startPath` looking for a directory containing
45
+ * `ecosystem.json`. Returns the absolute root path or null.
46
+ * Bounded to `resolveMaxParentWalk()` parents (default 5, override via
47
+ * the `MOMENTUM_MAX_PARENT_WALK` env var) so a misconfigured caller
48
+ * can't scan the entire filesystem.
49
+ *
50
+ * Memoized: repeated calls with paths under the same root return
51
+ * instantly. Cache key is the absolute starting path.
52
+ */
53
+ function findRoot(startPath) {
54
+ if (typeof startPath !== 'string' || startPath.length === 0) {
55
+ return null;
56
+ }
57
+ const abs = path.resolve(startPath);
58
+ if (rootCache.has(abs)) {
59
+ return rootCache.get(abs);
60
+ }
61
+ const maxDepth = resolveMaxParentWalk();
62
+ let current = abs;
63
+ for (let i = 0; i <= maxDepth; i++) {
64
+ const candidate = path.join(current, MANIFEST_FILENAME);
65
+ let stat;
66
+ try {
67
+ stat = fs.statSync(candidate);
68
+ } catch (_err) {
69
+ stat = null;
70
+ }
71
+ if (stat && stat.isFile()) {
72
+ rootCache.set(abs, current);
73
+ return current;
74
+ }
75
+ const parent = path.dirname(current);
76
+ if (parent === current) break; // reached filesystem root
77
+ current = parent;
78
+ }
79
+ rootCache.set(abs, null);
80
+ return null;
81
+ }
82
+
83
+ /**
84
+ * Read + parse the manifest at `rootPath/ecosystem.json`.
85
+ * Throws on missing file or invalid JSON; caller decides recovery.
86
+ */
87
+ function loadManifest(rootPath) {
88
+ if (typeof rootPath !== 'string' || rootPath.length === 0) {
89
+ throw new TypeError('loadManifest: rootPath must be a non-empty string');
90
+ }
91
+ const file = path.join(rootPath, MANIFEST_FILENAME);
92
+ const raw = fs.readFileSync(file, 'utf8');
93
+ let parsed;
94
+ try {
95
+ parsed = JSON.parse(raw);
96
+ } catch (err) {
97
+ throw new SyntaxError(`Invalid JSON in ${file}: ${err.message}`);
98
+ }
99
+ return parsed;
100
+ }
101
+
102
+ /**
103
+ * Returns the members array, defaulting to [] if absent.
104
+ * Does not validate; assumes caller already passed validateManifest.
105
+ */
106
+ function listMembers(manifest) {
107
+ if (!manifest || typeof manifest !== 'object') return [];
108
+ return Array.isArray(manifest.members) ? manifest.members : [];
109
+ }
110
+
111
+ /**
112
+ * Find a member by id. Returns the member object or null.
113
+ */
114
+ function findMember(manifest, id) {
115
+ return listMembers(manifest).find((m) => m && m.id === id) || null;
116
+ }
117
+
118
+ /**
119
+ * Hand-rolled structural validation. Returns
120
+ * { ok: true } on success
121
+ * { ok: false, errors: [{ path, message }] } on failure.
122
+ *
123
+ * Catches every issue (does not bail on first); the caller can render
124
+ * a useful error report. Authoritative schema is
125
+ * `core/ecosystem/schema/ecosystem.schema.json` — keep them in sync.
126
+ */
127
+ function validateManifest(obj) {
128
+ const errors = [];
129
+ const slug = /^[a-z][a-z0-9-]*$/;
130
+
131
+ if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
132
+ return { ok: false, errors: [{ path: '$', message: 'manifest must be a JSON object' }] };
133
+ }
134
+
135
+ // name
136
+ if (typeof obj.name !== 'string' || obj.name.length === 0) {
137
+ errors.push({ path: '$.name', message: 'required non-empty string' });
138
+ } else if (!slug.test(obj.name)) {
139
+ errors.push({ path: '$.name', message: 'must match /^[a-z][a-z0-9-]*$/' });
140
+ } else if (obj.name.length > 64) {
141
+ errors.push({ path: '$.name', message: 'must be at most 64 chars' });
142
+ }
143
+
144
+ // version
145
+ if (obj.version !== 1) {
146
+ errors.push({ path: '$.version', message: 'must be the integer 1 (no other schema version supported)' });
147
+ }
148
+
149
+ // created — optional, must be a valid ISO date if present
150
+ if (obj.created !== undefined) {
151
+ if (typeof obj.created !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(obj.created)) {
152
+ errors.push({ path: '$.created', message: 'must be an ISO-8601 date (YYYY-MM-DD)' });
153
+ }
154
+ }
155
+
156
+ // members
157
+ if (!Array.isArray(obj.members)) {
158
+ errors.push({ path: '$.members', message: 'required array' });
159
+ } else {
160
+ const seenIds = new Set();
161
+ obj.members.forEach((m, i) => {
162
+ const base = `$.members[${i}]`;
163
+ if (m === null || typeof m !== 'object' || Array.isArray(m)) {
164
+ errors.push({ path: base, message: 'must be an object' });
165
+ return;
166
+ }
167
+ if (typeof m.id !== 'string' || m.id.length === 0) {
168
+ errors.push({ path: `${base}.id`, message: 'required non-empty string' });
169
+ } else {
170
+ if (!slug.test(m.id)) errors.push({ path: `${base}.id`, message: 'must match slug pattern' });
171
+ if (m.id.length > 64) errors.push({ path: `${base}.id`, message: 'must be at most 64 chars' });
172
+ if (seenIds.has(m.id)) errors.push({ path: `${base}.id`, message: `duplicate member id "${m.id}"` });
173
+ seenIds.add(m.id);
174
+ }
175
+ if (typeof m.path !== 'string' || m.path.length === 0) {
176
+ errors.push({ path: `${base}.path`, message: 'required non-empty string' });
177
+ }
178
+ const validRoles = ['platform', 'client', 'library', 'infra', 'bench', 'other'];
179
+ if (!validRoles.includes(m.role)) {
180
+ errors.push({ path: `${base}.role`, message: `must be one of: ${validRoles.join(', ')}` });
181
+ }
182
+ if (m.owns !== undefined && !isArrayOfStrings(m.owns)) {
183
+ errors.push({ path: `${base}.owns`, message: 'must be array of strings' });
184
+ }
185
+ if (m.consumes !== undefined && !isArrayOfStrings(m.consumes)) {
186
+ errors.push({ path: `${base}.consumes`, message: 'must be array of strings' });
187
+ }
188
+ });
189
+ }
190
+
191
+ // dependencies — optional
192
+ if (obj.dependencies !== undefined) {
193
+ if (!Array.isArray(obj.dependencies)) {
194
+ errors.push({ path: '$.dependencies', message: 'must be array if present' });
195
+ } else {
196
+ const memberIds = new Set(
197
+ Array.isArray(obj.members) ? obj.members.map((m) => m && m.id).filter(Boolean) : [],
198
+ );
199
+ const validKinds = ['api-contract', 'library', 'deploy', 'build-time', 'other'];
200
+ obj.dependencies.forEach((d, i) => {
201
+ const base = `$.dependencies[${i}]`;
202
+ if (d === null || typeof d !== 'object' || Array.isArray(d)) {
203
+ errors.push({ path: base, message: 'must be an object' });
204
+ return;
205
+ }
206
+ if (typeof d.from !== 'string' || !memberIds.has(d.from)) {
207
+ errors.push({ path: `${base}.from`, message: 'must reference a known member id' });
208
+ }
209
+ if (typeof d.to !== 'string' || !memberIds.has(d.to)) {
210
+ errors.push({ path: `${base}.to`, message: 'must reference a known member id' });
211
+ }
212
+ if (!validKinds.includes(d.kind)) {
213
+ errors.push({ path: `${base}.kind`, message: `must be one of: ${validKinds.join(', ')}` });
214
+ }
215
+ });
216
+ }
217
+ }
218
+
219
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
220
+ }
221
+
222
+ function isArrayOfStrings(v) {
223
+ return Array.isArray(v) && v.every((s) => typeof s === 'string');
224
+ }
225
+
226
+ /**
227
+ * Clear the findRoot memo cache. Tests use this; production code
228
+ * doesn't need to.
229
+ */
230
+ function _clearRootCache() {
231
+ rootCache.clear();
232
+ }
233
+
234
+ module.exports = {
235
+ MAX_WALK_DEPTH,
236
+ MAX_PARENT_WALK_DEFAULT,
237
+ MANIFEST_FILENAME,
238
+ resolveMaxParentWalk,
239
+ findRoot,
240
+ loadManifest,
241
+ listMembers,
242
+ findMember,
243
+ validateManifest,
244
+ _clearRootCache,
245
+ };