@latysh-development/monolith 0.1.0-alpha.1 → 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -160,7 +160,7 @@ listing — not an official partnership, endorsement or certification.
160
160
 
161
161
  ## Project Status
162
162
 
163
- **Alpha** (`0.1.0-alpha.1`). The API and CLI are experimental and may change
163
+ **Alpha** (`0.1.0-alpha.2`). The API and CLI are experimental and may change
164
164
  without notice. Published to npm under the alpha dist-tag. Durable State and the Coordinatorium
165
165
  (`monolith durable`, `monolith mission`) are experimental and are not part of
166
166
  the Quick Start.
package/dist/cli/index.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * monolith brief — deterministic, bounded project context summary
13
13
  * monolith durable — inspect/verify/maintain Durable State task logs
14
14
  * monolith idea — capture and recall deferred ideas (add/list/show)
15
+ * monolith finalize — plan/apply a controlled story finalization (documentation sync)
15
16
  * monolith update — add missing Idea Capture infrastructure to an existing instance
16
17
  * monolith ui — launch Mission Control (read-only dashboard)
17
18
  */
@@ -42,6 +43,8 @@ Commands:
42
43
  list | show | assess | packet | verify | retention.
43
44
  idea Capture a deferred thought without leaving the current task, and
44
45
  recall it later: add | list | show. Plain Markdown files, no LLM.
46
+ finalize Close a story with checked preconditions and controlled documentation
47
+ updates: plan (read-only) | apply --approve <digest>.
45
48
  update Bring an existing instance up to Idea Capture & Recall. Preview by
46
49
  default (changes nothing). --apply creates MONOLITH-managed files
47
50
  and may add or refresh managed sections inside your existing
@@ -74,6 +77,10 @@ Idea usage:
74
77
  monolith idea list
75
78
  monolith idea show <id>
76
79
 
80
+ Finalize usage:
81
+ monolith finalize plan --proposal <file|-> [--json]
82
+ monolith finalize apply --proposal <file|-> --approve <digest> [--json]
83
+
77
84
  Update usage:
78
85
  monolith update [--apply]
79
86
 
@@ -169,6 +176,11 @@ async function main() {
169
176
  await runIdeaCommand(args.slice(1));
170
177
  break;
171
178
  }
179
+ case 'finalize': {
180
+ const { runFinalizeCommand } = await import('../finalize/cli.js');
181
+ process.exitCode = await runFinalizeCommand(args.slice(1));
182
+ break;
183
+ }
172
184
  case 'update': {
173
185
  const { runUpdate } = await import('../update/cli.js');
174
186
  process.exitCode = await runUpdate(args.slice(1));
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `monolith finalize plan|apply` (DOCSYNC-001, ADR-022).
3
+ *
4
+ * `plan` is read-only. `apply` writes only with `--approve <digest>` taken from
5
+ * a `plan` the owner reviewed. Exit codes: 0 ok / already finalized; 1 usage or
6
+ * unreadable proposal; 2 not ready / refused (nothing written); 3 incomplete
7
+ * (partially applied — see the output).
8
+ */
9
+ export declare function runFinalizeCommand(args: string[], targetDir?: string): Promise<number>;
10
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/finalize/cli.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AAuFH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBnG"}
@@ -0,0 +1,134 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * `monolith finalize plan|apply` (DOCSYNC-001, ADR-022).
4
+ *
5
+ * `plan` is read-only. `apply` writes only with `--approve <digest>` taken from
6
+ * a `plan` the owner reviewed. Exit codes: 0 ok / already finalized; 1 usage or
7
+ * unreadable proposal; 2 not ready / refused (nothing written); 3 incomplete
8
+ * (partially applied — see the output).
9
+ */
10
+ import { lstatSync, readFileSync } from 'fs';
11
+ import { applyFinalization, planFinalization } from './core.js';
12
+ import { parseProposal } from './proposal.js';
13
+ const USAGE = `Usage:
14
+ monolith finalize plan --proposal <file|-> [--json]
15
+ monolith finalize apply --proposal <file|-> --approve <digest> [--json]`;
16
+ const MAX_PROPOSAL_BYTES = 4 * 1024 * 1024;
17
+ // eslint-disable-next-line no-control-regex
18
+ const printable = (s) => s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
19
+ function parseFlags(args, allowApprove) {
20
+ const f = { json: false };
21
+ for (let i = 0; i < args.length; i++) {
22
+ const a = args[i];
23
+ if (a === '--json')
24
+ f.json = true;
25
+ else if (a === '--proposal' || (allowApprove && a === '--approve')) {
26
+ const v = args[i + 1];
27
+ if (v === undefined || v.startsWith('--'))
28
+ throw new Error(`${a} needs a value.\n${USAGE}`);
29
+ const key = a === '--proposal' ? 'proposal' : 'approve';
30
+ if (f[key] !== undefined)
31
+ throw new Error(`${a} was given more than once.\n${USAGE}`);
32
+ f[key] = v;
33
+ i++;
34
+ }
35
+ else
36
+ throw new Error(`Unexpected argument ${JSON.stringify(printable(a))}.\n${USAGE}`);
37
+ }
38
+ if (f.proposal === undefined)
39
+ throw new Error(`--proposal is required.\n${USAGE}`);
40
+ if (allowApprove && f.approve === undefined)
41
+ throw new Error(`--approve <digest> is required: run \`monolith finalize plan\`, review it, then pass its digest.\n${USAGE}`);
42
+ return f;
43
+ }
44
+ async function readProposal(src) {
45
+ let raw;
46
+ if (src === '-') {
47
+ const chunks = [];
48
+ let n = 0;
49
+ for await (const c of process.stdin) {
50
+ n += c.length;
51
+ if (n > MAX_PROPOSAL_BYTES)
52
+ throw new Error('proposal on stdin is too large.');
53
+ chunks.push(c);
54
+ }
55
+ raw = Buffer.concat(chunks).toString('utf8');
56
+ }
57
+ else {
58
+ const st = lstatSync(src);
59
+ if (!st.isFile())
60
+ throw new Error(`${src} is not a regular file.`);
61
+ if (st.size > MAX_PROPOSAL_BYTES)
62
+ throw new Error(`${src} is too large.`);
63
+ raw = readFileSync(src, 'utf8');
64
+ }
65
+ let json;
66
+ try {
67
+ json = JSON.parse(raw);
68
+ }
69
+ catch (err) {
70
+ throw new Error(`proposal is not valid JSON: ${err.message}`);
71
+ }
72
+ const parsed = parseProposal(json);
73
+ if (!parsed.ok)
74
+ throw new Error(`invalid proposal:\n${parsed.errors.map((e) => ` - ${e}`).join('\n')}`);
75
+ return parsed.proposal;
76
+ }
77
+ function printPlan(pl) {
78
+ console.log(`Finalization plan for ${printable(pl.storyId)}: ${pl.status}${pl.partial ? ' (PARTIAL: an earlier run was interrupted; apply rolls it forward)' : ''}`);
79
+ console.log('\nPreconditions:');
80
+ for (const c of pl.checks)
81
+ console.log(` [${c.status}] ${c.id}: ${printable(c.detail)}\n source: ${printable(c.source)}`);
82
+ console.log('\nTargets (in write order, active.md last):');
83
+ for (const t of pl.targets)
84
+ console.log(` [${t.state}] ${t.id} → ${printable(t.path)}${t.state === 'done' ? '' : ` — ${printable(t.reason)}`}`);
85
+ for (const d of pl.diagnostics)
86
+ console.log(`\nNote: ${printable(d)}`);
87
+ if (pl.status === 'READY')
88
+ console.log(`\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
+ }
90
+ function printApply(r) {
91
+ console.log(`Finalization of ${printable(r.storyId)}: ${r.status}`);
92
+ if (r.applied.length > 0)
93
+ console.log(` written: ${r.applied.join(', ')}`);
94
+ if (r.status !== 'FINALIZED' && r.pending.length > 0)
95
+ console.log(` not written: ${r.pending.join(', ')}`);
96
+ if (r.failed)
97
+ console.log(` failed: ${r.failed.target}${r.failed.publication ? ` [${r.failed.publication}]` : ''}: ${printable(r.failed.message)}`);
98
+ for (const d of r.diagnostics)
99
+ console.log(` ${printable(d)}`);
100
+ if (r.status === 'REFUSED')
101
+ for (const c of r.plan.checks.filter((x) => x.status !== 'PASS'))
102
+ console.log(` [${c.status}] ${c.id}: ${printable(c.detail)}`);
103
+ }
104
+ export async function runFinalizeCommand(args, targetDir = process.cwd()) {
105
+ const sub = args[0];
106
+ try {
107
+ if (sub === 'plan') {
108
+ const f = parseFlags(args.slice(1), false);
109
+ const pl = planFinalization(targetDir, await readProposal(f.proposal));
110
+ if (f.json)
111
+ console.log(JSON.stringify(pl, null, 2));
112
+ else
113
+ printPlan(pl);
114
+ return pl.status === 'NOT_READY' ? 2 : 0;
115
+ }
116
+ if (sub === 'apply') {
117
+ const f = parseFlags(args.slice(1), true);
118
+ const r = await applyFinalization(targetDir, await readProposal(f.proposal), f.approve, {
119
+ onWarning: (w) => console.error(`warning: ${printable(w.message)}`),
120
+ });
121
+ if (f.json)
122
+ console.log(JSON.stringify(r, null, 2));
123
+ else
124
+ printApply(r);
125
+ return r.status === 'FINALIZED' || r.status === 'ALREADY_FINALIZED' ? 0 : r.status === 'REFUSED' ? 2 : 3;
126
+ }
127
+ console.error(`Unknown finalize subcommand: ${sub === undefined ? '(none)' : printable(sub)}\n${USAGE}`);
128
+ return 1;
129
+ }
130
+ catch (err) {
131
+ console.error(err instanceof Error ? err.message : String(err));
132
+ return 1;
133
+ }
134
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Deterministic story finalization (DOCSYNC-001, ADR-022).
3
+ *
4
+ * `planFinalization` is read-only: it validates a proposal against the
5
+ * instance, evaluates explicit preconditions (PASS / FAIL / NOT_VERIFIED),
6
+ * classifies every write target and returns a digest that binds an owner's
7
+ * approval to exactly what was inspected. `applyFinalization` re-plans under a
8
+ * cross-process lock and writes only if the digest still matches.
9
+ *
10
+ * Consistency model (narrower than a transaction, deliberately): there is no
11
+ * rollback. Targets are written one at a time in a fixed order with `active.md`
12
+ * LAST, every write is an atomic single-file publish (src/root safe-write), and
13
+ * each target has a deterministic "already done" predicate. An interruption
14
+ * leaves a state that `planFinalization` reports as partial and that re-running
15
+ * plan + apply rolls forward; success is only reported after a post-write
16
+ * re-read of every target. See ADR-022.
17
+ *
18
+ * Reused, not reimplemented: safe reads/writes and containment (src/root),
19
+ * the cross-process lock (src/coordinatorium/lock), the Current Story section
20
+ * extractor of `monolith brief` (src/brief/story).
21
+ */
22
+ import { type ConfigRead, type FsOps, type WriteWarning } from '../root/index.js';
23
+ import { type LockOptions } from '../coordinatorium/lock.js';
24
+ import { type FinalizationProposal } from './proposal.js';
25
+ export type CheckStatus = 'PASS' | 'FAIL' | 'NOT_VERIFIED';
26
+ export type TargetKind = 'capsule' | 'execution-history' | 'story-index' | 'doc' | 'handoff' | 'active';
27
+ export type TargetState = 'pending' | 'done' | 'conflict' | 'missing' | 'unreadable';
28
+ export type PlanStatus = 'READY' | 'NOT_READY' | 'ALREADY_FINALIZED';
29
+ export type ApplyStatus = 'FINALIZED' | 'ALREADY_FINALIZED' | 'REFUSED' | 'INCOMPLETE';
30
+ export interface Check {
31
+ id: string;
32
+ status: CheckStatus;
33
+ detail: string;
34
+ /** Where the verdict comes from (a MONOLITH computation vs. attested evidence). */
35
+ source: string;
36
+ }
37
+ export interface PlannedTarget {
38
+ id: string;
39
+ kind: TargetKind;
40
+ /** Project-relative, `/`-separated. */
41
+ path: string;
42
+ state: TargetState;
43
+ reason: string;
44
+ /** sha256 of the file as inspected, `ABSENT`, or `DONE`. Part of the digest. */
45
+ base: string;
46
+ /** What will be written/appended; omitted for done/blocked targets and for opaque documents. */
47
+ preview?: string;
48
+ }
49
+ export interface FinalizationPlan {
50
+ status: PlanStatus;
51
+ storyId: string;
52
+ /** Approval token: sha256 over the proposal, every target's state/base and the evidence inspected. */
53
+ digest: string;
54
+ /** True when some, but not all, targets are already in their final state (an earlier run was interrupted). */
55
+ partial: boolean;
56
+ checks: Check[];
57
+ targets: PlannedTarget[];
58
+ diagnostics: string[];
59
+ }
60
+ export interface ApplyResult {
61
+ status: ApplyStatus;
62
+ storyId: string;
63
+ plan: FinalizationPlan;
64
+ applied: string[];
65
+ pending: string[];
66
+ failed?: {
67
+ target: string;
68
+ message: string;
69
+ publication?: string;
70
+ };
71
+ diagnostics: string[];
72
+ }
73
+ export interface FinalizeOptions {
74
+ /** Test-only fault injection for the safe-write primitives. */
75
+ ops?: Partial<FsOps>;
76
+ lock?: LockOptions;
77
+ onWarning?: (w: WriteWarning) => void;
78
+ }
79
+ export interface FinalizeConfig {
80
+ requiredDocs: Array<{
81
+ path: string;
82
+ label: string;
83
+ }>;
84
+ allowedDocs: string[];
85
+ errors: string[];
86
+ }
87
+ export declare function readFinalizeConfig(config: ConfigRead): FinalizeConfig;
88
+ export declare function renderCapsule(p: FinalizationProposal): string;
89
+ /** Evidence older than this many days before the finalization date is not accepted. */
90
+ export declare const EVIDENCE_MAX_AGE_DAYS = 7;
91
+ export type ActiveIdentity = {
92
+ ok: true;
93
+ id: string;
94
+ } | {
95
+ ok: false;
96
+ reason: string;
97
+ };
98
+ /**
99
+ * Structural identity of the active story (DOCSYNC-007). The ONLY source is the
100
+ * `**Story:** <id> …` field line inside `## Current Story` (same grammar as the
101
+ * evidence files and `## Last Finalization`); its FIRST token is the id. Ids
102
+ * mentioned in titles, descriptions, comments, code fences or other fields carry
103
+ * no authority. Zero, several, or malformed field lines → no identity (fail closed).
104
+ */
105
+ export declare function activeStoryIdentity(currentStoryBody: string): ActiveIdentity;
106
+ export declare function planFinalization(targetDir: string, proposal: FinalizationProposal): FinalizationPlan;
107
+ export declare function applyFinalization(targetDir: string, proposal: FinalizationProposal, approvedDigest: string, opts?: FinalizeOptions): Promise<ApplyResult>;
108
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/finalize/core.ts"],"names":[],"mappings":"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"}