@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.
- package/README.md +7 -0
- package/adapters/antigravity/instructions/AGENTS.md +6 -1
- package/adapters/codex/instructions/AGENTS.md +7 -1
- package/adapters/codex/instructions/surfaces.md +1 -0
- package/adapters/opencode/instructions/AGENTS.md +7 -1
- package/adapters/opencode/instructions/surfaces.md +1 -0
- package/bin/momentum.js +259 -5
- package/core/commands/brainstorm-idea.md +21 -2
- package/core/commands/brainstorm-phase.md +15 -0
- package/core/commands/complete-phase.md +41 -23
- package/core/commands/migrate.md +7 -0
- package/core/commands/start-phase.md +33 -3
- package/core/commands/start-project.md +234 -65
- package/core/commands/sync-config.md +43 -0
- package/core/commands/validate.md +24 -0
- package/core/config-templates.js +77 -0
- package/core/config.js +520 -0
- package/core/foundation-placeholder-hashes.json +18 -0
- package/core/git-hooks/contract.js +98 -0
- package/core/git-hooks/run-check.js +22 -1
- package/core/instructions/navigation.md +2 -1
- package/core/instructions/rules-body.md +4 -0
- package/core/lib/okf-types.js +1 -0
- package/core/project-lifecycle.md +68 -0
- package/core/specs-templates/CLAUDE.md +6 -1
- package/core/specs-templates/specs/README.md +8 -3
- package/core/specs-templates/specs/config.md +31 -0
- package/core/specs-templates/specs/index.md +0 -1
- package/core/specs-templates/specs/status.md +12 -5
- package/package.json +1 -1
- package/core/specs-templates/specs/planning/roadmap.md +0 -23
- package/core/specs-templates/specs/vision/principles.md +0 -16
- package/core/specs-templates/specs/vision/project-charter.md +0 -35
- 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
|
-
|
|
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
|
|
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('
|
|
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('
|
|
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
|
|
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
|
|
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.
|
|
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
|
package/core/commands/migrate.md
CHANGED
|
@@ -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
|