@limina-labs/momentum 0.31.2 → 0.33.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.
Files changed (34) hide show
  1. package/README.md +7 -0
  2. package/adapters/antigravity/instructions/AGENTS.md +6 -1
  3. package/adapters/codex/instructions/AGENTS.md +7 -1
  4. package/adapters/codex/instructions/surfaces.md +1 -0
  5. package/adapters/opencode/instructions/AGENTS.md +7 -1
  6. package/adapters/opencode/instructions/surfaces.md +1 -0
  7. package/bin/momentum.js +259 -5
  8. package/core/commands/brainstorm-idea.md +21 -2
  9. package/core/commands/brainstorm-phase.md +15 -0
  10. package/core/commands/complete-phase.md +41 -23
  11. package/core/commands/migrate.md +7 -0
  12. package/core/commands/start-phase.md +33 -3
  13. package/core/commands/start-project.md +234 -65
  14. package/core/commands/sync-config.md +43 -0
  15. package/core/commands/validate.md +24 -0
  16. package/core/config-templates.js +77 -0
  17. package/core/config.js +520 -0
  18. package/core/foundation-placeholder-hashes.json +18 -0
  19. package/core/git-hooks/contract.js +98 -0
  20. package/core/git-hooks/run-check.js +22 -1
  21. package/core/instructions/navigation.md +2 -1
  22. package/core/instructions/rules-body.md +4 -0
  23. package/core/lib/okf-types.js +1 -0
  24. package/core/project-lifecycle.md +68 -0
  25. package/core/specs-templates/CLAUDE.md +6 -1
  26. package/core/specs-templates/specs/README.md +8 -3
  27. package/core/specs-templates/specs/config.md +31 -0
  28. package/core/specs-templates/specs/index.md +0 -1
  29. package/core/specs-templates/specs/status.md +12 -5
  30. package/package.json +1 -1
  31. package/core/specs-templates/specs/planning/roadmap.md +0 -23
  32. package/core/specs-templates/specs/vision/principles.md +0 -16
  33. package/core/specs-templates/specs/vision/project-charter.md +0 -35
  34. package/core/specs-templates/specs/vision/success-criteria.md +0 -19
package/README.md CHANGED
@@ -82,6 +82,12 @@ Work happens in **phases** — one repeatable unit of work, five stages:
82
82
 
83
83
  **Brainstorm → Plan → Execute → Verify → Release**
84
84
 
85
+ New projects begin with a one-time **founding** step: `/brainstorm-idea`
86
+ explores the idea (writes nothing), then `/start-project` authors the
87
+ charter, principles, success criteria, and roadmap from that brainstorm —
88
+ momentum never ships placeholder docs, and phase commands wait until the
89
+ project is founded.
90
+
85
91
  Your agent drives all of it: scopes through dialogue (disk writes gated until
86
92
  you approve), plans groups + tasks + acceptance criteria, commits per group
87
93
  with conventional commits, and marks tasks done only with passing evidence.
@@ -176,6 +182,7 @@ and is idempotent. (ADR-0005.)
176
182
  | 🧭 **Phases** | Plan → execute → verify → release. Every phase has a brainstorm, plan, tasks, history. |
177
183
  | 📋 **Backlog** | Bugs / features / tech debt / enhancements with priorities and per-item context. |
178
184
  | 📜 **History** | Append-only log of decisions, discoveries, scope changes. The *why* outlives any session. |
185
+ | ⚙️ **Config** | `specs/config.md` — forge, publish target, branch flow, test/build commands. Recipes adapt to your project, not npm+GitHub defaults. |
179
186
  | ⚖️ **Rules** | 13 autonomous agent rules — orient first, verify before claim, log every decision. |
180
187
  | 🛠️ **Skills** | ~15 slash commands your agent runs — start, complete, sync, review, validate. |
181
188
  | 🌐 **Multi-project** | Ecosystem mode (state layer) + Orchestration primitives (action layer) for cross-project work. |
@@ -14,7 +14,8 @@
14
14
  | What's in the backlog? | `specs/backlog/backlog.md` |
15
15
  | Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
16
16
  | Why was X chosen? | `specs/decisions/NNNN-*.md` |
17
- | Roadmap / timeline? | `specs/planning/roadmap.md` |
17
+ | Roadmap / timeline? | `specs/planning/roadmap.md` (authored at founding — `/start-project`) |
18
+ | Project config (forge, publish, branch flow)? | `specs/config.md` (inferred by `momentum init`, authored at `/start-project`) |
18
19
  | How to contribute? | `docs/developer-guide.md` |
19
20
 
20
21
  > **First file to read: ALWAYS `specs/status.md`.**
@@ -184,6 +185,10 @@ Before ANY work, read `specs/status.md`. This tells you:
184
185
  - What's blocking progress
185
186
  - What P0 items need attention
186
187
 
188
+ If `status.md` says **Not founded**, stop: the project has no charter or
189
+ roadmap yet — foundation docs are authored at founding, never scaffolded
190
+ (`core/project-lifecycle.md`). Route to `/start-project` before any phase work.
191
+
187
192
  ### Rule 2: Auto-Update Tracking After Changes
188
193
 
189
194
  After completing ANY meaningful work, automatically update:
@@ -14,7 +14,8 @@
14
14
  | What's in the backlog? | `specs/backlog/backlog.md` |
15
15
  | Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
16
16
  | Why was X chosen? | `specs/decisions/NNNN-*.md` |
17
- | Roadmap / timeline? | `specs/planning/roadmap.md` |
17
+ | Roadmap / timeline? | `specs/planning/roadmap.md` (authored at founding — `/start-project`) |
18
+ | Project config (forge, publish, branch flow)? | `specs/config.md` (inferred by `momentum init`, authored at `/start-project`) |
18
19
  | How to contribute? | `docs/developer-guide.md` |
19
20
 
20
21
  > **First file to read: ALWAYS `specs/status.md`.**
@@ -47,6 +48,7 @@ The shipped recipe set (one skill per recipe, all under
47
48
  | log | Append a manual narrative entry to the history of the phase bound to your branch (fallback: status.md) |
48
49
  | migrate | Onboard an existing project into momentum |
49
50
  | validate | Check spec structure health |
51
+ | sync-config | Re-infer project shape, show config drift, apply on approval (ENH-062)
50
52
  | ecosystem | Cross-repo ecosystem coordination |
51
53
  | initiative | Manage cross-repo initiatives |
52
54
  | session | Append a manual narrative entry to today's ecosystem session log |
@@ -237,6 +239,10 @@ Before ANY work, read `specs/status.md`. This tells you:
237
239
  - What's blocking progress
238
240
  - What P0 items need attention
239
241
 
242
+ If `status.md` says **Not founded**, stop: the project has no charter or
243
+ roadmap yet — foundation docs are authored at founding, never scaffolded
244
+ (`core/project-lifecycle.md`). Route to `/start-project` before any phase work.
245
+
240
246
  ### Rule 2: Auto-Update Tracking After Changes
241
247
 
242
248
  After completing ANY meaningful work, automatically update:
@@ -24,6 +24,7 @@ The shipped recipe set (one skill per recipe, all under
24
24
  | log | Append a manual narrative entry to the history of the phase bound to your branch (fallback: status.md) |
25
25
  | migrate | Onboard an existing project into momentum |
26
26
  | validate | Check spec structure health |
27
+ | sync-config | Re-infer project shape, show config drift, apply on approval (ENH-062)
27
28
  | ecosystem | Cross-repo ecosystem coordination |
28
29
  | initiative | Manage cross-repo initiatives |
29
30
  | session | Append a manual narrative entry to today's ecosystem session log |
@@ -14,7 +14,8 @@
14
14
  | What's in the backlog? | `specs/backlog/backlog.md` |
15
15
  | Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
16
16
  | Why was X chosen? | `specs/decisions/NNNN-*.md` |
17
- | Roadmap / timeline? | `specs/planning/roadmap.md` |
17
+ | Roadmap / timeline? | `specs/planning/roadmap.md` (authored at founding — `/start-project`) |
18
+ | Project config (forge, publish, branch flow)? | `specs/config.md` (inferred by `momentum init`, authored at `/start-project`) |
18
19
  | How to contribute? | `docs/developer-guide.md` |
19
20
 
20
21
  > **First file to read: ALWAYS `specs/status.md`.**
@@ -47,6 +48,7 @@ The shipped recipe set (one command per recipe):
47
48
  | lanes | Work with lanes — concurrent workstreams in one repo (Rule 15) |
48
49
  | migrate | Onboard an existing project into momentum |
49
50
  | validate | Check spec structure health |
51
+ | sync-config | Re-infer project shape, show config drift, apply on approval (ENH-062)
50
52
  | ecosystem | Cross-repo ecosystem coordination |
51
53
  | initiative | Manage cross-repo initiatives |
52
54
  | session | Append a manual narrative entry to today's ecosystem session log |
@@ -150,6 +152,10 @@ Before ANY work, read `specs/status.md`. This tells you:
150
152
  - What's blocking progress
151
153
  - What P0 items need attention
152
154
 
155
+ If `status.md` says **Not founded**, stop: the project has no charter or
156
+ roadmap yet — foundation docs are authored at founding, never scaffolded
157
+ (`core/project-lifecycle.md`). Route to `/start-project` before any phase work.
158
+
153
159
  ### Rule 2: Auto-Update Tracking After Changes
154
160
 
155
161
  After completing ANY meaningful work, automatically update:
@@ -24,6 +24,7 @@ The shipped recipe set (one command per recipe):
24
24
  | lanes | Work with lanes — concurrent workstreams in one repo (Rule 15) |
25
25
  | migrate | Onboard an existing project into momentum |
26
26
  | validate | Check spec structure health |
27
+ | sync-config | Re-infer project shape, show config drift, apply on approval (ENH-062)
27
28
  | ecosystem | Cross-repo ecosystem coordination |
28
29
  | initiative | Manage cross-repo initiatives |
29
30
  | session | Append a manual narrative entry to today's ecosystem session log |
package/bin/momentum.js CHANGED
@@ -404,6 +404,219 @@ function migrateAgentRules(srcRoot, target, dests) {
404
404
  return 'kept-customized';
405
405
  }
406
406
 
407
+ /**
408
+ * Foundation docs are authored, not scaffolded (Phase 25 / ADR-0008).
409
+ * Legacy installs (≤ v0.31.2) shipped four placeholder foundation docs
410
+ * (vision/charter/principles/success-criteria + planning/roadmap.md). A copy
411
+ * whose frontmatter-stripped, whitespace-normalized body sha256 matches a
412
+ * historically shipped template (core/foundation-placeholder-hashes.json —
413
+ * frozen forever) carries provably zero user content: remove it, returning
414
+ * the project to the clean "not founded" signal that /start-project fills.
415
+ * Any file with user edits is untouched. specs/ is user content — never
416
+ * recorded in the managed manifest, so orphan cleanup can never touch it.
417
+ * Returns { removed: [rel], wouldRemove: [rel], keptAuthored, absent }.
418
+ */
419
+ function migrateFoundationDocs(srcRoot, target) {
420
+ const crypto = require('crypto');
421
+ const manifest = JSON.parse(
422
+ fs.readFileSync(
423
+ path.join(srcRoot, 'core', 'foundation-placeholder-hashes.json'),
424
+ 'utf8'
425
+ )
426
+ );
427
+ const result = { removed: [], wouldRemove: [], keptAuthored: 0, absent: 0 };
428
+ for (const [rel, hashes] of Object.entries(manifest.paths)) {
429
+ const abs = path.join(target, ...rel.split('/'));
430
+ if (!fileExists(abs)) { result.absent++; continue; }
431
+ // Normalization mirrors the manifest's recorded contract exactly:
432
+ // CRLF→LF, strip one leading YAML frontmatter block, trim, single \n.
433
+ let body = fs.readFileSync(abs, 'utf8').replace(/\r\n/g, '\n');
434
+ body = body.replace(/^---\n[\s\S]*?\n---\n/, '');
435
+ body = body.trim() + '\n';
436
+ const hash = crypto.createHash('sha256').update(body).digest('hex');
437
+ if (!hashes.includes(hash)) { result.keptAuthored++; continue; }
438
+ if (_dryRun) {
439
+ console.log(` ✋ would remove: ${rel} (untouched init placeholder — foundation docs are authored via /start-project, ADR-0008)`);
440
+ result.wouldRemove.push(rel);
441
+ continue;
442
+ }
443
+ fs.rmSync(abs);
444
+ try { fs.rmdirSync(path.dirname(abs)); } catch { /* dir not empty — fine */ }
445
+ console.log(` 🗑 removed: ${rel} (untouched init placeholder — foundation docs are authored via /start-project)`);
446
+ result.removed.push(rel);
447
+ }
448
+ if (result.removed.length || result.wouldRemove.length) {
449
+ console.log(' → project is not yet founded — run /start-project to author the foundation docs.');
450
+ }
451
+ return result;
452
+ }
453
+
454
+ /**
455
+ * Phase 26 — Project Config (ADR-0009).
456
+ *
457
+ * init: after the specs skeleton, infer + write `specs/config.md` and the
458
+ * derived `.momentum/config-cache.json` (only when the file is absent —
459
+ * re-init leaves an authored file alone).
460
+ *
461
+ * upgrade: write inferred config for FOUNDED projects only (charter +
462
+ * roadmap exist — ADR-0008); unfounded projects author them at /start-project.
463
+ * When the file exists, refresh the derived cache from the content source of
464
+ * truth and report any drift on the machine-inferable fields (never clobber
465
+ * user edits).
466
+ *
467
+ * specs/ is user content — the config file is NOT recorded in the managed
468
+ * manifest (orphan cleanup must never touch it). The cache is gitignored by the
469
+ * existing `.momentum/*` rule.
470
+ */
471
+ function installConfig(target, { dryRun, upgradeMode } = {}) {
472
+ const prefsLib = require('../core/config');
473
+ const specsDir = path.join(target, 'specs');
474
+ if (!fileExists(specsDir)) return; // no specs skeleton → nothing to do
475
+ const prefsPath = path.join(specsDir, 'config.md');
476
+ const exists = fileExists(prefsPath);
477
+
478
+ if (!exists) {
479
+ // upgrade only writes config for founded projects (migration).
480
+ if (upgradeMode && !prefsLib.isFounded(target)) return;
481
+ const prefs = prefsLib.inferConfig(target);
482
+ if (dryRun) {
483
+ console.log(` ✋ would add: specs/config.md (inferred: language=${prefs.language}, forge=${prefs.git_forge})`);
484
+ return;
485
+ }
486
+ prefsLib.writeConfig(specsDir, prefs, { inferred: true });
487
+ prefsLib.writeConfigCache(target, prefs);
488
+ console.log(` + added: specs/config.md (inferred: language=${prefs.language}, forge=${prefs.git_forge})`);
489
+ return;
490
+ }
491
+
492
+ // File exists: refresh the derived cache from the content source of truth.
493
+ // A present-but-garbage file parses to an all-defaults object; detect that
494
+ // (zero known keys present in the raw file) and LEAVE it untouched rather
495
+ // than silently overwriting the user's (broken) content with defaults.
496
+ const raw = (() => {
497
+ try {
498
+ return prefsLib.parseConfigMarkdown(fs.readFileSync(prefsPath, 'utf8'));
499
+ } catch {
500
+ return {};
501
+ }
502
+ })();
503
+ const hasAnyKey = prefsLib.KNOWN_KEYS.some((k) => k in raw);
504
+ if (!hasAnyKey) {
505
+ console.log(` ⚠️ specs/config.md exists but is empty/unparseable — left untouched (fix or delete it)`);
506
+ return;
507
+ }
508
+ const stored = prefsLib.readConfig(specsDir);
509
+ if (!dryRun) prefsLib.writeConfigCache(target, stored);
510
+
511
+ // Drift detection (upgrade only, founded only): report changed inferable
512
+ // fields and point at the approval-gated fix. Never clobber user edits —
513
+ // the user applies the change explicitly via `momentum config sync`.
514
+ if (upgradeMode && prefsLib.isFounded(target)) {
515
+ const inferred = prefsLib.inferConfig(target);
516
+ const drifted = prefsLib.INFERABLE_KEYS.filter((k) => !prefsLib.valuesEqual(inferred[k], stored[k]));
517
+ if (drifted.length) {
518
+ console.log(` ⚠️ specs/config.md drifted from manifests on: ${drifted.join(', ')} — run 'momentum config sync' to review + apply`);
519
+ }
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Ask the user a single question and resolve with their trimmed answer.
525
+ * Works whether stdin is a TTY or piped (tests pipe input). On EOF without an
526
+ * answer, resolves to the empty string so callers can treat it as "skip".
527
+ * @param {string} query
528
+ * @returns {Promise<string>}
529
+ */
530
+ function askQuestion(query) {
531
+ const readline = require('node:readline');
532
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
533
+ return new Promise((resolve) => {
534
+ let done = false;
535
+ const finish = (v) => {
536
+ if (done) return;
537
+ done = true;
538
+ rl.close();
539
+ resolve(v);
540
+ };
541
+ // EOF (piped stdin, no answer) resolves to '' so callers treat it as skip.
542
+ rl.on('close', () => finish(''));
543
+ rl.question(query, (answer) => finish((answer || '').trim()));
544
+ });
545
+ }
546
+
547
+ /**
548
+ * ENH-062 — proactive, approval-gated config drift sync. Re-infers the project
549
+ * shape and diffs it against specs/config.md. When drift is found, shows the
550
+ * per-field old→new diff and asks the user which fields to apply (or "all" /
551
+ * "skip"). Nothing is written without explicit approval.
552
+ * @param {string} targetDir
553
+ * @param {object} [opts] { dryRun }
554
+ */
555
+ async function syncConfig(targetDir, opts = {}) {
556
+ const prefsLib = require('../core/config');
557
+ const specsDir = path.join(targetDir, 'specs');
558
+ if (!fileExists(specsDir)) {
559
+ console.log(' ⚠️ no specs/ skeleton — run momentum init first');
560
+ return;
561
+ }
562
+ const { exists, drift } = prefsLib.diffConfig(specsDir, targetDir);
563
+
564
+ if (!exists) {
565
+ // Migration: a founded project with no config.md yet → create it.
566
+ if (prefsLib.isFounded(targetDir)) {
567
+ if (opts.dryRun) {
568
+ const p = prefsLib.inferConfig(targetDir);
569
+ console.log(` ✋ would add: specs/config.md (inferred: language=${p.language}, forge=${p.git_forge})`);
570
+ return;
571
+ }
572
+ const prefs = prefsLib.inferConfig(targetDir);
573
+ prefsLib.writeConfig(specsDir, prefs, { inferred: true });
574
+ prefsLib.writeConfigCache(targetDir, prefs);
575
+ console.log(` + added: specs/config.md (inferred: language=${prefs.language}, forge=${prefs.git_forge})`);
576
+ } else {
577
+ console.log(' ⚠️ project not founded — run /start-project to author specs/config.md');
578
+ }
579
+ return;
580
+ }
581
+
582
+ if (drift.length === 0) {
583
+ console.log(' ✓ specs/config.md matches the inferred project shape — nothing to sync');
584
+ return;
585
+ }
586
+
587
+ console.log('\n Project shape changed since specs/config.md was written:\n');
588
+ for (const d of drift) {
589
+ console.log(` • ${d.key}: ${JSON.stringify(d.old)} → ${JSON.stringify(d.new)}`);
590
+ }
591
+ console.log('');
592
+
593
+ if (opts.dryRun) {
594
+ console.log(` ✋ would sync ${drift.length} field(s) — re-run without --dry-run to apply`);
595
+ return;
596
+ }
597
+
598
+ const answer = await askQuestion(' Apply? [a]ll / comma-separated keys / [s]kip: ');
599
+ const a = answer.toLowerCase();
600
+ if (a === 's' || a === '' || a === 'skip') {
601
+ console.log(' ↩️ skipped — specs/config.md left unchanged');
602
+ return;
603
+ }
604
+ let keys;
605
+ if (a === 'a' || a === 'all' || a === 'y' || a === 'yes') {
606
+ keys = drift.map((d) => d.key);
607
+ } else {
608
+ const wanted = new Set(a.split(',').map((s) => s.trim()));
609
+ keys = drift.filter((d) => wanted.has(d.key)).map((d) => d.key);
610
+ if (keys.length === 0) {
611
+ console.log(' ↩️ no matching keys — specs/config.md left unchanged');
612
+ return;
613
+ }
614
+ }
615
+ const updated = prefsLib.mergeConfigDrift(specsDir, targetDir, keys);
616
+ console.log(` ✓ synced ${keys.join(', ')} → specs/config.md (cache refreshed)`);
617
+ return updated;
618
+ }
619
+
407
620
  function removeOrphans(targetDir, prevManifest, currentSet, opts = {}) {
408
621
  if (!prevManifest || !Array.isArray(prevManifest.managedFiles)) return [];
409
622
  const currentRel = new Set(
@@ -991,10 +1204,17 @@ function init(targetDir, agent, opts = {}) {
991
1204
  const specsSrc = path.join(src, 'core', 'specs-templates');
992
1205
  copyDir(specsSrc, target, {
993
1206
  skipIfExists: true,
994
- skipRelPaths: new Set(['CLAUDE.md']),
1207
+ // CLAUDE.md is adapter-owned (written separately); config.md is
1208
+ // inferred per-project by installConfig (Phase 26) — neither is
1209
+ // copied as a static template.
1210
+ skipRelPaths: new Set(['CLAUDE.md', 'config.md']),
995
1211
  record: false, // specs are install-once / user-owned — never orphan them
996
1212
  });
997
1213
 
1214
+ // Phase 26 — infer + write specs/config.md + the derived hook cache.
1215
+ console.log('→ Inferring project config...');
1216
+ installConfig(target, { dryRun: _dryRun });
1217
+
998
1218
  installPrimaryInstruction(src, target, adapterDir, adapter.primaryInstruction);
999
1219
 
1000
1220
  // Adapter overlay — per-agent commands/agent-rules/scripts (additive)
@@ -1023,13 +1243,13 @@ function init(targetDir, agent, opts = {}) {
1023
1243
  console.log('Next steps:');
1024
1244
  console.log('');
1025
1245
  const displayName = adapter.displayName || agent;
1026
- console.log(' Explore an idea first:');
1246
+ console.log(' Explore the idea first (writes nothing):');
1027
1247
  console.log(` Open ${displayName} and run: /brainstorm-idea`);
1028
1248
  console.log('');
1029
- console.log(' Ready to scaffold a project:');
1249
+ console.log(' Found the project author the charter + roadmap, plan Phase 0:');
1030
1250
  console.log(` Open ${displayName} and run: /start-project`);
1031
1251
  console.log('');
1032
- console.log(' Existing project — plan your next phase:');
1252
+ console.log(' Already founded — plan your next phase:');
1033
1253
  console.log(` Open ${displayName} and run: /brainstorm-phase`);
1034
1254
  console.log('');
1035
1255
  console.log(' See docs: https://github.com/LiminaLabsAI/momentum');
@@ -1068,7 +1288,7 @@ function upgrade(targetDir, agent, opts = {}) {
1068
1288
 
1069
1289
  _dryRun = !!opts.dryRun;
1070
1290
  _managedCollector = new Set();
1071
- let agentRulesResult, primaryInstructionResult, gitignoreResult, orphans = [];
1291
+ let agentRulesResult, primaryInstructionResult, gitignoreResult, foundationResult, orphans = [];
1072
1292
  try {
1073
1293
  // Upgrade slash commands
1074
1294
  console.log('→ Upgrading slash commands...');
@@ -1188,6 +1408,20 @@ function upgrade(targetDir, agent, opts = {}) {
1188
1408
  console.log('→ Refreshing .gitignore...');
1189
1409
  gitignoreResult = refreshGitignore(src, target);
1190
1410
 
1411
+ // Foundation docs — authored, not scaffolded (Phase 25 / ADR-0008).
1412
+ // Remove provably-untouched init placeholders; authored files stay.
1413
+ console.log('→ Checking foundation docs (authored, not scaffolded)...');
1414
+ foundationResult = migrateFoundationDocs(src, target);
1415
+ if (!foundationResult.removed.length && !foundationResult.wouldRemove.length) {
1416
+ console.log(' = no untouched placeholders (foundation docs are authored or absent)');
1417
+ }
1418
+
1419
+ // Phase 26 — project config (ADR-0009). Founded projects get an
1420
+ // inferred specs/config.md on first upgrade; existing files get their
1421
+ // derived cache refreshed + drift reported (never clobbered).
1422
+ console.log('→ Inferring project config...');
1423
+ installConfig(target, { dryRun: _dryRun, upgradeMode: true });
1424
+
1191
1425
  // Orphan cleanup — only remove files this agent's prior version installed
1192
1426
  // that this version no longer ships. Other agents' files are untouched.
1193
1427
  orphans = removeOrphans(target, { managedFiles: prevAgentFiles }, _managedCollector, {
@@ -1223,6 +1457,9 @@ function upgrade(targetDir, agent, opts = {}) {
1223
1457
  }
1224
1458
  console.log(` agent-rules: ${agentRulesResult}`);
1225
1459
  console.log(` .gitignore: ${gitignoreResult}`);
1460
+ if (foundationResult && foundationResult.removed.length) {
1461
+ console.log(` foundation docs: removed ${foundationResult.removed.length} untouched placeholder(s) — not yet founded; run /start-project`);
1462
+ }
1226
1463
  if (orphans.length) {
1227
1464
  console.log(` removed (orphaned): ${orphans.length} file(s) — see 🗑 lines above`);
1228
1465
  }
@@ -1413,6 +1650,7 @@ Waves — wave plan from dependency annotations (Phase 21c, one engine every sca
1413
1650
  OKF — specs/ as an Open Knowledge Format bundle (Phase 24, ADR-0005):
1414
1651
  momentum okf check [dir] OKF v0.1 conformance report for <dir>/specs
1415
1652
  momentum okf index [dir] Regenerate bundle indexes (root/phases/decisions)
1653
+ momentum config sync [target-dir] Re-infer project shape, show config drift, apply on approval
1416
1654
 
1417
1655
  Options:
1418
1656
  --agent <name> Agent to install for. \`init\` asks when this
@@ -1861,6 +2099,22 @@ async function main() {
1861
2099
  console.error(`\nError: ${err.message}`);
1862
2100
  exitCode = 1;
1863
2101
  }
2102
+ } else if (args[0] === 'config') {
2103
+ try {
2104
+ const sub = args[1];
2105
+ const target = args[2] ? path.resolve(args[2]) : process.cwd();
2106
+ if (sub === 'sync') {
2107
+ const dryRun = args.includes('--dry-run') || _dryRun;
2108
+ await syncConfig(target, { dryRun });
2109
+ } else {
2110
+ console.error(`Unknown config subcommand: ${sub || '(none)'}`);
2111
+ console.error('Usage: momentum config sync [target-dir] [--dry-run]');
2112
+ exitCode = 1;
2113
+ }
2114
+ } catch (err) {
2115
+ console.error(`\nError: ${err.message}`);
2116
+ exitCode = 1;
2117
+ }
1864
2118
  } else {
1865
2119
  console.error(`Unknown command: ${args[0]}`);
1866
2120
  console.error('Run "momentum --help" for usage.');
@@ -2,7 +2,7 @@ Explore any idea through structured dialogue before committing to anything.
2
2
 
3
3
  Use this to think through a concept, technical direction, product decision, or architecture question. **Nothing gets written to disk.** The output is a clear, structured summary you can act on.
4
4
 
5
- When you're ready to turn the result into a project, run `/start-project`. To turn a decision into an ADR, run `/log` with `--decision`.
5
+ When you're ready to turn the result into a project, run `/start-project` — it **founds** the project: authors the charter, principles, success criteria, and roadmap from this brainstorm and plans Phase 0. It works whether or not `momentum init` has already scaffolded the machinery (structure ≠ content — see `core/project-lifecycle.md`). To turn a decision into an ADR, run `/log` with `--decision`.
6
6
 
7
7
  ## When to use
8
8
 
@@ -44,13 +44,32 @@ When you're ready to turn the result into a project, run `/start-project`. To tu
44
44
 
45
45
  4. Iterate until the idea is clear.
46
46
 
47
+ 4b. **Config discovery** — once the idea is settled, gather the
48
+ project-shape config that `/start-project` will author into
49
+ `specs/config.md` (ADR-0009). Ask one question at a time; if the
50
+ user is unsure, suggest a default from the idea's context:
51
+ - **Git forge**: "Which forge? GitHub / GitLab / Bitbucket / Gitea /
52
+ Forgejo / bare-ssh?" (default from `git remote get-url origin` if a
53
+ repo exists, else GitHub)
54
+ - **Language + framework**: "What language/framework? Node+Next.js /
55
+ Python+FastAPI / Rust+Actix / Go / …?" (default from manifests if a
56
+ repo exists, else ask)
57
+ - **Publish/deploy target**: "How does this ship? npm publish / pypi /
58
+ crates.io / deploy-only (Vercel/Fly/…) / none?" (sets `publish_target`
59
+ + `release_flow`)
60
+ - **Branch flow**: "How do changes land? feature → staging → main /
61
+ feature → main / feature-branch-only?" (sets `branch_flow` +
62
+ `end_state`; default `feature → staging → main`)
63
+ Carry the settled config as context into `/start-project` so it
64
+ authors `specs/config.md` alongside the charter/roadmap in one batch.
65
+
47
66
  5. **Exit the brainstorm gate** before any follow-up command:
48
67
  ```bash
49
68
  rm .momentum/brainstorm-active
50
69
  ```
51
70
 
52
71
  6. Close with one of:
53
- - "Ready to build this? Run `/start-project` to scaffold it as a project."
72
+ - "Ready to build this? Run `/start-project` to found the project — it authors the foundation docs (charter, principles, success criteria, roadmap) from this brainstorm and plans Phase 0. An existing `momentum init` scaffold doesn't change this: init owns structure, founding owns content."
54
73
  - "Want to explore further before committing? Ask away."
55
74
  - "This is a decision, not a project — want to log it as an ADR with `/log --decision`?"
56
75
 
@@ -18,6 +18,14 @@ The brainstorm output IS the phase files — there is no intermediate design doc
18
18
  From this point until Step 8, do NOT call any write-class tool on any path under `specs/`. The PreToolUse hook (`core/scripts/brainstorm-gate.sh`, wired by Claude Code + Codex + Antigravity) will block such calls; this step makes the intent explicit.
19
19
 
20
20
  1. Review current state:
21
+ - **Founded gate first** (`core/project-lifecycle.md`, ADR-0008): verify
22
+ `specs/vision/project-charter.md` AND `specs/planning/roadmap.md` exist.
23
+ If either is missing, the project is NOT founded — STOP: remove the
24
+ sentinel (`rm .momentum/brainstorm-active`) and route to
25
+ `/start-project`, offering to draft the foundation docs with the user
26
+ from the context already in this conversation (best-effort content
27
+ marked for refinement beats absent docs). Re-run this command once
28
+ founded. Never plan a phase on an unfounded project.
21
29
  - Read `specs/status.md` — what phase just completed?
22
30
  - Read the completed phase's `history.md` — what was learned?
23
31
  - Read `specs/backlog/backlog.md` — any P0/P1 items to address first?
@@ -26,6 +34,13 @@ The brainstorm output IS the phase files — there is no intermediate design doc
26
34
  - Read `specs/planning/roadmap.md` — what's the next planned phase?
27
35
  - Is the planned phase still the right next step given what was learned?
28
36
 
37
+ 2b. Read verification defaults from config:
38
+ - Read `specs/config.md` (`core/config.js` → `readConfig('specs')`)
39
+ for `test_command` and `build_command`. When absent, default to
40
+ `npm test` / no build. Use these as the default verification commands in
41
+ the phase's deliverables table (overview.md) unless the phase needs
42
+ something different — call out any deviation explicitly.
43
+
29
44
  3. Define scope with the user (one question at a time):
30
45
  - What is the goal of this phase?
31
46
  - What are the key deliverables?
@@ -18,6 +18,11 @@ Verify, finalize, and release a completed phase.
18
18
  - If any `[ ]` unchecked items remain → report to user, do NOT proceed
19
19
 
20
20
  3. **Run project-specific validation and capture fresh evidence** (per Rule 12 — Verify Before Claim):
21
+ - Read `specs/config.md` (`core/config.js` → `readConfig('specs')`)
22
+ for `test_command` and `build_command`. When the file is absent or a
23
+ value is missing, fall back to `npm test` / no build (the historical
24
+ defaults). Run `test_command` always; run `build_command` when it is not
25
+ `none`.
21
26
  - Run all defined validation commands: tests, linting, type checks, build, smoke tests
22
27
  - Capture each command's stdout/stderr (e.g., redirect to a temp file with `tee`)
23
28
  - Note exit codes
@@ -67,49 +72,62 @@ Verify, finalize, and release a completed phase.
67
72
  git push origin phase-N-shortname
68
73
  ```
69
74
 
70
- 9. Ask the user ONCE for approval to release — present the full plan:
75
+ 9. Ask the user ONCE for approval to release — present the full plan, walking
76
+ the project's `branch_flow` (from `specs/config.md`; default
77
+ `[staging, main]`) in order. The plan stops at the universal git primitives
78
+ (`git merge` + `git tag -a` + `git push origin <tag>`); forge-specific
79
+ release creation and registry publishes are NOT listed here — they run
80
+ from `## Project Extensions` + `specs/config.md` (`release_command`,
81
+ `publish_target`, `release_flow`) after the tag is pushed.
71
82
  ```
72
83
  Ready to release vX.Y.Z. This will:
73
- 1. Merge phase-N-shortname → staging
74
- 2. Merge staging main
75
- 3. Tag vX.Y.Z and create GitHub Release
84
+ 1. Merge phase-N-shortname → <branch_flow[0]> (e.g. staging)
85
+ 2. …promote through <branch_flow[1..]> (e.g. main)
86
+ 3. Tag vX.Y.Z and push the tag
87
+
88
+ Project-specific release/publish/deploy (forge release, npm publish, …)
89
+ run from ## Project Extensions + specs/config.md after the tag lands.
76
90
 
77
91
  Proceed?
78
92
  ```
79
93
  Wait for a single "yes" before running any of the following steps.
80
94
 
81
- 10. On approval, execute all three steps in sequence.
95
+ 10. On approval, execute the merge sequence + tag in order.
82
96
 
83
97
  **Landing Order (Rule 6/15):** if another lane landed on `main` since
84
98
  this branch last rebased, rebase this branch onto updated `main` and
85
99
  re-run the suite BEFORE merging. Never land two lanes back-to-back
86
100
  without the suite passing in between.
87
101
 
102
+ Walk `branch_flow` (default `[staging, main]`) hop by hop — one merge per
103
+ protected branch in order, approval covered by the single "yes" above:
88
104
  ```bash
89
- # step 1 — phase branch → staging
90
- git checkout staging && git pull origin staging
91
- git merge --no-ff phase-N-shortname -m "merge: phase-N-shortname → staging (vX.Y.Z)"
92
- git push origin staging
93
-
94
- # step 2 — staging main
95
- git checkout main && git pull origin main
96
- git merge --no-ff staging -m "merge: staging → main (vX.Y.Z — Phase N: {phase name})"
97
- git push origin main
98
-
99
- # step 3 — tag + GitHub Release
105
+ # step 1 — phase branch → branch_flow[0]
106
+ git checkout <branch_flow[0]> && git pull origin <branch_flow[0]>
107
+ git merge --no-ff phase-N-shortname -m "merge: phase-N-shortname → <branch_flow[0]> (vX.Y.Z)"
108
+ git push origin <branch_flow[0]>
109
+
110
+ # step 2..promote through each remaining branch_flow entry
111
+ # (e.g. staging main)
112
+ git checkout <branch_flow[1]> && git pull origin <branch_flow[1]>
113
+ git merge --no-ff <branch_flow[0]> -m "merge: <branch_flow[0]> → <branch_flow[1]> (vX.Y.Z — Phase N: {phase name})"
114
+ git push origin <branch_flow[1]>
115
+
116
+ # final step — tag + push the tag (universal git; forge release + publish
117
+ # are project-specific — run them from ## Project Extensions now)
100
118
  git tag -a vX.Y.Z -m "Phase N: {phase name}"
101
119
  git push origin vX.Y.Z
102
- gh release create vX.Y.Z \
103
- --title "vX.Y.Z — Phase N: {phase name}" \
104
- --notes "## Phase N: {phase name}
105
- {bullet summary of what was delivered}
106
- ### Next: Phase N+1 — {next phase name}" \
107
- --target main
108
120
  ```
121
+ For `end_state: feature-branch-only` (config), SKIP the merge
122
+ sequence — the branch is already pushed; only tag (if the project tags
123
+ releases on the feature branch) per `## Project Extensions`. For
124
+ `end_state: staging-promotion`, merge to `branch_flow[0]` only and stop;
125
+ tag + release happen after the user promotes to `main`.
109
126
 
110
127
  12. Report summary to user:
111
128
  - What was delivered
112
- - GitHub Release URL
129
+ - Tag URL (`git push origin vX.Y.Z` output) — forge release/publish
130
+ status from `## Project Extensions` if any ran
113
131
  - What's next
114
132
 
115
133
  13. Delete the merged phase branch (ENH-042 — now that merge → main + release
@@ -22,6 +22,12 @@ This command fills gaps without overwriting anything the project already has.
22
22
  `brainstorm-idea`, `brainstorm-phase`, `start-project`, `start-phase`, `complete-phase`,
23
23
  `log`, `sync-docs`, `track`, `migrate`, `validate`
24
24
 
25
+ **Foundation docs are NOT migration gaps** (`core/project-lifecycle.md`,
26
+ ADR-0008): `specs/vision/*` and `specs/planning/roadmap.md` are authored
27
+ via `/start-project`, never copied from templates. If they're missing,
28
+ report "not founded — run `/start-project`" in the result; do NOT create
29
+ them here.
30
+
25
31
  2. **Report gap summary** — present findings before making any changes:
26
32
  ```
27
33
  Found: 8 / Missing: 4 items
@@ -64,6 +70,7 @@ This command fills gaps without overwriting anything the project already has.
64
70
  ✓ Migration complete.
65
71
  Added: 4 items
66
72
  Skipped: 6 items (already existed — not overwritten)
73
+ Founded: no — charter/roadmap missing; run /start-project to author them
67
74
  Needs manual attention:
68
75
  - 3 phases marked status 'unknown' in overview.md frontmatter — verify manually
69
76
  - specs/status.md: already exists — verify Current Phase matches the phase frontmatter