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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +63 -16
  2. package/dist/brief/collect.d.ts.map +1 -1
  3. package/dist/brief/collect.js +41 -1
  4. package/dist/brief/constants.d.ts +2 -0
  5. package/dist/brief/constants.d.ts.map +1 -1
  6. package/dist/brief/constants.js +2 -0
  7. package/dist/brief/render.d.ts +7 -0
  8. package/dist/brief/render.d.ts.map +1 -1
  9. package/dist/brief/render.js +52 -1
  10. package/dist/brief/types.d.ts +17 -1
  11. package/dist/brief/types.d.ts.map +1 -1
  12. package/dist/cli/index.js +33 -0
  13. package/dist/discovery/compose.d.ts +28 -0
  14. package/dist/discovery/compose.d.ts.map +1 -0
  15. package/dist/discovery/compose.js +159 -0
  16. package/dist/discovery/contracts.d.ts +211 -0
  17. package/dist/discovery/contracts.d.ts.map +1 -0
  18. package/dist/discovery/contracts.js +555 -0
  19. package/dist/discovery/docs.d.ts +278 -0
  20. package/dist/discovery/docs.d.ts.map +1 -0
  21. package/dist/discovery/docs.js +1182 -0
  22. package/dist/discovery/facts.d.ts +49 -0
  23. package/dist/discovery/facts.d.ts.map +1 -0
  24. package/dist/discovery/facts.js +541 -0
  25. package/dist/discovery/index.d.ts +8 -0
  26. package/dist/discovery/index.d.ts.map +1 -0
  27. package/dist/discovery/index.js +9 -0
  28. package/dist/discovery/orchestrate.d.ts +35 -0
  29. package/dist/discovery/orchestrate.d.ts.map +1 -0
  30. package/dist/discovery/orchestrate.js +32 -0
  31. package/dist/discovery/render.d.ts +36 -0
  32. package/dist/discovery/render.d.ts.map +1 -0
  33. package/dist/discovery/render.js +278 -0
  34. package/dist/discovery/traverse.d.ts +197 -0
  35. package/dist/discovery/traverse.d.ts.map +1 -0
  36. package/dist/discovery/traverse.js +510 -0
  37. package/dist/durable/errors.d.ts +6 -2
  38. package/dist/durable/errors.d.ts.map +1 -1
  39. package/dist/durable/errors.js +9 -3
  40. package/dist/durable/fs-store.d.ts +21 -0
  41. package/dist/durable/fs-store.d.ts.map +1 -1
  42. package/dist/durable/fs-store.js +103 -3
  43. package/dist/durable/prune-check.d.ts +17 -0
  44. package/dist/durable/prune-check.d.ts.map +1 -1
  45. package/dist/durable/prune-check.js +27 -0
  46. package/dist/durable/runtime.d.ts +5 -3
  47. package/dist/durable/runtime.d.ts.map +1 -1
  48. package/dist/durable/runtime.js +23 -6
  49. package/dist/durable/seams.d.ts +4 -0
  50. package/dist/durable/seams.d.ts.map +1 -1
  51. package/dist/durable/testing.js +2 -2
  52. package/dist/ideas/cli.d.ts +33 -0
  53. package/dist/ideas/cli.d.ts.map +1 -0
  54. package/dist/ideas/cli.js +158 -0
  55. package/dist/ideas/format.d.ts +38 -0
  56. package/dist/ideas/format.d.ts.map +1 -0
  57. package/dist/ideas/format.js +194 -0
  58. package/dist/ideas/index.d.ts +6 -0
  59. package/dist/ideas/index.d.ts.map +1 -0
  60. package/dist/ideas/index.js +3 -0
  61. package/dist/ideas/store.d.ts +55 -0
  62. package/dist/ideas/store.d.ts.map +1 -0
  63. package/dist/ideas/store.js +262 -0
  64. package/dist/ideas/types.d.ts +54 -0
  65. package/dist/ideas/types.d.ts.map +1 -0
  66. package/dist/ideas/types.js +26 -0
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +2 -0
  70. package/dist/init/scaffold.d.ts +4 -0
  71. package/dist/init/scaffold.d.ts.map +1 -1
  72. package/dist/init/scaffold.js +6 -2
  73. package/dist/managed/index.d.ts +73 -0
  74. package/dist/managed/index.d.ts.map +1 -0
  75. package/dist/managed/index.js +222 -0
  76. package/dist/root/index.d.ts +7 -0
  77. package/dist/root/index.d.ts.map +1 -1
  78. package/dist/root/index.js +13 -1
  79. package/dist/update/cli.d.ts +8 -0
  80. package/dist/update/cli.d.ts.map +1 -0
  81. package/dist/update/cli.js +115 -0
  82. package/dist/update/index.d.ts +78 -0
  83. package/dist/update/index.d.ts.map +1 -0
  84. package/dist/update/index.js +285 -0
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/package.json +1 -1
  88. package/templates/default-instance/instruction/.cursorrules +6 -0
  89. package/templates/default-instance/instruction/.windsurfrules +6 -0
  90. package/templates/default-instance/instruction/CLAUDE.md +4 -0
  91. package/templates/default-instance/managed/agent.md +20 -0
  92. package/templates/default-instance/managed/bootstrap.md +7 -0
  93. package/templates/default-instance/managed/index-idea.md +5 -0
  94. package/templates/default-instance/managed/routing-idea.md +14 -0
  95. package/templates/default-instance/monolith/AGENT.md +1 -0
  96. package/templates/default-instance/monolith/context/maps/routing-map.md +4 -0
  97. package/templates/default-instance/monolith/context/maps/semantic-map.md +2 -0
  98. package/templates/default-instance/monolith/skills/INDEX.md +4 -0
  99. package/templates/default-instance/monolith/skills/idea-capture/SKILL.md +157 -0
  100. package/templates/default-instance/projectroot/AGENTS.md +4 -0
@@ -2,6 +2,7 @@
2
2
  import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
3
3
  import { join, dirname, resolve, relative, basename } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
+ import { BLOCK_IDS, blockBody, placeholderFor, renderBlock } from '../managed/index.js';
5
6
  import { SafeWriteError, createFile, openParent, probeTarget, projectBoundary, resolveMonolithRootValue, } from '../root/index.js';
6
7
  const INSTRUCTION_FILE_MAP = {
7
8
  claude: 'CLAUDE.md',
@@ -11,8 +12,8 @@ const INSTRUCTION_FILE_MAP = {
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = dirname(__filename);
13
14
  // Works both from dist/init/ (compiled) and src/init/ (tsx)
14
- const TEMPLATES_ROOT = resolve(__dirname, '..', '..', 'templates', 'default-instance');
15
- function substitute(content, vars) {
15
+ export const TEMPLATES_ROOT = resolve(__dirname, '..', '..', 'templates', 'default-instance');
16
+ export function substitute(content, vars) {
16
17
  return content.replace(/\{\{([A-Z_]+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
17
18
  }
18
19
  function stripTemplateSuffix(p) {
@@ -63,6 +64,8 @@ function buildVars(config) {
63
64
  .split('\n')
64
65
  .join('\n '),
65
66
  DRIFT_LOG_PATH: `${r}/enforcement/drift-log.json`,
67
+ // Same managed blocks `monolith update` writes into existing instances.
68
+ ...Object.fromEntries(BLOCK_IDS.map((id) => [placeholderFor(id), renderBlock(id, blockBody(id, r)).replace(/\n$/, '')])),
66
69
  };
67
70
  }
68
71
  // Relative to config.monolithRoot (`r`). Shared between createEmptyDirs
@@ -71,6 +74,7 @@ function buildVars(config) {
71
74
  const GITKEEP_DIRS = [
72
75
  'memory/working/phases',
73
76
  'memory/execution/traces',
77
+ 'memory/long-term/ideas',
74
78
  'protocols/engineering',
75
79
  'learning/mistakes/architecture',
76
80
  'learning/mistakes/dependencies',
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Managed blocks (DX-UPDATE-003): the one mechanism by which `monolith init`
3
+ * and `monolith update` put MONOLITH-owned text into a file the user also owns
4
+ * (CLAUDE.md, AGENTS.md, routing-map, skills index) or into a MONOLITH-owned
5
+ * file the user may extend (`AGENT.md`).
6
+ *
7
+ * <!-- monolith:begin <id> sha256=<16 hex of the body> -->
8
+ * ...body...
9
+ * <!-- monolith:end <id> -->
10
+ *
11
+ * The checksum is what makes a refresh safe without a stored template version:
12
+ * a block whose body still matches its own checksum was never edited, so it is
13
+ * ours to refresh; a block that does not match carries a user edit and is left
14
+ * alone (CONFLICT). Everything outside the markers is never read for meaning
15
+ * and never changed. Markers inside fenced code blocks are examples, not blocks.
16
+ * This is not a Markdown parser: it knows lines, fences and these markers.
17
+ */
18
+ export declare const BLOCK_IDS: readonly ["bootstrap", "agent", "routing-idea", "index-idea"];
19
+ export type BlockId = (typeof BLOCK_IDS)[number];
20
+ /** `{{MANAGED_<ID>}}` placeholder name used by the init templates. */
21
+ export declare function placeholderFor(id: BlockId): string;
22
+ export declare function bodyHash(body: string): string;
23
+ /** The body of a block for a given instance root. */
24
+ export declare function blockBody(id: BlockId, monolithRoot: string): string;
25
+ /** The block as it is written to a file (markers + body), ending in a newline. */
26
+ export declare function renderBlock(id: BlockId, body: string, eol?: string): string;
27
+ export type BlockState = {
28
+ state: 'absent';
29
+ }
30
+ /** Exactly one well-formed block whose body matches its checksum. `current`: also equals the template. */
31
+ | {
32
+ state: 'pristine';
33
+ current: boolean;
34
+ from: number;
35
+ to: number;
36
+ lines: string[];
37
+ } | {
38
+ state: 'edited';
39
+ detail: string;
40
+ } | {
41
+ state: 'malformed';
42
+ detail: string;
43
+ };
44
+ /** Classify `id` in `text` against the template body `desired`. */
45
+ export declare function inspectBlock(text: string, id: BlockId, desired: string): BlockState;
46
+ /** `text` plus the block, appended; every existing byte stays in place. */
47
+ export declare function appendBlock(text: string, id: BlockId, body: string): string;
48
+ /** `text` with only the lines of the block replaced. */
49
+ export declare function refreshBlock(text: string, state: Extract<BlockState, {
50
+ state: 'pristine';
51
+ }>, id: BlockId, body: string): string;
52
+ export type Planned = {
53
+ ok: true;
54
+ content: string;
55
+ } | {
56
+ ok: false;
57
+ detail: string;
58
+ };
59
+ /**
60
+ * `text` with the block appended, or the reason that is not safe. Safe means the
61
+ * result, read back by `inspectBlock`, shows exactly one well-formed, unedited
62
+ * block that equals the template, and every original byte is still in place.
63
+ * An unclosed code fence or HTML comment in `text` swallows the new markers (the next run would
64
+ * not see the block and append another), so it is refused up front.
65
+ */
66
+ export declare function planAppend(text: string, id: BlockId, body: string): Planned;
67
+ /** `text` with the block refreshed in place, or the reason that is not safe (see `planAppend`). */
68
+ export declare function planRefresh(text: string, state: Extract<BlockState, {
69
+ state: 'pristine';
70
+ }>, id: BlockId, body: string): Planned;
71
+ /** Text with fenced code blocks and HTML comments removed: what an assistant reads as instructions. */
72
+ export declare function prose(text: string): string;
73
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/managed/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,eAAO,MAAM,SAAS,+DAAgE,CAAC;AACvF,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAElD;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAMD,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,SAAO,GAAG,MAAM,CAGzE;AA8FD,MAAM,MAAM,UAAU,GAClB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE;AACrB,0GAA0G;GACxG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAClF;IAAE,KAAK,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3C,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAyBnF;AAMD,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3E;AAED,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/H;AAED,MAAM,MAAM,OAAO,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAW3E;AAED,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAM/H;AAQD,uGAAuG;AACvG,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1C"}
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Managed blocks (DX-UPDATE-003): the one mechanism by which `monolith init`
3
+ * and `monolith update` put MONOLITH-owned text into a file the user also owns
4
+ * (CLAUDE.md, AGENTS.md, routing-map, skills index) or into a MONOLITH-owned
5
+ * file the user may extend (`AGENT.md`).
6
+ *
7
+ * <!-- monolith:begin <id> sha256=<16 hex of the body> -->
8
+ * ...body...
9
+ * <!-- monolith:end <id> -->
10
+ *
11
+ * The checksum is what makes a refresh safe without a stored template version:
12
+ * a block whose body still matches its own checksum was never edited, so it is
13
+ * ours to refresh; a block that does not match carries a user edit and is left
14
+ * alone (CONFLICT). Everything outside the markers is never read for meaning
15
+ * and never changed. Markers inside fenced code blocks are examples, not blocks.
16
+ * This is not a Markdown parser: it knows lines, fences and these markers.
17
+ */
18
+ import { createHash } from 'crypto';
19
+ import { readFileSync } from 'fs';
20
+ import { dirname, join, resolve } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ // Works from src/managed/ and dist/managed/ alike.
24
+ const MANAGED_TEMPLATES = resolve(__dirname, '..', '..', 'templates', 'default-instance', 'managed');
25
+ export const BLOCK_IDS = ['bootstrap', 'agent', 'routing-idea', 'index-idea'];
26
+ /** `{{MANAGED_<ID>}}` placeholder name used by the init templates. */
27
+ export function placeholderFor(id) {
28
+ return `MANAGED_${id.toUpperCase().replace(/-/g, '_')}`;
29
+ }
30
+ export function bodyHash(body) {
31
+ return createHash('sha256').update(normalize(body)).digest('hex').slice(0, 16);
32
+ }
33
+ function normalize(s) {
34
+ return s.replace(/\r\n/g, '\n').replace(/\s+$/, '');
35
+ }
36
+ /** The body of a block for a given instance root. */
37
+ export function blockBody(id, monolithRoot) {
38
+ const raw = readFileSync(join(MANAGED_TEMPLATES, `${id}.md`), 'utf8');
39
+ return normalize(raw.replace(/\{\{MONOLITH_ROOT\}\}/g, monolithRoot));
40
+ }
41
+ /** The block as it is written to a file (markers + body), ending in a newline. */
42
+ export function renderBlock(id, body, eol = '\n') {
43
+ const lines = [`<!-- monolith:begin ${id} sha256=${bodyHash(body)} -->`, ...normalize(body).split('\n'), `<!-- monolith:end ${id} -->`];
44
+ return lines.join(eol) + eol;
45
+ }
46
+ const MARKER = /^<!-- monolith:(begin|end) ([a-z-]+)(?: sha256=([0-9a-f]{16}))? -->$/;
47
+ const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
48
+ const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
49
+ function splitLines(text) {
50
+ return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
51
+ }
52
+ /** A leading UTF-8 BOM is not part of the first line's Markdown. */
53
+ const stripBom = (l, i) => (i === 0 ? l.replace(/^\uFEFF/, '') : l);
54
+ /**
55
+ * Per line: is it a fence line or inside a fenced code block? Same rules as
56
+ * CommonMark for the cases that matter here: a fence is 3+ backticks or tildes
57
+ * (at most 3 spaces of indent), it closes on the same character with at least
58
+ * the same length and nothing after it, and a block left open runs to the end
59
+ * of the file. `open` tells whether the text ends inside such a block.
60
+ */
61
+ function scanFences(lines) {
62
+ const hidden = [];
63
+ let fence = null;
64
+ for (const [i, l] of lines.entries()) {
65
+ const t = stripBom(l, i).replace(/\r?\n$/, '');
66
+ if (fence === null) {
67
+ const o = FENCE_OPEN.exec(t);
68
+ if (o && !(o[1][0] === '`' && o[2].includes('`'))) {
69
+ fence = { ch: o[1][0], len: o[1].length };
70
+ hidden.push(true);
71
+ }
72
+ else
73
+ hidden.push(false);
74
+ }
75
+ else {
76
+ const c = FENCE_CLOSE.exec(t);
77
+ if (c && c[1][0] === fence.ch && c[1].length >= fence.len)
78
+ fence = null;
79
+ hidden.push(true);
80
+ }
81
+ }
82
+ return { hidden, open: fence !== null };
83
+ }
84
+ /**
85
+ * Per line (outside fences): does it start inside an HTML comment? `open` tells
86
+ * whether the text ends inside one. A line that is exactly a marker opens and
87
+ * closes its own comment and does not change the state. Deliberately naive: a
88
+ * `<!--` in inline code counts as a comment, which can only make us refuse.
89
+ */
90
+ function scanComments(lines, hidden) {
91
+ const inside = [];
92
+ let open = false;
93
+ lines.forEach((l, i) => {
94
+ inside.push(open);
95
+ if (hidden[i])
96
+ return;
97
+ const t = stripBom(l, i).replace(/\r?\n$/, '');
98
+ if (!open && MARKER.test(t.trim()))
99
+ return;
100
+ let at = 0;
101
+ for (;;) {
102
+ if (open) {
103
+ const end = t.indexOf('-->', at);
104
+ if (end < 0)
105
+ break;
106
+ open = false;
107
+ at = end + 3;
108
+ }
109
+ else {
110
+ const start = t.indexOf('<!--', at);
111
+ if (start < 0)
112
+ break;
113
+ open = true;
114
+ at = start + 4;
115
+ }
116
+ }
117
+ });
118
+ return { inside, open };
119
+ }
120
+ function markers(lines) {
121
+ const out = [];
122
+ const { hidden } = scanFences(lines);
123
+ const { inside } = scanComments(lines, hidden);
124
+ lines.forEach((l, i) => {
125
+ if (hidden[i])
126
+ return;
127
+ const t = l.replace(/\r?\n$/, '');
128
+ const m = MARKER.exec(t.trim());
129
+ if (m)
130
+ out.push({ kind: m[1], id: m[2], hash: m[3], line: i, commented: inside[i] });
131
+ });
132
+ return out;
133
+ }
134
+ /** Classify `id` in `text` against the template body `desired`. */
135
+ export function inspectBlock(text, id, desired) {
136
+ const lines = splitLines(text);
137
+ const all = markers(lines);
138
+ const mine = all.filter((m) => m.id === id);
139
+ if (mine.length === 0)
140
+ return { state: 'absent' };
141
+ if (mine.some((m) => m.commented)) {
142
+ return { state: 'malformed', detail: `a "${id}" marker sits inside an HTML comment, so the block is not active` };
143
+ }
144
+ const begins = mine.filter((m) => m.kind === 'begin');
145
+ const ends = mine.filter((m) => m.kind === 'end');
146
+ if (begins.length !== 1 || ends.length !== 1) {
147
+ return { state: 'malformed', detail: `${begins.length} begin and ${ends.length} end marker(s) for "${id}"; exactly one of each is required` };
148
+ }
149
+ const [b] = begins;
150
+ const [e] = ends;
151
+ if (e.line < b.line)
152
+ return { state: 'malformed', detail: `the "${id}" end marker precedes its begin marker` };
153
+ if (all.some((m) => m !== b && m !== e && m.line > b.line && m.line < e.line)) {
154
+ return { state: 'malformed', detail: `another managed marker sits inside the "${id}" block` };
155
+ }
156
+ if (b.hash === undefined)
157
+ return { state: 'malformed', detail: `the "${id}" begin marker has no checksum` };
158
+ const body = lines.slice(b.line + 1, e.line).join('');
159
+ if (bodyHash(body) !== b.hash) {
160
+ return { state: 'edited', detail: `the text inside the "${id}" block was edited after MONOLITH wrote it` };
161
+ }
162
+ return { state: 'pristine', current: normalize(body) === normalize(desired), from: b.line, to: e.line, lines };
163
+ }
164
+ function eolOf(text) {
165
+ return text.includes('\r\n') ? '\r\n' : '\n';
166
+ }
167
+ /** `text` plus the block, appended; every existing byte stays in place. */
168
+ export function appendBlock(text, id, body) {
169
+ const eol = eolOf(text);
170
+ const sep = text.length === 0 ? '' : text.endsWith('\n') ? eol : eol + eol;
171
+ return text + sep + renderBlock(id, body, eol);
172
+ }
173
+ /** `text` with only the lines of the block replaced. */
174
+ export function refreshBlock(text, state, id, body) {
175
+ const eol = eolOf(text);
176
+ const rendered = renderBlock(id, body, eol);
177
+ // The end-marker line may be the unterminated last line of the file: keep it that way.
178
+ const unterminated = !/\n$/.test(state.lines[state.to]);
179
+ const block = unterminated ? rendered.slice(0, -eol.length) : rendered;
180
+ return state.lines.slice(0, state.from).join('') + block + state.lines.slice(state.to + 1).join('');
181
+ }
182
+ /**
183
+ * `text` with the block appended, or the reason that is not safe. Safe means the
184
+ * result, read back by `inspectBlock`, shows exactly one well-formed, unedited
185
+ * block that equals the template, and every original byte is still in place.
186
+ * An unclosed code fence or HTML comment in `text` swallows the new markers (the next run would
187
+ * not see the block and append another), so it is refused up front.
188
+ */
189
+ export function planAppend(text, id, body) {
190
+ if (scanFences(splitLines(text)).open) {
191
+ return { ok: false, detail: 'the file ends inside an unclosed fenced code block (``` or ~~~), which would hide the new block' };
192
+ }
193
+ const lines = splitLines(text);
194
+ if (scanComments(lines, scanFences(lines).hidden).open) {
195
+ return { ok: false, detail: 'the file ends inside an unclosed HTML comment (<!--), which would hide the new block' };
196
+ }
197
+ const content = appendBlock(text, id, body);
198
+ if (!content.startsWith(text))
199
+ return { ok: false, detail: 'the append would not keep the existing bytes in place' };
200
+ return verified(content, id, body);
201
+ }
202
+ /** `text` with the block refreshed in place, or the reason that is not safe (see `planAppend`). */
203
+ export function planRefresh(text, state, id, body) {
204
+ const content = refreshBlock(text, state, id, body);
205
+ const head = state.lines.slice(0, state.from).join('');
206
+ const tail = state.lines.slice(state.to + 1).join('');
207
+ if (!content.startsWith(head) || !content.endsWith(tail))
208
+ return { ok: false, detail: 'the refresh would change text outside the block' };
209
+ return verified(content, id, body);
210
+ }
211
+ function verified(content, id, body) {
212
+ const st = inspectBlock(content, id, body);
213
+ if (st.state === 'pristine' && st.current)
214
+ return { ok: true, content };
215
+ return { ok: false, detail: `the written block would not be recognised as current (${st.state === 'pristine' ? 'stale' : st.state})` };
216
+ }
217
+ /** Text with fenced code blocks and HTML comments removed: what an assistant reads as instructions. */
218
+ export function prose(text) {
219
+ const lines = splitLines(text);
220
+ const { hidden } = scanFences(lines);
221
+ return lines.filter((_, i) => !hidden[i]).join('').replace(/<!--[\s\S]*?-->/g, '');
222
+ }
@@ -91,6 +91,13 @@ export interface ProjectFileOptions {
91
91
  maxBytes?: number;
92
92
  /** Accept a zero-byte file. Defaults to false. */
93
93
  allowEmpty?: boolean;
94
+ /**
95
+ * Refuse (FILE_UNREADABLE) content that is not strictly valid UTF-8 or that
96
+ * contains a NUL byte (UTF-16 without BOM), instead of replacing bad bytes
97
+ * with U+FFFD. For callers that write the text back: a lossy read would
98
+ * corrupt the file. A UTF-8 BOM is kept in the string. Defaults to false.
99
+ */
100
+ strictUtf8?: boolean;
94
101
  }
95
102
  /**
96
103
  * Options of the unified reader. `boundary` is mandatory and explicit: an
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/root/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/root/index.ts"],"names":[],"mappings":"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"}
@@ -348,7 +348,19 @@ export function readBoundedFile(abs, label, opts, wantContent) {
348
348
  return fail('FILE_TOO_LARGE', `${label} grew beyond the limit of ${maxBytes} bytes while being read.`);
349
349
  chunks.push(buf.subarray(0, n));
350
350
  }
351
- return { ok: true, path: abs, size: total, content: Buffer.concat(chunks).toString('utf8') };
351
+ const bytes = Buffer.concat(chunks);
352
+ if (opts.strictUtf8) {
353
+ try {
354
+ if (bytes.includes(0))
355
+ throw new Error('NUL byte');
356
+ const content = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
357
+ return { ok: true, path: abs, size: total, content };
358
+ }
359
+ catch {
360
+ return fail('FILE_UNREADABLE', `${label} is not valid UTF-8 text (another encoding, UTF-16 or binary); MONOLITH does not convert encodings.`);
361
+ }
362
+ }
363
+ return { ok: true, path: abs, size: total, content: bytes.toString('utf8') };
352
364
  }
353
365
  catch (err) {
354
366
  return fail('FILE_UNREADABLE', `${label} cannot be read: ${errMsg(err)}`);
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `monolith update [--apply]` handler (DX-UPDATE-001). Preview is read-only.
3
+ * Exit codes: 0 ok (preview, or apply with integration complete), 1 error,
4
+ * 2 apply finished but manual actions remain (integration incomplete).
5
+ */
6
+ export declare const UPDATE_USAGE = "Usage: monolith update [--apply]\n\nBrings an existing MONOLITH instance up to the current agent integration: a\nstable entry point (<root>/AGENT.md), the idea-capture skill, its routing and the\npointer in your AI instruction files.\n (no flag) Preview: shows the plan, writes nothing.\n --apply Performs the plan: ADD creates a missing MONOLITH file, UPDATE adds\n or refreshes a managed block inside a file of yours.\n\nWhat is written, and what is not:\n - MONOLITH text goes only into checksummed managed blocks\n (<!-- monolith:begin ... -->). Appending leaves every existing byte in place;\n refreshing replaces only an unedited block.\n - A block you edited, a malformed block, a clashing section of yours, an\n unclosed code fence (backticks or tildes) or HTML comment that would hide the block, a file that is not\n valid UTF-8 (no encoding is converted) or an unreadable file is CONFLICT: nothing is written there and the plan says how\n to resolve it. SKIP means left alone on purpose (e.g. your own skill).\n - Your existing skills, memory, state.json, ideas and text are never overwritten.\n Nothing is executed.\nInstances carry no template version, so state is read from the files. Safe to\nre-run. Not transactional and not coordinated between simultaneous runs: apply\nre-checks each file just before writing and skips one that changed. Exit code 2\nafter --apply means the integration is not complete (conflicts or skipped\nchanges remain).";
7
+ export declare function runUpdate(args: string[], targetDir?: string): Promise<number>;
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/update/cli.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAKH,eAAO,MAAM,YAAY,+9CAuBR,CAAC;AA6ClB,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,SAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoC1F"}
@@ -0,0 +1,115 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * `monolith update [--apply]` handler (DX-UPDATE-001). Preview is read-only.
4
+ * Exit codes: 0 ok (preview, or apply with integration complete), 1 error,
5
+ * 2 apply finished but manual actions remain (integration incomplete).
6
+ */
7
+ import { reportWriteWarning } from '../root/index.js';
8
+ import { applyPlan, integrationComplete, planUpdate } from './index.js';
9
+ export const UPDATE_USAGE = `Usage: monolith update [--apply]
10
+
11
+ Brings an existing MONOLITH instance up to the current agent integration: a
12
+ stable entry point (<root>/AGENT.md), the idea-capture skill, its routing and the
13
+ pointer in your AI instruction files.
14
+ (no flag) Preview: shows the plan, writes nothing.
15
+ --apply Performs the plan: ADD creates a missing MONOLITH file, UPDATE adds
16
+ or refreshes a managed block inside a file of yours.
17
+
18
+ What is written, and what is not:
19
+ - MONOLITH text goes only into checksummed managed blocks
20
+ (<!-- monolith:begin ... -->). Appending leaves every existing byte in place;
21
+ refreshing replaces only an unedited block.
22
+ - A block you edited, a malformed block, a clashing section of yours, an
23
+ unclosed code fence (backticks or tildes) or HTML comment that would hide the block, a file that is not
24
+ valid UTF-8 (no encoding is converted) or an unreadable file is CONFLICT: nothing is written there and the plan says how
25
+ to resolve it. SKIP means left alone on purpose (e.g. your own skill).
26
+ - Your existing skills, memory, state.json, ideas and text are never overwritten.
27
+ Nothing is executed.
28
+ Instances carry no template version, so state is read from the files. Safe to
29
+ re-run. Not transactional and not coordinated between simultaneous runs: apply
30
+ re-checks each file just before writing and skips one that changed. Exit code 2
31
+ after --apply means the integration is not complete (conflicts or skipped
32
+ changes remain).`;
33
+ function printable(s) {
34
+ // eslint-disable-next-line no-control-regex
35
+ return s.replace(/[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
36
+ }
37
+ function printPlan(plan) {
38
+ const width = Math.max(...plan.items.map((i) => i.action.length));
39
+ for (const i of plan.items)
40
+ console.log(` ${i.action.padEnd(width)} ${printable(i.path)}\n ${' '.repeat(width)} ${i.reason}`);
41
+ }
42
+ function printEffects(items) {
43
+ const todo = items.filter((i) => i.action === 'ADD' || i.action === 'UPDATE');
44
+ if (todo.length === 0)
45
+ return;
46
+ console.log('\nWhy this is safe: ADD only creates files that do not exist; UPDATE only adds or refreshes a');
47
+ console.log('managed block and is checked to leave all other text unchanged. Nothing else is written.');
48
+ console.log('After --apply:');
49
+ for (const i of todo)
50
+ console.log(` - ${printable(i.path)}: ${i.effect ?? ''}`);
51
+ }
52
+ function printProposals(items) {
53
+ for (const i of items.filter((x) => x.action === 'CONFLICT' && x.proposal)) {
54
+ console.log(`\n--- Manual fallback for ${printable(i.path)}: add this block yourself ---\n${i.proposal}--- end ---`);
55
+ }
56
+ }
57
+ function printChain(plan) {
58
+ console.log('\nConsistency (instructions → routing → skill → CLI → storage):');
59
+ for (const c of plan.chain)
60
+ console.log(` ${c.ok ? 'ok ' : 'FAIL'} ${c.link}: ${printable(c.detail)}`);
61
+ }
62
+ function verdict(plan, stale = []) {
63
+ const pending = plan.items.filter((i) => i.action === 'ADD' || i.action === 'UPDATE').length;
64
+ const manual = plan.items.filter((i) => i.action === 'CONFLICT').length;
65
+ const broken = plan.chain.filter((c) => !c.ok).length;
66
+ if (pending === 0 && manual === 0 && broken === 0 && stale.length === 0)
67
+ return 'Idea Capture integration: COMPLETE (the managed components are installed and pass the structural check; how an AI assistant behaves is not verified).';
68
+ const parts = [];
69
+ if (pending > 0)
70
+ parts.push(`${pending} change(s) to apply`);
71
+ if (manual > 0)
72
+ parts.push(`${manual} manual action(s)`);
73
+ if (stale.length > 0)
74
+ parts.push(`${stale.length} change(s) skipped because the file changed meanwhile`);
75
+ if (broken > 0 && parts.length === 0)
76
+ parts.push(`${broken} consistency check(s) failing`);
77
+ return `Idea Capture integration: INCOMPLETE (${parts.join(', ')}). A partly installed integration is not active.`;
78
+ }
79
+ export async function runUpdate(args, targetDir = process.cwd()) {
80
+ let apply = false;
81
+ for (const a of args) {
82
+ if (a === '--apply')
83
+ apply = true;
84
+ else {
85
+ console.error(`Unexpected argument ${JSON.stringify(printable(a))}.\n${UPDATE_USAGE}`);
86
+ return 1;
87
+ }
88
+ }
89
+ const plan = planUpdate(targetDir);
90
+ console.log(`monolith update — ${apply ? 'apply' : 'preview (nothing is written)'}`);
91
+ console.log(`Instance root: ${plan.monolithRoot}/\n`);
92
+ printPlan(plan);
93
+ if (!apply) {
94
+ printEffects(plan.items);
95
+ printProposals(plan.items);
96
+ printChain(plan);
97
+ console.log(`\n${verdict(plan)}`);
98
+ if (plan.items.some((i) => i.action === 'ADD' || i.action === 'UPDATE'))
99
+ console.log('Run `monolith update --apply` to perform the ADD / UPDATE items.');
100
+ return 0;
101
+ }
102
+ const result = applyPlan(targetDir, plan);
103
+ for (const w of result.warnings)
104
+ reportWriteWarning(w);
105
+ const list = (xs) => (xs.length ? ': ' + xs.map(printable).join(', ') : '');
106
+ console.log(`\nCreated ${result.created.length} file(s)${list(result.created)}.`);
107
+ console.log(`Updated ${result.updated.length} file(s)${list(result.updated)}.`);
108
+ if (result.stale.length > 0)
109
+ console.log(`Skipped (changed since planning)${list(result.stale)}. Re-run to plan again.`);
110
+ const after = planUpdate(targetDir);
111
+ printProposals(after.items);
112
+ printChain(after);
113
+ console.log(`\n${verdict(after, result.stale)}`);
114
+ return integrationComplete(after) && result.stale.length === 0 ? 0 : 2;
115
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `monolith update` (DX-UPDATE-001/003): bring an existing instance up to the
3
+ * current agent integration (stable `AGENT.md` entry point, Idea Capture)
4
+ * without touching project memory.
5
+ *
6
+ * Ownership model (no template version is stored in an instance, so state is
7
+ * read from the files themselves):
8
+ * - MONOLITH-owned, created when absent: `<root>/AGENT.md`, the `idea-capture`
9
+ * skill. An existing skill is never overwritten, whatever it contains.
10
+ * - Mixed (user-owned file + MONOLITH text): CLAUDE.md / AGENTS.md /
11
+ * .cursorrules / .windsurfrules, routing-map, skills index. MONOLITH text
12
+ * lives only in checksummed managed blocks (src/managed). A missing block is
13
+ * appended; an unedited block is refreshed in place; an edited, malformed or
14
+ * competing one is a CONFLICT and nothing is written. Bytes outside a block
15
+ * are never changed (asserted before every write).
16
+ * - User memory (state.json, memory/, ideas): never touched.
17
+ *
18
+ * Reads use the bounded safe read; writes use the safe-write primitives
19
+ * (project boundary, no symlinks, exclusive create / atomic replace). Update is
20
+ * not transactional and not coordinated between processes (SEC-001B.3): apply
21
+ * re-plans just before writing and refuses a change whose preconditions moved;
22
+ * a re-run is the recovery. Nothing is executed and no user text is interpreted
23
+ * as code.
24
+ */
25
+ import { RootContainmentError, type WriteWarning } from '../root/index.js';
26
+ export type UpdateAction = 'ADD' | 'UPDATE' | 'UNCHANGED' | 'CONFLICT' | 'SKIP';
27
+ export interface UpdateItem {
28
+ /** Path relative to the project root, `/`-separated. */
29
+ path: string;
30
+ action: UpdateAction;
31
+ /** Why, in one line. */
32
+ reason: string;
33
+ /** For CONFLICT: how to resolve it by hand. */
34
+ proposal?: string;
35
+ /** For ADD / UPDATE: the complete file content to write. */
36
+ content?: string;
37
+ /** For UPDATE: sha256 of the file as planned against; apply refuses if it moved. */
38
+ before?: string;
39
+ /** For ADD / UPDATE: what it makes available. */
40
+ effect?: string;
41
+ }
42
+ /** One link of Agent Instructions → Routing → Skill → CLI → Storage. */
43
+ export interface ChainCheck {
44
+ link: string;
45
+ ok: boolean;
46
+ detail: string;
47
+ }
48
+ export interface UpdatePlan {
49
+ monolithRoot: string;
50
+ items: UpdateItem[];
51
+ chain: ChainCheck[];
52
+ }
53
+ /**
54
+ * Read-only. Throws RootContainmentError when the root/config is unusable and
55
+ * `Error` when the directory is not a MONOLITH instance.
56
+ */
57
+ export declare function planUpdate(target: string): UpdatePlan;
58
+ /**
59
+ * "Complete" is a structural statement: every managed component is installed,
60
+ * unedited-or-current, and the static chain checks out. It says nothing about
61
+ * what an AI model will do with the text. Integration is complete only when nothing is left to add, change or resolve AND every link of the chain checks out. */
62
+ export declare function integrationComplete(plan: UpdatePlan): boolean;
63
+ export interface ApplyResult {
64
+ created: string[];
65
+ updated: string[];
66
+ /** Planned changes not applied because the file changed since planning (or could not be written). */
67
+ stale: string[];
68
+ warnings: WriteWarning[];
69
+ }
70
+ /**
71
+ * Write every ADD and UPDATE of `plan`. The tree is re-planned first and an
72
+ * item is applied only if the file is exactly as planned: an `ADD` target that
73
+ * appeared is skipped, an `UPDATE` whose file changed is reported as stale and
74
+ * left alone. Stops at the first write failure (earlier files stay; re-run).
75
+ */
76
+ export declare function applyPlan(target: string, plan: UpdatePlan): ApplyResult;
77
+ export { RootContainmentError };
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/update/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAEL,oBAAoB,EAQpB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAC;AAK1B,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;AAEhF,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AA+ED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAgGrD;AAiCD;;;iKAGiK;AACjK,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAE7D;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,qGAAqG;IACrG,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAUD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CAsCvE;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}