@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,79 @@
1
+ # {{NAME}} — Ecosystem (Coordination Layer)
2
+
3
+ > **You are in a momentum ecosystem coordination root — NOT a project.**
4
+ > This directory coordinates work across multiple sibling project repos.
5
+ > Auto-managed by `momentum`. The content above `## Project Extensions`
6
+ > may be refreshed by `momentum upgrade`.
7
+
8
+ ## What this directory is
9
+
10
+ | Path | Purpose |
11
+ |------|---------|
12
+ | `ecosystem.json` | Manifest: members, roles, dependency edges |
13
+ | `initiatives/` | Cross-repo feature files (`NNNN-<slug>.md`) — first-class records of features that span >1 member |
14
+ | `sessions/` | Auto-appended daily activity log (`YYYY-MM-DD.md`); member-repo hooks write here |
15
+ | `.state/` | Runtime state (gitignored) — active initiative pointer, etc. |
16
+ | `CLAUDE.md` / `AGENTS.md` | These instructions |
17
+
18
+ **There are no `specs/` here.** Per-project specs, phases, backlog, and history stay in each member repo. The ecosystem layer is strictly additive — it never writes into a member's `specs/`.
19
+
20
+ ## Cross-repo work? Write an initiative — NEVER plan implementation here
21
+
22
+ If a feature, refactor, or coordination effort touches MORE THAN ONE member repo, the entry point is an **initiative**:
23
+
24
+ ```bash
25
+ momentum ecosystem initiative create <slug> \
26
+ --why "<one-paragraph motivation>" \
27
+ --repos <member-id-1>,<member-id-2> \
28
+ --owner <you>
29
+ ```
30
+
31
+ This writes `initiatives/NNNN-<slug>.md` and sets it as active. The initiative is the *only* place cross-repo scope, deploy chronology, and links to per-repo decisions live. Per-repo implementation phases stay in each member's `specs/phases/`.
32
+
33
+ **Anti-pattern:** do not create planning notes, task lists, or "phase" files directly in this coordination root. There is no `specs/phases/`. If you find yourself reaching for one, you are working in the wrong layer — switch to a member repo or create an initiative.
34
+
35
+ ## Orchestration primitives (use these for multi-repo work)
36
+
37
+ These are slash commands you should invoke when working across member repos. Each has a CLI fallback under `momentum <name>`; the slash command is the LLM-synthesis door, the CLI is the keyword/scripted door.
38
+
39
+ | Primitive | When to use |
40
+ |---|---|
41
+ | `/scout <repo>` | Read-only context fetch from another member ("what's in `<repo>/specs/status.md` right now?"). Use before planning any cross-repo work. |
42
+ | `/dispatch <r1> <r2> "<intent>"` | Parallel multi-repo audit/investigation ("audit how X is implemented across A, B, C"). Spawns one sub-agent per repo and synthesises. **Do NOT use `momentum dispatch` from the CLI for synthesis** — CLI mode produces keyword summaries only; use the slash command. |
43
+ | `/handoff <repo>` | Transfer context to an agent working in another member repo. Writes a structured handoff into the target's `.momentum/inbox/`. The receiving agent picks it up via `/continue` on session start. |
44
+ | `/continue` | Pick up a pending handoff from this repo's inbox (SessionStart hook surfaces these automatically). |
45
+ | `/initiative create <slug>` | Same as the CLI above; the slash door also wires the active initiative for follow-up `/session log` entries. |
46
+ | `/session log "<note>"` | Append a narrative entry to today's `sessions/YYYY-MM-DD.md`. Auto-events (commits/PRs) are logged by the PostToolUse hook in each member; use `/session log` for decisions, problems hit, checkpoint notes. |
47
+
48
+ ## Discovery — how to find current state
49
+
50
+ ```bash
51
+ momentum ecosystem status # manifest summary + per-member git state
52
+ cat ecosystem.json # raw manifest
53
+ ls initiatives/ # all initiatives, active marked in .state/active-initiative
54
+ tail sessions/$(date -u +%F).md # what happened today
55
+ ```
56
+
57
+ ## Quick member management
58
+
59
+ ```bash
60
+ momentum ecosystem add ../<repo-name> # register a member (must already be `momentum init`-installed)
61
+ momentum ecosystem remove <member-id> # unregister
62
+ ```
63
+
64
+ `add` also injects a fenced pointer block into the member's `CLAUDE.md`/`AGENTS.md` so agents working *inside* the member know they're part of this ecosystem and can reach the orchestration primitives.
65
+
66
+ ## Docs
67
+
68
+ - Full ecosystem mode: https://trymomentum.github.io/ecosystem/
69
+ - Orchestration primitives: https://trymomentum.github.io/orchestration/
70
+ - Concepts: https://trymomentum.github.io/concepts/
71
+
72
+ ---
73
+
74
+ ## Project Extensions
75
+
76
+ > Everything below this heading is preserved across `momentum upgrade`.
77
+ > Add ecosystem-specific notes, conventions, cross-repo references,
78
+ > escalation contacts, etc. here. Anything above this heading is
79
+ > managed by momentum and may be refreshed on upgrade.
@@ -0,0 +1,14 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "bash scripts/sessionstart-handoff.sh"
9
+ }
10
+ ]
11
+ }
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,50 @@
1
+ ---
2
+ id: 0
3
+ slug: replace-me
4
+ title: Replace me
5
+ status: in-progress
6
+ started: 1970-01-01
7
+ owner: replace-me
8
+ repos: []
9
+ ---
10
+
11
+ # Initiative {{ID}} — {{TITLE}}
12
+
13
+ > Cross-repo feature spanning {{REPOS}}.
14
+ > Started {{STARTED}} by {{OWNER}}.
15
+
16
+ ## Why
17
+
18
+ One short paragraph that captures the motivation for this initiative.
19
+ What problem are we solving? Why does it span multiple repos? What
20
+ becomes possible once it ships?
21
+
22
+ ## Per-repo contributions
23
+
24
+ For each member repo touched, list the contributing branches, PRs,
25
+ and phases. Update as work lands.
26
+
27
+ - **{{REPO_1}}**: …
28
+ - **{{REPO_2}}**: …
29
+
30
+ ## Linked decisions
31
+
32
+ ADRs, scope changes, or decisions taken in any member repo's
33
+ `specs/decisions/` or `specs/phases/<phase>/history.md` that this
34
+ initiative implements or depends on. Cross-repo links allowed.
35
+
36
+ - …
37
+
38
+ ## Deploy chronology
39
+
40
+ When the initiative ships in stages across repos, log the order here.
41
+ Timestamps in UTC. Format:
42
+ `YYYY-MM-DD HH:MMZ — <member> <sha> — <one-line summary>`
43
+
44
+ - …
45
+
46
+ ## Close
47
+
48
+ Populated by `momentum ecosystem initiative close <slug>`. Captures:
49
+ what shipped, what was deferred, what was learned. Mirrors the role of
50
+ a phase retrospective at the cross-repo level.
@@ -0,0 +1,80 @@
1
+ # Subagent Dispatch Contract
2
+
3
+ This contract defines the core coordination patterns for how AI coding agents (such as Claude Code or Antigravity) dispatch background subagents or sub-tasks autonomously during a development phase.
4
+
5
+ Adapters that declare `subagents: true` in their capabilities must implement these dispatch interfaces to keep the core workflow tool-agnostic.
6
+
7
+ ---
8
+
9
+ ## 1. Persona and Role Declarations
10
+
11
+ When spawning a subagent, the parent agent must declare a specialized **Persona** or **Role** from this list. Do not use generic or ad-hoc personas.
12
+
13
+ | Role Name | Scope & Focus | Typical Tools Needed |
14
+ |-----------|---------------|----------------------|
15
+ | `QA Engineer` | Writes test files, designs edge-case inputs, runs validations | `read_file`, `write_file`, `command` |
16
+ | `Security Reviewer` | Analyzes code for secrets leakage, unsafe operations, overflow risks | `read_file` |
17
+ | `Architecture Guard` | Asserts alignment with SOLID, DIP boundaries, and design specs | `read_file` |
18
+ | `Researcher` | Gathers context, reads library documentation, runs web searches | `read_file`, `read_url`, `web_search` |
19
+ | `Developer` | Implements logic changes, fixes bugs, formats code | `read_file`, `write_file`, `command` |
20
+
21
+ ---
22
+
23
+ ## 2. Dispatch Patterns
24
+
25
+ The engine supports two primary coordination patterns for executing subagents:
26
+
27
+ ### Pattern A: Sequential with Feedback
28
+
29
+ Used for iterative refinement of a single task (such as bug fixing or feature implementation).
30
+
31
+ ```mermaid
32
+ sequenceDiagram
33
+ Parent Agent->>Subagent (Developer): Run task with instructions
34
+ Subagent (Developer)->>Parent Agent: Proposed edits & code
35
+ Parent Agent->>Parent Agent: Run Group Verification Command
36
+ alt Verification Passes
37
+ Parent Agent->>Parent Agent: Commit & check tasks
38
+ else Verification Fails (Strike N)
39
+ Parent Agent->>Subagent (Developer): Return stdout/stderr feedback & reframe instructions
40
+ end
41
+ ```
42
+
43
+ * **Strike/Retry Budget**: The sequential loop is capped at a maximum of **3 strikes** (reframes) before the parent agent must perform a discretionary stop and request user intervention.
44
+
45
+ ### Pattern B: Parallel Fan-Out
46
+
47
+ Used for comprehensive review, research, or cross-cutting analysis.
48
+
49
+ ```mermaid
50
+ sequenceDiagram
51
+ Parent Agent->>Subagent (Security): Review branch changes
52
+ Parent Agent->>Subagent (QA): Review branch changes
53
+ Parent Agent->>Subagent (Arch): Review branch changes
54
+ Subagent (Security)->>Parent Agent: Report findings
55
+ Subagent (QA)->>Parent Agent: Report findings
56
+ Subagent (Arch)->>Parent Agent: Report findings
57
+ Parent Agent->>Parent Agent: Aggregate into markdown report
58
+ ```
59
+
60
+ * **Aggregation**: Parent agents must aggregate parallel subagent outputs into a single consolidated, user-visible markdown document (e.g. `/review-code` aggregates security, QA, and architectural reviews).
61
+
62
+ ---
63
+
64
+ ## 3. Error Handling and Aggression
65
+
66
+ 1. **Failing Fast**: If a subagent encounters a permission error, missing credentials, or external billing failure, it must immediately report this to the parent. The parent must perform a discretionary stop.
67
+ 2. **No Leaked Side-Effects**: Subagents must only operate within the target workspace. Any temporary scratch directories must be cleaned up on completion.
68
+ 3. **Validation Dominance**: The parent agent's validation commands always override a subagent's claim of completion. A task is only `[x]` complete if the parent-executed verification command exits `0`.
69
+
70
+ ---
71
+
72
+ ## 4. Adapter Integration Guides
73
+
74
+ ### Claude Code (`claude-code`)
75
+ * **Wiring**: Claude Code dispatches subagents using the native `Task` tool.
76
+ * **Context Sizing**: Limit subagent task descriptions to keep prompt overhead low.
77
+
78
+ ### Antigravity (`antigravity`)
79
+ * **Wiring**: Antigravity dispatches subagents using the native `invoke_subagent` and `define_subagent` tools.
80
+ * **Artifact Alignment**: The parent Antigravity agent updates `task.md` and `implementation_plan.md` in the parent context when subagents report success.
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+ # momentum commit-msg hook (Phase 19 — Lifecycle Hardening).
3
+ # Validates that the commit subject is a Conventional Commit. All logic lives
4
+ # in run-check.js (shared with the installer + /hotfix, unit-tested) so this
5
+ # wrapper stays trivial. Vendor-neutral: pure git, runs under any agent.
6
+ #
7
+ # Bypass for emergencies: MOMENTUM_SKIP_HOOKS=1 git commit ...
8
+ dir=$(dirname "$0")
9
+ if command -v node >/dev/null 2>&1; then
10
+ exec node "$dir/run-check.js" commit-msg "$1"
11
+ fi
12
+ # No node on PATH — cannot enforce; allow rather than block the commit.
13
+ echo "momentum: node not found on PATH — skipping commit-msg validation." >&2
14
+ exit 0
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Phase 19 — Lifecycle Hardening, Group 0.
5
+ *
6
+ * Canonical contract for momentum's git-lifecycle + ad-hoc-work enforcement.
7
+ * PURE functions + constants only — NO fs access, NO process side effects —
8
+ * so that the git hooks (core/git-hooks/run-check.js), the installer
9
+ * (bin/momentum.js), and the /hotfix command all share ONE source of truth,
10
+ * and the logic is unit-testable in isolation.
11
+ *
12
+ * Vendor-neutral by design (DIP): pure git, no forge API. Enforcement lives in
13
+ * plain git hooks that work on any forge and under any agent. See
14
+ * core/lifecycle-contract.md for the full reference.
15
+ */
16
+
17
+ const CONTRACT = {
18
+ // Hooks ship as plain scripts in a tracked dir, wired via
19
+ // `git config core.hooksPath`. No husky/lefthook — zero dependencies.
20
+ hooksPath: '.githooks',
21
+
22
+ // Emergency bypass for ALL momentum git hooks (e.g. MOMENTUM_SKIP_HOOKS=1).
23
+ skipEnv: 'MOMENTUM_SKIP_HOOKS',
24
+
25
+ // Single-use sentinel authorizing ONE push to a protected branch. Created
26
+ // by the agent only after the user approves a merge; consumed on use.
27
+ mergeApprovedSentinel: '.momentum/merge-approved',
28
+
29
+ // Direct pushes to these branches are blocked unless the sentinel exists.
30
+ // 'master' included for downstream repos that haven't renamed to 'main'.
31
+ protectedBranches: ['main', 'master', 'staging'],
32
+
33
+ // Canonical Verification-Evidence heading in retrospective.md (Rule 12).
34
+ // The same heading gates ad-hoc records (specs/adhoc/<id>/record.md).
35
+ verifyEvidenceHeading: '## Verification Evidence',
36
+
37
+ // Allowed Conventional-Commit types: momentum's stated set
38
+ // (feat/fix/docs/refactor/chore/infra) plus the standard extras the project
39
+ // actually uses (test/perf/build/ci/style/revert).
40
+ conventionalTypes: [
41
+ 'feat', 'fix', 'docs', 'refactor', 'chore', 'infra',
42
+ 'test', 'perf', 'build', 'ci', 'style', 'revert',
43
+ ],
44
+
45
+ // First-class work types (Rule 14). 'phase' = full ceremony; 'quick-task' =
46
+ // ad-hoc record + Rule 12 gate, no phase scaffold; 'spike' = declared,
47
+ // gate-exempt, throwaway.
48
+ workTypes: ['phase', 'quick-task', 'spike'],
49
+ };
50
+
51
+ // Subjects that bypass type validation (git-generated or WIP markers).
52
+ // 'merge: ' covers the house merge style `merge: x → y` alongside git's
53
+ // default `Merge ` (BUG-015 — CLAUDE.md Naming Conventions + repo history).
54
+ const COMMIT_SUBJECT_BYPASS = /^(Merge |merge: |Revert "|fixup!|squash!|amend!|Initial commit$)/;
55
+
56
+ /**
57
+ * True when the emergency bypass env var is set to a truthy value.
58
+ * @param {NodeJS.ProcessEnv} [env]
59
+ */
60
+ function skipRequested(env) {
61
+ const e = env || (typeof process !== 'undefined' ? process.env : {});
62
+ const v = e[CONTRACT.skipEnv];
63
+ return v != null && v !== '' && v !== '0' && String(v).toLowerCase() !== 'false';
64
+ }
65
+
66
+ /**
67
+ * The first non-empty, non-comment line of a commit message (the subject).
68
+ * @param {string} message
69
+ * @returns {string}
70
+ */
71
+ function firstMeaningfulLine(message) {
72
+ if (!message) return '';
73
+ for (const raw of String(message).split('\n')) {
74
+ const line = raw.trim();
75
+ if (line === '' || line.startsWith('#')) continue;
76
+ return line;
77
+ }
78
+ return '';
79
+ }
80
+
81
+ /**
82
+ * True when a commit subject conforms to Conventional Commits (or is a
83
+ * git-generated/WIP subject that bypasses validation).
84
+ * @param {string} subject
85
+ * @param {string[]} [types]
86
+ */
87
+ function isValidCommitSubject(subject, types) {
88
+ const s = (subject || '').trim();
89
+ if (s === '') return false;
90
+ if (COMMIT_SUBJECT_BYPASS.test(s)) return true;
91
+ const typeAlt = (types || CONTRACT.conventionalTypes).join('|');
92
+ // type(scope)!: description — scope and ! optional; single space after colon.
93
+ const re = new RegExp('^(' + typeAlt + ')(\\([^)]+\\))?(!)?: .+');
94
+ return re.test(s);
95
+ }
96
+
97
+ /**
98
+ * Validate a full commit message; returns { subject, valid }.
99
+ * @param {string} message
100
+ * @param {string[]} [types]
101
+ */
102
+ function validateCommitMessage(message, types) {
103
+ const subject = firstMeaningfulLine(message);
104
+ return { subject, valid: isValidCommitSubject(subject, types) };
105
+ }
106
+
107
+ /** refs/heads/main -> 'main'; non-branch refs -> null. */
108
+ function branchFromRef(ref) {
109
+ if (!ref) return null;
110
+ const m = /^refs\/heads\/(.+)$/.exec(ref);
111
+ return m ? m[1] : null;
112
+ }
113
+
114
+ /** refs/tags/v1.2.3 -> 'v1.2.3'; non-tag refs -> null. */
115
+ function tagFromRef(ref) {
116
+ if (!ref) return null;
117
+ const m = /^refs\/tags\/(.+)$/.exec(ref);
118
+ return m ? m[1] : null;
119
+ }
120
+
121
+ /** True for a semver release tag (v1.2.3, v1.2.3-rc.1, ...). */
122
+ function isReleaseTag(tag) {
123
+ return !!tag && /^v\d+\.\d+\.\d+/.test(tag);
124
+ }
125
+
126
+ /** True when `branch` is in the protected list. */
127
+ function branchIsProtected(branch, list) {
128
+ return !!branch && (list || CONTRACT.protectedBranches).includes(branch);
129
+ }
130
+
131
+ /**
132
+ * True when `content` (a retrospective.md or ad-hoc record) has a non-empty
133
+ * `## Verification Evidence` section — i.e. ≥1 non-blank, non-heading line
134
+ * before the next heading.
135
+ * @param {string} content
136
+ * @param {string} [heading]
137
+ */
138
+ function retroHasEvidence(content, heading) {
139
+ if (!content) return false;
140
+ const target = (heading || CONTRACT.verifyEvidenceHeading).trim();
141
+ const lines = String(content).split('\n');
142
+ const idx = lines.findIndex((l) => l.trim() === target);
143
+ if (idx === -1) return false;
144
+ for (let i = idx + 1; i < lines.length; i++) {
145
+ const line = lines[i].trim();
146
+ if (/^#{1,6}\s/.test(line)) break; // next heading ends the section
147
+ if (line !== '') return true;
148
+ }
149
+ return false;
150
+ }
151
+
152
+ /**
153
+ * Parse a pre-push stdin line: "<localRef> <localSha> <remoteRef> <remoteSha>".
154
+ * Returns null for malformed lines.
155
+ */
156
+ function parsePrePushLine(line) {
157
+ const parts = (line || '').trim().split(/\s+/);
158
+ if (parts.length < 4 || parts[0] === '') return null;
159
+ const [localRef, localSha, remoteRef, remoteSha] = parts;
160
+ return { localRef, localSha, remoteRef, remoteSha };
161
+ }
162
+
163
+ module.exports = {
164
+ CONTRACT,
165
+ COMMIT_SUBJECT_BYPASS,
166
+ skipRequested,
167
+ firstMeaningfulLine,
168
+ isValidCommitSubject,
169
+ validateCommitMessage,
170
+ branchFromRef,
171
+ tagFromRef,
172
+ isReleaseTag,
173
+ branchIsProtected,
174
+ retroHasEvidence,
175
+ parsePrePushLine,
176
+ };
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+ # momentum pre-push hook (Phase 19 — Lifecycle Hardening).
3
+ # Blocks (a) direct pushes to protected branches without the single-use
4
+ # .momentum/merge-approved sentinel, and (b) release-tag pushes lacking
5
+ # verification evidence (Rule 12). Logic lives in run-check.js (unit-tested).
6
+ # Vendor-neutral: pure git, runs under any agent on any forge.
7
+ #
8
+ # git passes <remote> <url> as args and the pushed refs on stdin; we forward
9
+ # both. Bypass for emergencies: MOMENTUM_SKIP_HOOKS=1 git push ...
10
+ dir=$(dirname "$0")
11
+ if command -v node >/dev/null 2>&1; then
12
+ exec node "$dir/run-check.js" pre-push "$@"
13
+ fi
14
+ echo "momentum: node not found on PATH — skipping pre-push checks." >&2
15
+ exit 0
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Phase 19 — Lifecycle Hardening, Group 1.
6
+ *
7
+ * momentum git-hook dispatcher: a thin I/O layer over the pure contract in
8
+ * ./contract.js. Invoked by the .githooks/commit-msg and .githooks/pre-push
9
+ * shell wrappers. All decision logic lives in contract.js (unit-tested);
10
+ * this file only does fs/stdin/exit-code plumbing.
11
+ *
12
+ * Vendor-neutral: pure git, no forge API. Runs under any agent on any forge.
13
+ */
14
+
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+ const C = require('./contract');
18
+
19
+ // git invokes hooks with cwd = repo root.
20
+ function repoRoot() {
21
+ return process.cwd();
22
+ }
23
+
24
+ function fail(msg) {
25
+ process.stderr.write(`\n✖ momentum git hook blocked this action:\n ${msg}\n`);
26
+ process.stderr.write(`\n Emergency bypass (use sparingly): ${C.CONTRACT.skipEnv}=1 <git command>\n\n`);
27
+ process.exit(1);
28
+ }
29
+
30
+ // ── commit-msg ────────────────────────────────────────────────────────────────
31
+ function commitMsg(msgFile) {
32
+ if (C.skipRequested()) process.exit(0);
33
+ let content = '';
34
+ try {
35
+ content = fs.readFileSync(msgFile, 'utf8');
36
+ } catch {
37
+ process.exit(0); // no message file — let git handle it
38
+ }
39
+ const { subject, valid } = C.validateCommitMessage(content);
40
+ if (valid) process.exit(0);
41
+ fail(
42
+ `commit subject is not a Conventional Commit:\n "${subject}"\n` +
43
+ ` Expected: <type>(scope?)!: description\n` +
44
+ ` where <type> is one of: ${C.CONTRACT.conventionalTypes.join(', ')}`
45
+ );
46
+ }
47
+
48
+ // ── pre-push ──────────────────────────────────────────────────────────────────
49
+ function findLatestRetro(root) {
50
+ const specsDir = path.join(root, 'specs');
51
+ if (!fs.existsSync(specsDir)) return null;
52
+ const candidates = [];
53
+ const walk = (dir) => {
54
+ let entries;
55
+ try {
56
+ entries = fs.readdirSync(dir, { withFileTypes: true });
57
+ } catch {
58
+ return;
59
+ }
60
+ for (const e of entries) {
61
+ if (e.name.startsWith('._')) continue;
62
+ const p = path.join(dir, e.name);
63
+ if (e.isDirectory()) {
64
+ walk(p);
65
+ } else if (
66
+ e.name === 'retrospective.md' ||
67
+ (e.name === 'record.md' && dir.split(path.sep).includes('adhoc'))
68
+ ) {
69
+ try {
70
+ candidates.push({ p, mtime: fs.statSync(p).mtimeMs });
71
+ } catch {
72
+ /* ignore */
73
+ }
74
+ }
75
+ }
76
+ };
77
+ walk(specsDir);
78
+ if (!candidates.length) return null;
79
+ candidates.sort((a, b) => b.mtime - a.mtime);
80
+ return candidates[0].p;
81
+ }
82
+
83
+ function prePush() {
84
+ if (C.skipRequested()) process.exit(0);
85
+
86
+ let input = '';
87
+ try {
88
+ input = fs.readFileSync(0, 'utf8'); // git pipes ref lines on stdin
89
+ } catch {
90
+ input = '';
91
+ }
92
+ const root = repoRoot();
93
+ const lines = input.split('\n').map((l) => l.trim()).filter(Boolean);
94
+
95
+ for (const line of lines) {
96
+ const parsed = C.parsePrePushLine(line);
97
+ if (!parsed) continue;
98
+ if (/^0+$/.test(parsed.localSha)) continue; // branch/tag deletion — skip
99
+
100
+ // (1) Block direct push to a protected branch without the approval sentinel.
101
+ const branch = C.branchFromRef(parsed.remoteRef);
102
+ if (C.branchIsProtected(branch)) {
103
+ const sentinel = path.join(root, C.CONTRACT.mergeApprovedSentinel);
104
+ if (fs.existsSync(sentinel)) {
105
+ try {
106
+ fs.unlinkSync(sentinel); // single-use: consume on push
107
+ } catch {
108
+ /* ignore */
109
+ }
110
+ process.stderr.write(
111
+ ` momentum: '${C.CONTRACT.mergeApprovedSentinel}' consumed — push to '${branch}' authorized.\n`
112
+ );
113
+ } else {
114
+ fail(
115
+ `direct push to protected branch '${branch}' is blocked.\n` +
116
+ ` Merging to '${branch}' needs explicit human approval. After the user approves,\n` +
117
+ ` authorize a single push with the single-use sentinel:\n` +
118
+ ` touch ${C.CONTRACT.mergeApprovedSentinel}\n` +
119
+ ` (it is consumed on push).`
120
+ );
121
+ }
122
+ }
123
+
124
+ // (2) Block a release-tag push lacking verification evidence (Rule 12 / FEAT-019).
125
+ const tag = C.tagFromRef(parsed.remoteRef);
126
+ if (C.isReleaseTag(tag)) {
127
+ const retro = findLatestRetro(root);
128
+ // Only enforce where the retrospective convention is in use. A project
129
+ // with no retrospective at all is not gated.
130
+ if (retro) {
131
+ let content = '';
132
+ try {
133
+ content = fs.readFileSync(retro, 'utf8');
134
+ } catch {
135
+ content = '';
136
+ }
137
+ if (!C.retroHasEvidence(content)) {
138
+ fail(
139
+ `release tag '${tag}' is blocked: the most recent retrospective\n` +
140
+ ` (${path.relative(root, retro)})\n` +
141
+ ` has no non-empty '${C.CONTRACT.verifyEvidenceHeading}' section.\n` +
142
+ ` Capture fresh verification output there (Rule 12) before tagging a release.`
143
+ );
144
+ }
145
+ }
146
+ }
147
+ }
148
+ process.exit(0);
149
+ }
150
+
151
+ // ── dispatch ──────────────────────────────────────────────────────────────────
152
+ const [cmd, ...rest] = process.argv.slice(2);
153
+ if (cmd === 'commit-msg') {
154
+ commitMsg(rest[0]);
155
+ } else if (cmd === 'pre-push') {
156
+ prePush();
157
+ } else {
158
+ process.stderr.write(`momentum run-check: unknown command '${cmd}'\n`);
159
+ process.exit(0); // unknown → don't block
160
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "description": "SHA-256 of every historically shipped revision of core/agent-rules/project.md (retired by Phase 23 / ADR-0004). Used by `momentum upgrade` to distinguish a pristine installed copy (delete) from a user-customized one (keep + deprecation warning). Harvested 2026-07-04 via: git log --all --format=%H -- core/agent-rules/project.md | while read c; do git ls-tree $c -- core/agent-rules/project.md; done | awk '{print $3}' | sort -u | xargs -I{} sh -c 'git cat-file blob {} | shasum -a 256'",
3
+ "sha256": [
4
+ "42956f18f619aaa2a666326b3873ca41d8aa6c530d3eff2240f12592bdcf1916",
5
+ "e9e99f7b4230bfdb95a44fe967d5d7559eec7d7f559158dca3e822887b77988a",
6
+ "59117d0f582f1f387a9851dc322b91e97d6eba19d091de2b2a258238ad6f4629",
7
+ "bd41b8a1369f88ffaaee13f7d441c845b2ab30789695c8460b7a2d1f7acadfd7",
8
+ "175bc78d3370130c195fb0a5c4b1cc151c5594e8c7eb1c759db0906abaa4b8ee",
9
+ "1d3815740b5956e9524ab8f3acf3cccfe11c20560c08d7f1d0aef2ca0a0f14a9",
10
+ "e35fd7461cab76fb61b8bf601c837ac278b476507d9d56319281a9a332038313"
11
+ ]
12
+ }
@@ -0,0 +1,14 @@
1
+ ## Navigation (Where to Find Things)
2
+
3
+ | Question | File |
4
+ |----------|------|
5
+ | Current state / what phase? | `specs/status.md` |
6
+ | What's in the backlog? | `specs/backlog/backlog.md` |
7
+ | Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
8
+ | Why was X chosen? | `specs/decisions/NNNN-*.md` |
9
+ | Roadmap / timeline? | `specs/planning/roadmap.md` |
10
+ | How to contribute? | `docs/developer-guide.md` |
11
+
12
+ > **First file to read: ALWAYS `specs/status.md`.**
13
+
14
+ ---