@latysh-development/monolith 0.1.0-alpha.3 → 0.1.0-alpha.4
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 -1
- package/dist/brief/cli.d.ts.map +1 -1
- package/dist/brief/cli.js +8 -2
- package/dist/brief/collect.d.ts.map +1 -1
- package/dist/brief/collect.js +11 -3
- package/dist/brief/freshness.d.ts +17 -2
- package/dist/brief/freshness.d.ts.map +1 -1
- package/dist/brief/freshness.js +22 -4
- package/dist/brief/git-signals.d.ts +32 -5
- package/dist/brief/git-signals.d.ts.map +1 -1
- package/dist/brief/git-signals.js +169 -39
- package/dist/brief/knowledge.d.ts +62 -0
- package/dist/brief/knowledge.d.ts.map +1 -0
- package/dist/brief/knowledge.js +123 -0
- package/dist/brief/reconcile.d.ts +7 -1
- package/dist/brief/reconcile.d.ts.map +1 -1
- package/dist/brief/reconcile.js +42 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/style.d.ts +26 -0
- package/dist/cli/style.d.ts.map +1 -0
- package/dist/cli/style.js +76 -0
- package/dist/cli/ui.d.ts.map +1 -1
- package/dist/cli/ui.js +33 -6
- package/dist/coordinatorium/cli.d.ts.map +1 -1
- package/dist/coordinatorium/cli.js +13 -0
- package/dist/finalize/cli.d.ts.map +1 -1
- package/dist/finalize/cli.js +30 -13
- package/dist/finalize/consistency.d.ts +51 -0
- package/dist/finalize/consistency.d.ts.map +1 -0
- package/dist/finalize/consistency.js +57 -0
- package/dist/finalize/core.d.ts +26 -0
- package/dist/finalize/core.d.ts.map +1 -1
- package/dist/finalize/core.js +140 -1
- package/dist/ideas/cli.d.ts +1 -0
- package/dist/ideas/cli.d.ts.map +1 -1
- package/dist/ideas/cli.js +14 -3
- package/dist/ideas/format.d.ts +31 -1
- package/dist/ideas/format.d.ts.map +1 -1
- package/dist/ideas/format.js +109 -1
- package/dist/ideas/store.d.ts +2 -0
- package/dist/ideas/store.d.ts.map +1 -1
- package/dist/ideas/store.js +4 -1
- package/dist/ideas/types.d.ts +10 -0
- package/dist/ideas/types.d.ts.map +1 -1
- package/dist/ideas/types.js +3 -0
- package/dist/readmodel/dashboard.d.ts.map +1 -1
- package/dist/readmodel/dashboard.js +19 -1
- package/dist/readmodel/index.d.ts +2 -1
- package/dist/readmodel/index.d.ts.map +1 -1
- package/dist/readmodel/index.js +1 -0
- package/dist/readmodel/roadmap.d.ts +34 -0
- package/dist/readmodel/roadmap.d.ts.map +1 -0
- package/dist/readmodel/roadmap.js +254 -0
- package/dist/readmodel/types.d.ts +88 -0
- package/dist/readmodel/types.d.ts.map +1 -1
- package/dist/root/safe-write.d.ts +15 -4
- package/dist/root/safe-write.d.ts.map +1 -1
- package/dist/root/safe-write.js +264 -50
- package/dist/ui/assets/index-BJKxUDPD.css +1 -0
- package/dist/ui/assets/index-ioC9DPHQ.js +28 -0
- package/dist/ui/index.html +2 -2
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +4 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -2
- package/dist/ui/assets/index-5zXAgig8.css +0 -1
- package/dist/ui/assets/index-CQgoMYLM.js +0 -28
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent knowledge attribution (KA-001).
|
|
3
|
+
*
|
|
4
|
+
* A project may keep knowledge that belongs to no work item — a strategy
|
|
5
|
+
* paper, a vision note — without that being "unattributed activity" and
|
|
6
|
+
* without pretending it is part of the active story. The registration is an
|
|
7
|
+
* idea of the Idea Store (`IDEA-NNN`) that declares the artifact's `paths`.
|
|
8
|
+
*
|
|
9
|
+
* A commit is attributed to independent knowledge only when ALL hold:
|
|
10
|
+
* 1. its subject names no work item (a work item keeps its own semantics);
|
|
11
|
+
* 2. it names at least one `IDEA-NNN` that is a valid idea file **in the
|
|
12
|
+
* tree of that very commit** (so registering an idea later never
|
|
13
|
+
* legalises older commits, and a commit that registers or edits the idea
|
|
14
|
+
* is itself never attributed — its own files are not covered);
|
|
15
|
+
* 3. the ideas it names declare `paths`, every declared path passes the
|
|
16
|
+
* attribution policy (exact document files only — no directory prefixes, no
|
|
17
|
+
* source code, nothing inside the MONOLITH root; `attributionPathProblem`),
|
|
18
|
+
* and EVERY file the commit changed (other than state documents) is covered
|
|
19
|
+
* by those paths;
|
|
20
|
+
* 4. no changed file lies inside the MONOLITH root;
|
|
21
|
+
* 5. it names at most MAX_IDEA_IDS_PER_COMMIT ideas, and the run stays inside a
|
|
22
|
+
* fixed budget of blob reads (each read is a git subprocess).
|
|
23
|
+
* Attribution only EXPLAINS a commit. It is not approval: a registered, correctly
|
|
24
|
+
* attributed change may still be wrong or unreviewed.
|
|
25
|
+
* Anything else stays exactly what it was: unattributed activity. Nothing here
|
|
26
|
+
* activates a story, touches the roadmap or marks anything done; it only
|
|
27
|
+
* decides whether a commit is *explained*. No LLM, no network, read-only git.
|
|
28
|
+
*/
|
|
29
|
+
import { artifactPathsCover, attributionPathProblem, isIdeaId, parseIdea } from '../ideas/format.js';
|
|
30
|
+
export const EMPTY_KNOWLEDGE = { attributed: new Map(), rejected: new Map() };
|
|
31
|
+
/** Upper bound of commits examined; beyond it the rest simply stay unattributed. */
|
|
32
|
+
export const MAX_KNOWLEDGE_COMMITS = 200;
|
|
33
|
+
/** A subject naming more ideas than this is refused without any git call (A4-SEC-05). */
|
|
34
|
+
export const MAX_IDEA_IDS_PER_COMMIT = 8;
|
|
35
|
+
/** Total blob reads (git subprocesses) per run; beyond it commits stay unattributed. */
|
|
36
|
+
export const MAX_KNOWLEDGE_BLOB_READS = 64;
|
|
37
|
+
const IDEA_REF_RE = /(?<![A-Za-z0-9-])IDEA-\d{3,9}(?![A-Za-z0-9]|\.\d|-[A-Z0-9])/g;
|
|
38
|
+
export function ideaIdsInSubject(subject) {
|
|
39
|
+
return [...new Set(subject.match(IDEA_REF_RE) ?? [])].filter(isIdeaId);
|
|
40
|
+
}
|
|
41
|
+
export function attributeKnowledge(git, opts) {
|
|
42
|
+
if (git.status !== 'ok')
|
|
43
|
+
return EMPTY_KNOWLEDGE;
|
|
44
|
+
const attributed = new Map();
|
|
45
|
+
const rejected = new Map();
|
|
46
|
+
const root = opts.monolithRoot.replace(/^(\.\/)+/, '').replace(/\/+$/, '');
|
|
47
|
+
const rootPrefix = root === '' || root === '.' ? '' : `${root}/`;
|
|
48
|
+
let examined = 0;
|
|
49
|
+
const budget = { reads: MAX_KNOWLEDGE_BLOB_READS };
|
|
50
|
+
for (const c of git.commitsSince) {
|
|
51
|
+
const files = c.files.filter((f) => !opts.stateFiles.has(f));
|
|
52
|
+
if (files.length === 0)
|
|
53
|
+
continue; // not activity
|
|
54
|
+
if (opts.hasWorkItem(c.subject))
|
|
55
|
+
continue; // work items keep their own semantics
|
|
56
|
+
const named = ideaIdsInSubject(c.subject);
|
|
57
|
+
if (named.length === 0)
|
|
58
|
+
continue;
|
|
59
|
+
if (++examined > MAX_KNOWLEDGE_COMMITS)
|
|
60
|
+
break;
|
|
61
|
+
if (named.length > MAX_IDEA_IDS_PER_COMMIT) {
|
|
62
|
+
rejected.set(c.sha, { reason: `it names ${named.length} ideas (more than ${MAX_IDEA_IDS_PER_COMMIT}); no registration was read` });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const verdict = judge(c, files, named, rootPrefix, root, opts.readBlob, budget);
|
|
66
|
+
if ('ideaIds' in verdict)
|
|
67
|
+
attributed.set(c.sha, verdict);
|
|
68
|
+
else
|
|
69
|
+
rejected.set(c.sha, verdict);
|
|
70
|
+
}
|
|
71
|
+
return { attributed, rejected };
|
|
72
|
+
}
|
|
73
|
+
function judge(c, files, named, rootPrefix, monolithRoot, readBlob, budget) {
|
|
74
|
+
const unjudgeable = files.find((f) => /^\.?\.?\//.test(f));
|
|
75
|
+
if (unjudgeable !== undefined)
|
|
76
|
+
return { reason: `a changed path (${unjudgeable.slice(0, 40)}) is not project-relative and cannot be judged` };
|
|
77
|
+
const inRoot = (rootPrefix === '' ? files[0] : files.find((f) => f.toLowerCase().startsWith(rootPrefix.toLowerCase())));
|
|
78
|
+
if (inRoot !== undefined)
|
|
79
|
+
return { reason: `it changes ${inRoot}, inside the MONOLITH root; knowledge attribution never covers that` };
|
|
80
|
+
const valid = [];
|
|
81
|
+
const paths = [];
|
|
82
|
+
const problems = [];
|
|
83
|
+
for (const id of named) {
|
|
84
|
+
if (budget.reads <= 0) {
|
|
85
|
+
problems.push(`${id} was not checked: the per-run limit of ${MAX_KNOWLEDGE_BLOB_READS} registration reads was reached`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
budget.reads--;
|
|
89
|
+
const text = readBlob(c.sha, `${monolithRoot}/memory/long-term/ideas/${id}.md`);
|
|
90
|
+
if (text === null) {
|
|
91
|
+
problems.push(`${id} is not a registered idea in this commit (or its content is not available locally)`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const idea = parseIdea(id, text);
|
|
96
|
+
if (idea.paths === undefined || idea.paths.length === 0)
|
|
97
|
+
problems.push(`${id} declares no paths`);
|
|
98
|
+
else {
|
|
99
|
+
const refused = idea.paths.map((p) => ({ p, why: attributionPathProblem(p, monolithRoot) })).find((x) => x.why !== null);
|
|
100
|
+
if (refused !== undefined) {
|
|
101
|
+
problems.push(`${id} declares a path that cannot attribute knowledge (${refused.p.slice(0, 60)}): ${refused.why}`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
valid.push(id);
|
|
105
|
+
for (const p of idea.paths)
|
|
106
|
+
if (!paths.includes(p))
|
|
107
|
+
paths.push(p);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
problems.push(`${id} is not a readable idea in this commit`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Strict: every idea the subject names must be valid; a bad id is never silently ignored.
|
|
115
|
+
if (problems.length > 0)
|
|
116
|
+
return { reason: problems.join('; ') };
|
|
117
|
+
const outside = files.filter((f) => !artifactPathsCover(paths, f));
|
|
118
|
+
if (outside.length > 0) {
|
|
119
|
+
const shown = outside.slice(0, 3).join(', ');
|
|
120
|
+
return { reason: `${shown}${outside.length > 3 ? ` and ${outside.length - 3} more` : ''} not covered by the declared paths of ${valid.join(', ')}` };
|
|
121
|
+
}
|
|
122
|
+
return { ideaIds: valid, paths };
|
|
123
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { type FreshnessEvidence, type FreshnessResult, type FreshnessStatus } from './freshness.js';
|
|
24
24
|
export type FindingKind = 'confirmed' | 'suspected' | 'unknown';
|
|
25
|
-
export type FindingCode = 'STATE_CONTRADICTION' | 'UNRECORDED_WORK' | 'UNATTRIBUTED_ACTIVITY' | 'ACTIVITY_WITHOUT_ACTIVE_STORY' | 'GIT_UNAVAILABLE' | 'STATE_NOT_IN_GIT' | 'GIT_HISTORY_TRUNCATED' | 'ACTIVE_STORY_UNKNOWN';
|
|
25
|
+
export type FindingCode = 'STATE_CONTRADICTION' | 'UNRECORDED_WORK' | 'UNATTRIBUTED_ACTIVITY' | 'ACTIVITY_WITHOUT_ACTIVE_STORY' | 'GIT_UNAVAILABLE' | 'STATE_NOT_IN_GIT' | 'GIT_HISTORY_TRUNCATED' | 'GIT_HISTORY_SHALLOW' | 'ACTIVE_STORY_UNKNOWN';
|
|
26
26
|
export interface ReconciliationFinding {
|
|
27
27
|
kind: FindingKind;
|
|
28
28
|
code: FindingCode;
|
|
@@ -54,6 +54,12 @@ export interface ReconciliationReport {
|
|
|
54
54
|
untrackedWork: UntrackedWorkItem[];
|
|
55
55
|
/** Ids git references that some state document mentions. Mentioned is not correct. Sorted. */
|
|
56
56
|
mentionedWork: string[];
|
|
57
|
+
/** KA-001: commits explained by registered independent knowledge (not part of any work item). */
|
|
58
|
+
independentKnowledge: {
|
|
59
|
+
ideaIds: string[];
|
|
60
|
+
paths: string[];
|
|
61
|
+
commits: string[];
|
|
62
|
+
}[];
|
|
57
63
|
documents: ReviewDocument[];
|
|
58
64
|
limitations: string[];
|
|
59
65
|
actions: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/brief/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,qBAAqB,GACrB,iBAAiB,GACjB,uBAAuB,GACvB,+BAA+B,GAC/B,iBAAiB,GACjB,kBAAkB,GAClB,uBAAuB,GACvB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAC;IAC3B,eAAe,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,wGAAwG;IACxG,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,8FAA8F;IAC9F,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAiBtC,wBAAgB,SAAS,CAAC,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,eAAe,GAAG,oBAAoB,
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/brief/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,eAAe,EAErB,MAAM,gBAAgB,CAAC;AAGxB,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,SAAS,CAAC;AAEhE,MAAM,MAAM,WAAW,GACnB,qBAAqB,GACrB,iBAAiB,GACjB,uBAAuB,GACvB,+BAA+B,GAC/B,iBAAiB,GACjB,kBAAkB,GAClB,uBAAuB,GACvB,qBAAqB,GACrB,sBAAsB,CAAC;AAE3B,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,oDAAoD;IACpD,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,eAAe,CAAC;IAC3B,eAAe,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,wGAAwG;IACxG,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,8FAA8F;IAC9F,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iGAAiG;IACjG,oBAAoB,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAClF,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAiBtC,wBAAgB,SAAS,CAAC,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,eAAe,GAAG,oBAAoB,CAkK9F;AAiBD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CA+F3G"}
|
package/dist/brief/reconcile.js
CHANGED
|
@@ -62,6 +62,7 @@ export function reconcile(ev, result) {
|
|
|
62
62
|
}
|
|
63
63
|
// 2. Git-derived evidence.
|
|
64
64
|
const untrackedWork = [];
|
|
65
|
+
const independentKnowledge = [];
|
|
65
66
|
let mentionedWork = [];
|
|
66
67
|
if (ev.git.status === 'unavailable') {
|
|
67
68
|
findings.push({ kind: 'unknown', code: 'GIT_UNAVAILABLE', summary: reasonMessage(result, 'GIT_UNAVAILABLE'), evidence: [] });
|
|
@@ -77,8 +78,20 @@ export function reconcile(ev, result) {
|
|
|
77
78
|
const activity = ev.git.commitsSince.filter((c) => c.files.some((f) => !ev.stateFiles.has(f)));
|
|
78
79
|
const byId = new Map();
|
|
79
80
|
const unattributed = [];
|
|
81
|
+
const knowledgeBy = new Map();
|
|
80
82
|
for (const c of activity) {
|
|
81
83
|
const ids = extractWorkItemIds(c.subject);
|
|
84
|
+
const k = ids.length === 0 ? ev.knowledge?.attributed.get(c.sha) : undefined;
|
|
85
|
+
if (k !== undefined) {
|
|
86
|
+
const key = k.ideaIds.join(', ');
|
|
87
|
+
const entry = knowledgeBy.get(key) ?? { paths: [], lines: [] };
|
|
88
|
+
entry.lines.push(commitLine(c));
|
|
89
|
+
for (const p of k.paths)
|
|
90
|
+
if (!entry.paths.includes(p))
|
|
91
|
+
entry.paths.push(p);
|
|
92
|
+
knowledgeBy.set(key, entry);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
82
95
|
if (ids.length === 0)
|
|
83
96
|
unattributed.push(c);
|
|
84
97
|
for (const id of ids)
|
|
@@ -117,11 +130,21 @@ export function reconcile(ev, result) {
|
|
|
117
130
|
kind: 'suspected',
|
|
118
131
|
code: 'UNATTRIBUTED_ACTIVITY',
|
|
119
132
|
summary: `${unattributed.length} commit(s) after the last state update change project files without naming a work item; whether the state documents cover them cannot be determined.`,
|
|
120
|
-
evidence: unattributed.slice(0, MAX_COMMITS_PER_ITEM).map(
|
|
133
|
+
evidence: unattributed.slice(0, MAX_COMMITS_PER_ITEM).map((c) => {
|
|
134
|
+
const why = ev.knowledge?.rejected.get(c.sha);
|
|
135
|
+
return why === undefined ? commitLine(c) : `${commitLine(c)} — names an idea but is not attributed: ${why.reason}`;
|
|
136
|
+
}),
|
|
121
137
|
});
|
|
122
138
|
actions.push(`Review the ${unattributed.length} commit(s) that name no work item and decide whether the recorded state covers them.`);
|
|
123
139
|
}
|
|
124
|
-
|
|
140
|
+
for (const [ids, entry] of knowledgeBy) {
|
|
141
|
+
independentKnowledge.push({ ideaIds: ids.split(', '), paths: entry.paths, commits: entry.lines });
|
|
142
|
+
}
|
|
143
|
+
if (knowledgeBy.size > 0) {
|
|
144
|
+
limitations.push('Commits attributed to independent knowledge are explained by a registered idea and its declared document paths; this is attribution, not approval or review, and says nothing about any work item or the active story.');
|
|
145
|
+
}
|
|
146
|
+
const explainedByKnowledge = [...knowledgeBy.values()].reduce((n, e) => n + e.lines.length, 0);
|
|
147
|
+
if (activity.length - explainedByKnowledge > 0 && ev.activeStory.kind === 'none') {
|
|
125
148
|
findings.push({
|
|
126
149
|
kind: 'suspected',
|
|
127
150
|
code: 'ACTIVITY_WITHOUT_ACTIVE_STORY',
|
|
@@ -131,6 +154,11 @@ export function reconcile(ev, result) {
|
|
|
131
154
|
review('active.md', 'no active story recorded while git shows later activity');
|
|
132
155
|
actions.push('Review the active story: confirm whether any work is current. No active story does not mean the project is complete.');
|
|
133
156
|
}
|
|
157
|
+
if (ev.git.shallow === true) {
|
|
158
|
+
findings.push({ kind: 'unknown', code: 'GIT_HISTORY_SHALLOW', summary: reasonMessage(result, 'GIT_HISTORY_SHALLOW'), evidence: [] });
|
|
159
|
+
limitations.push('The repository is a shallow clone; history before its boundary is absent, so the report cannot show what happened earlier.');
|
|
160
|
+
actions.push('Deepen the clone (git fetch --unshallow) or run this from a full checkout, then re-run.');
|
|
161
|
+
}
|
|
134
162
|
if (ev.git.truncated) {
|
|
135
163
|
findings.push({ kind: 'unknown', code: 'GIT_HISTORY_TRUNCATED', summary: reasonMessage(result, 'GIT_HISTORY_TRUNCATED'), evidence: [] });
|
|
136
164
|
limitations.push('Git history after the last state update was cut at the read limit; older commits are not examined.');
|
|
@@ -156,6 +184,7 @@ export function reconcile(ev, result) {
|
|
|
156
184
|
findings,
|
|
157
185
|
untrackedWork,
|
|
158
186
|
mentionedWork,
|
|
187
|
+
independentKnowledge,
|
|
159
188
|
documents,
|
|
160
189
|
limitations,
|
|
161
190
|
actions,
|
|
@@ -219,6 +248,17 @@ export function renderReconciliation(report, maxChars) {
|
|
|
219
248
|
}
|
|
220
249
|
sections.push(bullets(title, lines));
|
|
221
250
|
}
|
|
251
|
+
if (report.independentKnowledge.length > 0) {
|
|
252
|
+
const lines = [];
|
|
253
|
+
for (const k of report.independentKnowledge.slice(0, MAX_LISTED_ITEMS)) {
|
|
254
|
+
lines.push(`- ${san(k.ideaIds.join(', '))} (${k.commits.length} commit(s))`);
|
|
255
|
+
const shownPaths = k.paths.slice(0, MAX_LISTED_ITEMS).map(san).join(', ');
|
|
256
|
+
lines.push(` declared paths: ${shownPaths}${k.paths.length > MAX_LISTED_ITEMS ? ` and ${k.paths.length - MAX_LISTED_ITEMS} more` : ''}`);
|
|
257
|
+
for (const c of k.commits.slice(0, MAX_COMMITS_PER_ITEM))
|
|
258
|
+
lines.push(` ${san(c)}`);
|
|
259
|
+
}
|
|
260
|
+
sections.push(bullets('Attributed to registered independent knowledge (no work item, active story unaffected):', lines));
|
|
261
|
+
}
|
|
222
262
|
if (report.documents.length > 0) {
|
|
223
263
|
const lines = [];
|
|
224
264
|
for (const d of report.documents)
|
package/dist/cli/index.js
CHANGED
|
@@ -73,7 +73,7 @@ Durable usage:
|
|
|
73
73
|
monolith durable retention --retain-days N [--compact-after N] [--apply]
|
|
74
74
|
|
|
75
75
|
Idea usage:
|
|
76
|
-
monolith idea add --title "..." (--description "..." | --description-stdin) [--context "..."]
|
|
76
|
+
monolith idea add --title "..." (--description "..." | --description-stdin) [--context "..."] [--path <project-relative document file> ...]
|
|
77
77
|
monolith idea list
|
|
78
78
|
monolith idea show <id>
|
|
79
79
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic terminal colour (CLI-UX-001). Presentation only.
|
|
3
|
+
*
|
|
4
|
+
* Colour never carries meaning alone: every status word (PASS, FAIL, FRESH, …)
|
|
5
|
+
* is printed as text first and merely tinted. ANSI is emitted only to an
|
|
6
|
+
* interactive terminal; `NO_COLOR` (any non-empty value), `TERM=dumb`, pipes,
|
|
7
|
+
* redirects and `--json` output stay plain. No dependency, no state.
|
|
8
|
+
*/
|
|
9
|
+
export type Tone = 'good' | 'warn' | 'bad' | 'dim' | 'head';
|
|
10
|
+
/** Status word → tone. Words not listed are never tinted. */
|
|
11
|
+
export declare const STATUS_TONE: Readonly<Record<string, Tone>>;
|
|
12
|
+
export interface TextStream {
|
|
13
|
+
isTTY?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** True only for an interactive terminal that has not opted out. */
|
|
16
|
+
export declare function colorEnabled(stream?: TextStream, env?: NodeJS.ProcessEnv): boolean;
|
|
17
|
+
export declare function paint(text: string, tone: Tone, enabled: boolean): string;
|
|
18
|
+
/** Tints a bare status word; unknown words and a disabled style return it unchanged. */
|
|
19
|
+
export declare function statusWord(word: string, enabled: boolean): string;
|
|
20
|
+
/**
|
|
21
|
+
* Tints status tokens in a block of already-rendered text: the value after
|
|
22
|
+
* `Label: ` on the given labels, and `[STATUS]` markers at line start. Text is
|
|
23
|
+
* otherwise untouched, so disabling colour returns the input byte for byte.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tintStatuses(text: string, enabled: boolean, labels?: readonly string[]): string;
|
|
26
|
+
//# sourceMappingURL=style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/cli/style.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAU5D,6DAA6D;AAC7D,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CA2BtD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,MAAM,GAAE,UAA2B,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAK/G;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAExE;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAGjE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAOnG"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic terminal colour (CLI-UX-001). Presentation only.
|
|
3
|
+
*
|
|
4
|
+
* Colour never carries meaning alone: every status word (PASS, FAIL, FRESH, …)
|
|
5
|
+
* is printed as text first and merely tinted. ANSI is emitted only to an
|
|
6
|
+
* interactive terminal; `NO_COLOR` (any non-empty value), `TERM=dumb`, pipes,
|
|
7
|
+
* redirects and `--json` output stay plain. No dependency, no state.
|
|
8
|
+
*/
|
|
9
|
+
const CODES = {
|
|
10
|
+
good: '32', // green
|
|
11
|
+
warn: '33', // yellow
|
|
12
|
+
bad: '31', // red
|
|
13
|
+
dim: '90', // grey
|
|
14
|
+
head: '1;35', // bold purple
|
|
15
|
+
};
|
|
16
|
+
/** Status word → tone. Words not listed are never tinted. */
|
|
17
|
+
export const STATUS_TONE = {
|
|
18
|
+
PASS: 'good',
|
|
19
|
+
COMPLETE: 'good',
|
|
20
|
+
COMPLETED: 'good',
|
|
21
|
+
FRESH: 'good',
|
|
22
|
+
READY: 'good',
|
|
23
|
+
FINALIZED: 'good',
|
|
24
|
+
ALREADY_FINALIZED: 'good',
|
|
25
|
+
CONSISTENT: 'good',
|
|
26
|
+
done: 'good',
|
|
27
|
+
NOT_VERIFIED: 'warn',
|
|
28
|
+
UNKNOWN: 'warn',
|
|
29
|
+
WARNING: 'warn',
|
|
30
|
+
UPDATE: 'warn',
|
|
31
|
+
INCOMPLETE: 'warn',
|
|
32
|
+
pending: 'warn',
|
|
33
|
+
FAIL: 'bad',
|
|
34
|
+
CONFLICT: 'bad',
|
|
35
|
+
conflict: 'bad',
|
|
36
|
+
STALE: 'bad',
|
|
37
|
+
INCONSISTENT: 'bad',
|
|
38
|
+
REFUSED: 'bad',
|
|
39
|
+
NOT_READY: 'bad',
|
|
40
|
+
missing: 'bad',
|
|
41
|
+
unreadable: 'bad',
|
|
42
|
+
UNCHANGED: 'dim',
|
|
43
|
+
SKIP: 'dim',
|
|
44
|
+
};
|
|
45
|
+
/** True only for an interactive terminal that has not opted out. */
|
|
46
|
+
export function colorEnabled(stream = process.stdout, env = process.env) {
|
|
47
|
+
if (stream.isTTY !== true)
|
|
48
|
+
return false;
|
|
49
|
+
if ((env.NO_COLOR ?? '') !== '')
|
|
50
|
+
return false;
|
|
51
|
+
if (env.TERM === 'dumb')
|
|
52
|
+
return false;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
export function paint(text, tone, enabled) {
|
|
56
|
+
return enabled ? `\u001b[${CODES[tone]}m${text}\u001b[0m` : text;
|
|
57
|
+
}
|
|
58
|
+
/** Tints a bare status word; unknown words and a disabled style return it unchanged. */
|
|
59
|
+
export function statusWord(word, enabled) {
|
|
60
|
+
const tone = STATUS_TONE[word];
|
|
61
|
+
return tone === undefined ? word : paint(word, tone, enabled);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tints status tokens in a block of already-rendered text: the value after
|
|
65
|
+
* `Label: ` on the given labels, and `[STATUS]` markers at line start. Text is
|
|
66
|
+
* otherwise untouched, so disabling colour returns the input byte for byte.
|
|
67
|
+
*/
|
|
68
|
+
export function tintStatuses(text, enabled, labels = []) {
|
|
69
|
+
if (!enabled)
|
|
70
|
+
return text;
|
|
71
|
+
let out = text.replace(/^(\s*)\[([A-Za-z_]+)\]/gm, (m, sp, w) => (STATUS_TONE[w] ? `${sp}[${statusWord(w, true)}]` : m));
|
|
72
|
+
for (const label of labels) {
|
|
73
|
+
out = out.replace(new RegExp(`^(${label}: )([A-Za-z_]+)`, 'gm'), (_m, pre, w) => `${pre}${statusWord(w, true)}`);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
package/dist/cli/ui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/cli/ui.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AAYH,kDAAkD;AAClD,qBAAa,aAAc,SAAQ,KAAK;CAAG;AAE3C,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,qFAAqF;IACrF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;CACjC;AAMD,yGAAyG;AACzG,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAqB/D;AA2CD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBvE;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuD1E;AAED,sGAAsG;AACtG,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8C3D"}
|
package/dist/cli/ui.js
CHANGED
|
@@ -17,6 +17,7 @@ import { dirname, join, resolve } from 'path';
|
|
|
17
17
|
import { fileURLToPath } from 'url';
|
|
18
18
|
import { startDashboardServer, loadUiAssets, UiAssetsError } from '../http/index.js';
|
|
19
19
|
import { RootContainmentError, resolveMonolithRoot } from '../root/index.js';
|
|
20
|
+
import { colorEnabled, paint, statusWord } from './style.js';
|
|
20
21
|
import { DEFAULT_DEV_UI_PORT, DEFAULT_UI_PORT, UI_DEV_USAGE, UI_USAGE, parseUiArgs } from './ui-args.js';
|
|
21
22
|
/** Thrown for expected, user-fixable failures. */
|
|
22
23
|
export class UiLaunchError extends Error {
|
|
@@ -47,6 +48,31 @@ export function assertMonolithProject(projectRoot) {
|
|
|
47
48
|
throw new UiLaunchError(`No MONOLITH instance found in ${projectRoot} (expected directory ${rootAbs}). Run 'monolith init' here, or cd into a MONOLITH project.`);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The launch block (CLI-UX-001). Printed only after the server is actually
|
|
53
|
+
* listening, so READY is never shown for a failed start (failures throw before
|
|
54
|
+
* this and are reported on stderr). The first line is the alpha.3 machine-readable
|
|
55
|
+
* `MONOLITH Mission Control: <url>` (scripts that parse it keep working); the URL
|
|
56
|
+
* also stands on a line of its own for copying. The CLI does not open a browser
|
|
57
|
+
* and says so by asking the user to.
|
|
58
|
+
*/
|
|
59
|
+
function announce(log, info) {
|
|
60
|
+
const on = colorEnabled();
|
|
61
|
+
for (const line of [
|
|
62
|
+
// Compatibility (A4-CLI-02): the first line stays the machine-readable `<title>: <url>` that alpha.3 scripts parse.
|
|
63
|
+
`${paint(info.title, 'head', on)}: ${info.url}`,
|
|
64
|
+
`Status: ${statusWord('READY', on)}`,
|
|
65
|
+
'',
|
|
66
|
+
'Open in your browser:',
|
|
67
|
+
` ${info.url}`,
|
|
68
|
+
'',
|
|
69
|
+
`Project: ${info.projectRoot}`,
|
|
70
|
+
info.note,
|
|
71
|
+
'Press Ctrl+C to stop.',
|
|
72
|
+
]) {
|
|
73
|
+
log(line);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
50
76
|
async function listen(projectRoot, port, assets) {
|
|
51
77
|
try {
|
|
52
78
|
return await startDashboardServer({ projectRoot, port, assets });
|
|
@@ -77,9 +103,7 @@ export async function launchUi(opts) {
|
|
|
77
103
|
throw err;
|
|
78
104
|
}
|
|
79
105
|
const server = await listen(projectRoot, opts.port, assets);
|
|
80
|
-
log
|
|
81
|
-
log(`Project: ${projectRoot}`);
|
|
82
|
-
log('Read-only. Listening on 127.0.0.1 only. Press Ctrl+C to stop.');
|
|
106
|
+
announce(log, { title: 'MONOLITH Mission Control', url: server.url, projectRoot, note: 'Read-only. Listening on 127.0.0.1 only.' });
|
|
83
107
|
return {
|
|
84
108
|
url: server.url,
|
|
85
109
|
stop: () => server.close(),
|
|
@@ -124,9 +148,12 @@ export async function launchUiDev(opts) {
|
|
|
124
148
|
await stop();
|
|
125
149
|
return code;
|
|
126
150
|
});
|
|
127
|
-
log
|
|
128
|
-
|
|
129
|
-
|
|
151
|
+
announce(log, {
|
|
152
|
+
title: 'MONOLITH Mission Control (dev, HMR)',
|
|
153
|
+
url: `http://127.0.0.1:${opts.port}`,
|
|
154
|
+
projectRoot,
|
|
155
|
+
note: `Read-only dashboard API on ${api.url}. Listening on 127.0.0.1 only.`,
|
|
156
|
+
});
|
|
130
157
|
return { url: `http://127.0.0.1:${opts.port}`, stop, ended };
|
|
131
158
|
}
|
|
132
159
|
/** CLI entry for `monolith ui ...`. Resolves with the process exit code once the launch has ended. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/coordinatorium/cli.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAEH,OAAO,EAAgE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAChH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAwBhD;;;;;;;;;;;;GAYG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAG,SAAS,CAAU;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,QAAQ,EAAE,cAAc,GAAG,iBAAiB;CAiBzD;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAS5E;AA8BD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9F;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB9F;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7F;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/coordinatorium/cli.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAEH,OAAO,EAAgE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAChH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAwBhD;;;;;;;;;;;;GAYG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAG,SAAS,CAAU;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,QAAQ,EAAE,cAAc,GAAG,iBAAiB;CAiBzD;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAS5E;AA8BD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9F;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB9F;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7F;AA8BD,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBnG;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAepG;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/F"}
|
|
@@ -155,7 +155,20 @@ function formatCheckpoint(c) {
|
|
|
155
155
|
];
|
|
156
156
|
return lines.join('\n');
|
|
157
157
|
}
|
|
158
|
+
const CHECKPOINT_VALUE_FLAGS = ['--summary', '--remaining', '--context'];
|
|
159
|
+
/** `mission checkpoint` takes only its documented options; an unknown `--option` is an error, not silently dropped. */
|
|
160
|
+
function rejectUnknownCheckpointFlags(args) {
|
|
161
|
+
for (let i = 1; i < args.length; i++) {
|
|
162
|
+
if (CHECKPOINT_VALUE_FLAGS.includes(args[i]))
|
|
163
|
+
i++; // its value, whatever it looks like
|
|
164
|
+
else if (args[i].startsWith('--')) {
|
|
165
|
+
throw new Error(`Unknown option ${JSON.stringify(args[i].slice(0, 40))} for mission checkpoint.\n` +
|
|
166
|
+
'Usage: monolith mission checkpoint <missionId> --summary "..." [--remaining "..." ...] [--context "..."]');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
158
170
|
export async function runMissionCheckpoint(args, targetDir = process.cwd()) {
|
|
171
|
+
rejectUnknownCheckpointFlags(args);
|
|
159
172
|
const missionId = args[0];
|
|
160
173
|
const summary = parseFlag(args, '--summary');
|
|
161
174
|
if (!missionId || !summary) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/finalize/cli.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/finalize/cli.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAsGH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBnG"}
|
package/dist/finalize/cli.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { lstatSync, readFileSync } from 'fs';
|
|
11
11
|
import { applyFinalization, planFinalization } from './core.js';
|
|
12
|
+
import { colorEnabled, tintStatuses } from '../cli/style.js';
|
|
12
13
|
import { parseProposal } from './proposal.js';
|
|
13
14
|
const USAGE = `Usage:
|
|
14
15
|
monolith finalize plan --proposal <file|-> [--json]
|
|
@@ -74,32 +75,48 @@ async function readProposal(src) {
|
|
|
74
75
|
throw new Error(`invalid proposal:\n${parsed.errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
75
76
|
return parsed.proposal;
|
|
76
77
|
}
|
|
78
|
+
function printConsistency(c) {
|
|
79
|
+
if (c === undefined)
|
|
80
|
+
return;
|
|
81
|
+
say(`\nState consistency (${c.projected ? 'projected after apply' : 'actual'}): ${c.status}`);
|
|
82
|
+
for (const f of c.findings)
|
|
83
|
+
say(` [CONFLICT] ${printable(f.message)}\n → ${printable(f.remediation)}`);
|
|
84
|
+
for (const n of c.notVerified)
|
|
85
|
+
say(` [NOT_VERIFIED] ${printable(n)}`);
|
|
86
|
+
if (c.status !== 'CONSISTENT')
|
|
87
|
+
say(' This does not change the outcome of the finalization; MONOLITH edits none of the documents above on its own.');
|
|
88
|
+
say(` Scope: ${printable(c.scope)}`);
|
|
89
|
+
}
|
|
90
|
+
const say = (line) => console.log(tintStatuses(line, colorEnabled(), ['Finalization plan for [^:]+', 'Finalization of [^:]+', 'State consistency [^:]+']));
|
|
77
91
|
function printPlan(pl) {
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
say(`Finalization plan for ${printable(pl.storyId)}: ${pl.status}${pl.partial ? ' (PARTIAL: an earlier run was interrupted; apply rolls it forward)' : ''}`);
|
|
93
|
+
say('\nPreconditions:');
|
|
80
94
|
for (const c of pl.checks)
|
|
81
|
-
|
|
82
|
-
|
|
95
|
+
say(` [${c.status}] ${c.id}: ${printable(c.detail)}\n source: ${printable(c.source)}`);
|
|
96
|
+
say('\nTargets (in write order, active.md last):');
|
|
83
97
|
for (const t of pl.targets)
|
|
84
|
-
|
|
98
|
+
say(` [${t.state}] ${t.id} → ${printable(t.path)}${t.state === 'done' ? '' : ` — ${printable(t.reason)}`}`);
|
|
85
99
|
for (const d of pl.diagnostics)
|
|
86
|
-
|
|
100
|
+
say(`\nNote: ${printable(d)}`);
|
|
101
|
+
printConsistency(pl.consistency);
|
|
87
102
|
if (pl.status === 'READY')
|
|
88
|
-
|
|
103
|
+
say(`\nDigest: ${pl.digest}\nOwner approval: monolith finalize apply --proposal <same file> --approve ${pl.digest}\nThe digest shows this plan is unchanged; it does not prove who approves it. Do not run apply unless the owner approved this exact plan.`);
|
|
89
104
|
}
|
|
90
105
|
function printApply(r) {
|
|
91
|
-
|
|
106
|
+
say(`Finalization of ${printable(r.storyId)}: ${r.status}`);
|
|
92
107
|
if (r.applied.length > 0)
|
|
93
|
-
|
|
108
|
+
say(` written: ${r.applied.join(', ')}`);
|
|
94
109
|
if (r.status !== 'FINALIZED' && r.pending.length > 0)
|
|
95
|
-
|
|
110
|
+
say(` not written: ${r.pending.join(', ')}`);
|
|
96
111
|
if (r.failed)
|
|
97
|
-
|
|
112
|
+
say(` failed: ${r.failed.target}${r.failed.publication ? ` [${r.failed.publication}]` : ''}: ${printable(r.failed.message)}`);
|
|
98
113
|
for (const d of r.diagnostics)
|
|
99
|
-
|
|
114
|
+
say(` ${printable(d)}`);
|
|
100
115
|
if (r.status === 'REFUSED')
|
|
101
116
|
for (const c of r.plan.checks.filter((x) => x.status !== 'PASS'))
|
|
102
|
-
|
|
117
|
+
say(` [${c.status}] ${c.id}: ${printable(c.detail)}`);
|
|
118
|
+
if (r.status === 'FINALIZED' || r.status === 'ALREADY_FINALIZED')
|
|
119
|
+
printConsistency(r.plan.consistency);
|
|
103
120
|
}
|
|
104
121
|
export async function runFinalizeCommand(args, targetDir = process.cwd()) {
|
|
105
122
|
const sub = args[0];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consistency assessment of the state documents around a finalization (FIN-002).
|
|
3
|
+
*
|
|
4
|
+
* `finalize` writes a fixed, small set of documents (story-index.md, active.md,
|
|
5
|
+
* and whatever the owner listed in `docUpdates` / HANDOFF). It deliberately does
|
|
6
|
+
* NOT write roadmap-index.md, so a roadmap row that still says IN PROGRESS for
|
|
7
|
+
* the story that was just finalized used to survive a successful `apply`
|
|
8
|
+
* unnoticed. This module reports such disagreements; it never edits anything
|
|
9
|
+
* and never changes the outcome of the operation.
|
|
10
|
+
*
|
|
11
|
+
* It reuses `findContradictions()` of `monolith brief` — the single definition
|
|
12
|
+
* of "state documents disagree" — and feeds it the text the documents have (or
|
|
13
|
+
* will have, for a plan) after finalization. Four things are kept apart:
|
|
14
|
+
* - operation success → plan/apply `status` (unchanged)
|
|
15
|
+
* - managed documents → written by this finalization
|
|
16
|
+
* - external documents → not written by it; remediation is advice only
|
|
17
|
+
* - completeness of the evidence → NOT_VERIFIED when a document cannot be read
|
|
18
|
+
*/
|
|
19
|
+
export type ConsistencyStatus = 'CONSISTENT' | 'INCONSISTENT' | 'NOT_VERIFIED';
|
|
20
|
+
export interface ConsistencyFinding {
|
|
21
|
+
message: string;
|
|
22
|
+
/** State documents (file names) that disagree. */
|
|
23
|
+
documents: string[];
|
|
24
|
+
/** The subset of `documents` that this finalization does not write. */
|
|
25
|
+
externalDocuments: string[];
|
|
26
|
+
/** What a person can do; MONOLITH does not do it. */
|
|
27
|
+
remediation: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ConsistencyAssessment {
|
|
30
|
+
status: ConsistencyStatus;
|
|
31
|
+
/** true: computed from the texts a pending plan would write; false: read from disk. */
|
|
32
|
+
projected: boolean;
|
|
33
|
+
findings: ConsistencyFinding[];
|
|
34
|
+
/** Why the check could not be completed (NOT_VERIFIED). */
|
|
35
|
+
notVerified: string[];
|
|
36
|
+
/** What this check does not cover. */
|
|
37
|
+
scope: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ConsistencyInput {
|
|
40
|
+
/** Text of each state document as it is / will be; null = missing or unreadable. */
|
|
41
|
+
active: string | null;
|
|
42
|
+
roadmap: string | null;
|
|
43
|
+
storyIndex: string | null;
|
|
44
|
+
/** Project-relative paths this finalization writes (or has written). */
|
|
45
|
+
writtenPaths: string[];
|
|
46
|
+
projected: boolean;
|
|
47
|
+
/** The MONOLITH root (project-relative); documents are matched by their full canonical path. */
|
|
48
|
+
managedRoot?: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function assessConsistency(input: ConsistencyInput): ConsistencyAssessment;
|
|
51
|
+
//# sourceMappingURL=consistency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consistency.d.ts","sourceRoot":"","sources":["../../src/finalize/consistency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,cAAc,GAAG,cAAc,CAAC;AAE/E,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,uEAAuE;IACvE,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,uFAAuF;IACvF,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,2DAA2D;IAC3D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,oFAAoF;IACpF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,gGAAgG;IAChG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAMD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,qBAAqB,CAmChF"}
|