@limina-labs/momentum 0.32.0 → 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.
- package/README.md +1 -0
- package/adapters/antigravity/instructions/AGENTS.md +1 -0
- package/adapters/codex/instructions/AGENTS.md +2 -0
- package/adapters/codex/instructions/surfaces.md +1 -0
- package/adapters/opencode/instructions/AGENTS.md +2 -0
- package/adapters/opencode/instructions/surfaces.md +1 -0
- package/bin/momentum.js +197 -1
- package/core/commands/brainstorm-idea.md +19 -0
- package/core/commands/brainstorm-phase.md +7 -0
- package/core/commands/complete-phase.md +41 -23
- package/core/commands/start-phase.md +27 -3
- package/core/commands/start-project.md +49 -3
- package/core/commands/sync-config.md +43 -0
- package/core/commands/validate.md +14 -0
- package/core/config-templates.js +77 -0
- package/core/config.js +520 -0
- package/core/git-hooks/contract.js +98 -0
- package/core/git-hooks/run-check.js +22 -1
- package/core/instructions/navigation.md +1 -0
- package/core/lib/okf-types.js +1 -0
- package/core/specs-templates/CLAUDE.md +1 -0
- package/core/specs-templates/specs/config.md +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -182,6 +182,7 @@ and is idempotent. (ADR-0005.)
|
|
|
182
182
|
| 🧭 **Phases** | Plan → execute → verify → release. Every phase has a brainstorm, plan, tasks, history. |
|
|
183
183
|
| 📋 **Backlog** | Bugs / features / tech debt / enhancements with priorities and per-item context. |
|
|
184
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. |
|
|
185
186
|
| ⚖️ **Rules** | 13 autonomous agent rules — orient first, verify before claim, log every decision. |
|
|
186
187
|
| 🛠️ **Skills** | ~15 slash commands your agent runs — start, complete, sync, review, validate. |
|
|
187
188
|
| 🌐 **Multi-project** | Ecosystem mode (state layer) + Orchestration primitives (action layer) for cross-project work. |
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
|
|
16
16
|
| Why was X chosen? | `specs/decisions/NNNN-*.md` |
|
|
17
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`.**
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
|
|
16
16
|
| Why was X chosen? | `specs/decisions/NNNN-*.md` |
|
|
17
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 |
|
|
@@ -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 |
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| Phase tasks/progress? | `specs/phases/phase-N-*/tasks.md` |
|
|
16
16
|
| Why was X chosen? | `specs/decisions/NNNN-*.md` |
|
|
17
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 |
|
|
@@ -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
|
@@ -451,6 +451,172 @@ function migrateFoundationDocs(srcRoot, target) {
|
|
|
451
451
|
return result;
|
|
452
452
|
}
|
|
453
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
|
+
|
|
454
620
|
function removeOrphans(targetDir, prevManifest, currentSet, opts = {}) {
|
|
455
621
|
if (!prevManifest || !Array.isArray(prevManifest.managedFiles)) return [];
|
|
456
622
|
const currentRel = new Set(
|
|
@@ -1038,10 +1204,17 @@ function init(targetDir, agent, opts = {}) {
|
|
|
1038
1204
|
const specsSrc = path.join(src, 'core', 'specs-templates');
|
|
1039
1205
|
copyDir(specsSrc, target, {
|
|
1040
1206
|
skipIfExists: true,
|
|
1041
|
-
|
|
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']),
|
|
1042
1211
|
record: false, // specs are install-once / user-owned — never orphan them
|
|
1043
1212
|
});
|
|
1044
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
|
+
|
|
1045
1218
|
installPrimaryInstruction(src, target, adapterDir, adapter.primaryInstruction);
|
|
1046
1219
|
|
|
1047
1220
|
// Adapter overlay — per-agent commands/agent-rules/scripts (additive)
|
|
@@ -1243,6 +1416,12 @@ function upgrade(targetDir, agent, opts = {}) {
|
|
|
1243
1416
|
console.log(' = no untouched placeholders (foundation docs are authored or absent)');
|
|
1244
1417
|
}
|
|
1245
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
|
+
|
|
1246
1425
|
// Orphan cleanup — only remove files this agent's prior version installed
|
|
1247
1426
|
// that this version no longer ships. Other agents' files are untouched.
|
|
1248
1427
|
orphans = removeOrphans(target, { managedFiles: prevAgentFiles }, _managedCollector, {
|
|
@@ -1471,6 +1650,7 @@ Waves — wave plan from dependency annotations (Phase 21c, one engine every sca
|
|
|
1471
1650
|
OKF — specs/ as an Open Knowledge Format bundle (Phase 24, ADR-0005):
|
|
1472
1651
|
momentum okf check [dir] OKF v0.1 conformance report for <dir>/specs
|
|
1473
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
|
|
1474
1654
|
|
|
1475
1655
|
Options:
|
|
1476
1656
|
--agent <name> Agent to install for. \`init\` asks when this
|
|
@@ -1919,6 +2099,22 @@ async function main() {
|
|
|
1919
2099
|
console.error(`\nError: ${err.message}`);
|
|
1920
2100
|
exitCode = 1;
|
|
1921
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
|
+
}
|
|
1922
2118
|
} else {
|
|
1923
2119
|
console.error(`Unknown command: ${args[0]}`);
|
|
1924
2120
|
console.error('Run "momentum --help" for usage.');
|
|
@@ -44,6 +44,25 @@ When you're ready to turn the result into a project, run `/start-project` — it
|
|
|
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
|
|
@@ -34,6 +34,13 @@ The brainstorm output IS the phase files — there is no intermediate design doc
|
|
|
34
34
|
- Read `specs/planning/roadmap.md` — what's the next planned phase?
|
|
35
35
|
- Is the planned phase still the right next step given what was learned?
|
|
36
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
|
+
|
|
37
44
|
3. Define scope with the user (one question at a time):
|
|
38
45
|
- What is the goal of this phase?
|
|
39
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.
|
|
75
|
-
3. Tag vX.Y.Z and
|
|
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
|
|
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 →
|
|
90
|
-
git checkout
|
|
91
|
-
git merge --no-ff phase-N-shortname -m "merge: phase-N-shortname →
|
|
92
|
-
git push origin
|
|
93
|
-
|
|
94
|
-
# step 2 —
|
|
95
|
-
|
|
96
|
-
git
|
|
97
|
-
git
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
-
|
|
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
|
|
@@ -119,9 +119,33 @@ Once a phase plan is approved, this command executes the plan end-to-end without
|
|
|
119
119
|
|
|
120
120
|
### Hard stop — always
|
|
121
121
|
|
|
122
|
-
**Merge to
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
**Merge to a protected branch + release.** After the final group's
|
|
123
|
+
verification passes, STOP. Read the project's config
|
|
124
|
+
(`specs/config.md` via `core/config.js` →
|
|
125
|
+
`readConfig('<repo>/specs')`; returns `null` when absent → use the
|
|
126
|
+
defaults below) and ask the user the gate question matching the project's
|
|
127
|
+
`end_state`:
|
|
128
|
+
|
|
129
|
+
- **`merge-after-yes`** (default): "All groups complete and verified. Ready
|
|
130
|
+
to merge `<phase-branch>` → `<branch_flow in order>` (e.g. `staging`, then
|
|
131
|
+
`main`), tag `v<version>`. Approve to proceed?"
|
|
132
|
+
- **`staging-promotion`**: "All groups complete and verified. Ready to merge
|
|
133
|
+
`<phase-branch>` → `staging`. Tag `v<version>` + release after you promote
|
|
134
|
+
to `main`. Approve to proceed?"
|
|
135
|
+
- **`feature-branch-only`**: "All groups complete and verified. The feature
|
|
136
|
+
branch `<phase-branch>` is pushed. Merge, tag, and release are yours to
|
|
137
|
+
do. Review the branch?"
|
|
138
|
+
|
|
139
|
+
Then append: "Project-specific release/publish/deploy steps (e.g.
|
|
140
|
+
`npm publish`, forge release creation) run from `## Project Extensions` in
|
|
141
|
+
CLAUDE.md/AGENTS.md — consult them now if any apply."
|
|
142
|
+
|
|
143
|
+
The gate stops at the truly universal git primitives — `git merge` +
|
|
144
|
+
`git tag -a` + `git push origin <tag>`. Forge-specific release creation
|
|
145
|
+
(`gh release create`, `glab release create`, …) and registry publishes
|
|
146
|
+
(`npm publish`, `twine upload`, …) are NOT in this template; they live in
|
|
147
|
+
`## Project Extensions` + `specs/config.md` (`release_command`,
|
|
148
|
+
`publish_target`, `release_flow`).
|
|
125
149
|
|
|
126
150
|
This is the only place the engine asks. Do NOT skip it.
|
|
127
151
|
|
|
@@ -51,14 +51,20 @@ If you're still exploring the idea, run `/brainstorm-idea` first.
|
|
|
51
51
|
- `status.md` Summary paragraph (what this project is)
|
|
52
52
|
- For monorepo: first-pass architecture sketch (core abstractions, interfaces)
|
|
53
53
|
- Phase 0 contents using the [Group Execution Pattern](#group-execution-pattern)
|
|
54
|
+
- **Config** (`specs/config.md`, ADR-0009): the project-shape
|
|
55
|
+
settings recipe templates read at execution time (forge, publish target,
|
|
56
|
+
branch flow, verification commands — see the [Config Format](#config-format)).
|
|
57
|
+
Carry the values settled during `/brainstorm-idea`; when `momentum init`
|
|
58
|
+
already inferred them, confirm the inferred file. `protected_branches`
|
|
59
|
+
is derived from `branch_flow`.
|
|
54
60
|
- Use the [Foundation Doc Formats](#foundation-doc-formats) below; author
|
|
55
61
|
real content from the brainstorm — never emit `_(TBD)_` placeholders.
|
|
56
62
|
Where understanding is genuinely thin, write the best-effort version,
|
|
57
63
|
mark the specific line `<!-- refine: ... -->`, and ask the user.
|
|
58
64
|
|
|
59
65
|
4. Present the founding draft for approval:
|
|
60
|
-
- Show the charter, roadmap, and key Phase 0 sections in chat
|
|
61
|
-
- List every file that will be created
|
|
66
|
+
- Show the charter, roadmap, config table, and key Phase 0 sections in chat
|
|
67
|
+
- List every file that will be created (including `specs/config.md`)
|
|
62
68
|
- Ask: "Ready to found the project? This will write N files. Approve to proceed."
|
|
63
69
|
|
|
64
70
|
5. **On approval — exit the gate**:
|
|
@@ -71,12 +77,17 @@ If you're still exploring the idea, run `/brainstorm-idea` first.
|
|
|
71
77
|
- `specs/vision/principles.md`
|
|
72
78
|
- `specs/vision/success-criteria.md`
|
|
73
79
|
- `specs/planning/roadmap.md`
|
|
80
|
+
- `specs/config.md` (from the config table above; overwrite the
|
|
81
|
+
`momentum init`-inferred file if present — founding owns the content)
|
|
74
82
|
- Update `specs/status.md`: Summary, Current Phase (Phase 0 `not started`),
|
|
75
83
|
Upcoming Phases, Next Actions
|
|
76
84
|
- For monorepo: `specs/architecture/` first-pass doc(s)
|
|
77
85
|
- `specs/phases/phase-0-shortname/{overview,plan,tasks,history}.md`
|
|
78
86
|
(log the founding decisions from this conversation in `history.md`)
|
|
79
|
-
- Refresh
|
|
87
|
+
- Refresh the derived cache: `core/config.js` →
|
|
88
|
+
`writeConfigCache('.', readConfig('specs'))` so the pre-push
|
|
89
|
+
hook's `protected_branches` matches the authored `branch_flow`;
|
|
90
|
+
then `momentum okf index` to refresh bundle listings
|
|
80
91
|
|
|
81
92
|
7. Commit the founding:
|
|
82
93
|
```bash
|
|
@@ -202,6 +213,41 @@ type: Roadmap
|
|
|
202
213
|
|
|
203
214
|
---
|
|
204
215
|
|
|
216
|
+
## Config Format
|
|
217
|
+
|
|
218
|
+
Author `specs/config.md` in this shape (`type: Config` → OKF bundle).
|
|
219
|
+
Lists (`branch_flow`, `protected_branches`) are comma-separated;
|
|
220
|
+
`protected_branches` is derived from `branch_flow`. The trust layer (human
|
|
221
|
+
authorization for protected-branch pushes) is invariant, NOT a preference
|
|
222
|
+
(ADR-0009); bypass only via `MOMENTUM_SKIP_HOOKS=1`.
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
---
|
|
226
|
+
type: Config
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
# Project Config
|
|
230
|
+
|
|
231
|
+
> Recipes read these at execution time; missing values fall back to npm/GitHub
|
|
232
|
+
> defaults. Edit freely.
|
|
233
|
+
|
|
234
|
+
| Key | Value |
|
|
235
|
+
|-----|-------|
|
|
236
|
+
| language | node |
|
|
237
|
+
| framework | nextjs |
|
|
238
|
+
| test_command | npm test |
|
|
239
|
+
| build_command | npm run build |
|
|
240
|
+
| publish_target | npm |
|
|
241
|
+
| git_forge | github |
|
|
242
|
+
| release_command | gh release create |
|
|
243
|
+
| release_flow | tag-and-publish |
|
|
244
|
+
| end_state | merge-after-yes |
|
|
245
|
+
| branch_flow | staging, main |
|
|
246
|
+
| protected_branches | staging, main |
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
205
251
|
## Brainstorm Gate Contract
|
|
206
252
|
|
|
207
253
|
This command runs in two phases: **brainstorm** (conversational, no disk writes) and **commit** (writes files to disk on explicit approval).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Sync the project's `specs/config.md` with the project's current shape — review drift and apply on approval.
|
|
2
|
+
|
|
3
|
+
Momentum infers project shape (language, framework, publish target, git forge)
|
|
4
|
+
from manifests (`package.json` / `pyproject.toml` / `Cargo.toml` / `go.mod` / …) and
|
|
5
|
+
the git remote. When that shape changes — you switched CI from GitHub Actions to
|
|
6
|
+
GitLab, adopted a framework, renamed the package — `specs/config.md` can drift
|
|
7
|
+
from reality. This command re-infers and shows the difference, then asks before
|
|
8
|
+
writing anything (ENH-062 — approval-gated, never a silent mutation).
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
1. Run:
|
|
13
|
+
```
|
|
14
|
+
momentum config sync
|
|
15
|
+
```
|
|
16
|
+
(pass a `target-dir` to sync a different project; `--dry-run` to preview.)
|
|
17
|
+
|
|
18
|
+
2. Read the output:
|
|
19
|
+
- `✓ matches the inferred project shape — nothing to sync` → no drift, done.
|
|
20
|
+
- A per-field `old → new` list → drift detected.
|
|
21
|
+
|
|
22
|
+
3. At the prompt, decide:
|
|
23
|
+
- `a` / `all` → apply every drifted field.
|
|
24
|
+
- `language,git_forge` (comma-separated keys) → apply only those.
|
|
25
|
+
- `s` / `skip` / empty → leave `specs/config.md` unchanged.
|
|
26
|
+
|
|
27
|
+
4. On approval, momentum rewrites `specs/config.md` and refreshes the derived
|
|
28
|
+
`.momentum/config-cache.json`. The **trust layer is untouched** — protected
|
|
29
|
+
branches can only ever be *added*, never removed (ADR-0009). Only the
|
|
30
|
+
inferable fields (`language`, `framework`, `publish_target`, `git_forge`) are
|
|
31
|
+
ever offered; user-authored fields (`branch_flow`, `end_state`, commands) are
|
|
32
|
+
never flagged or overwritten.
|
|
33
|
+
|
|
34
|
+
5. Report to the user what changed (or that nothing changed). If drift was
|
|
35
|
+
found and skipped, note they can re-run `momentum config sync` any time.
|
|
36
|
+
|
|
37
|
+
## When to run this proactively
|
|
38
|
+
|
|
39
|
+
- After a visible project-shape change (new framework, forge move, language
|
|
40
|
+
bump) — offer to run `/sync-config` rather than silently editing config.
|
|
41
|
+
- `momentum upgrade` prints a drift warning when it detects one; route the user
|
|
42
|
+
to this command to resolve it.
|
|
43
|
+
- Never auto-edit `specs/config.md` outside this approval flow.
|
|
@@ -31,6 +31,20 @@ Run with no arguments for a fast index-first check, or pass `--deep` for a full
|
|
|
31
31
|
report informationally ("not founded yet — `/start-project` when
|
|
32
32
|
ready"), never as a failure
|
|
33
33
|
|
|
34
|
+
2c. Config (ADR-0009) — WARNING, never a failure (recipes fall back to
|
|
35
|
+
npm/GitHub defaults when absent):
|
|
36
|
+
- founded AND no `specs/config.md` → WARNING: "config not set
|
|
37
|
+
— recipes will use npm/GitHub defaults; run `momentum upgrade` to
|
|
38
|
+
infer them, or author at `/start-project`"
|
|
39
|
+
- `specs/config.md` exists → drift check: compare the
|
|
40
|
+
machine-inferable fields (`language`, `framework`, `publish_target`,
|
|
41
|
+
`git_forge`) against the manifests + `git remote get-url origin`
|
|
42
|
+
(`core/config.js` → `inferConfig('.')` vs
|
|
43
|
+
`readConfig('specs')`); on mismatch → WARNING: "config
|
|
44
|
+
drifted from manifests on: <fields> — edit by hand or delete the file
|
|
45
|
+
+ re-run `momentum upgrade`"
|
|
46
|
+
- Not founded → skip (config author at founding)
|
|
47
|
+
|
|
34
48
|
3. For each phase directory under `specs/phases/` (OKF bundle, ADR-0005):
|
|
35
49
|
- Verify all 4 files present: `overview.md`, `plan.md`, `tasks.md`, `history.md`
|
|
36
50
|
- Verify `overview.md` frontmatter carries `type: Phase` and a `status:`
|