@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.
- package/README.md +63 -16
- package/dist/brief/collect.d.ts.map +1 -1
- package/dist/brief/collect.js +41 -1
- package/dist/brief/constants.d.ts +2 -0
- package/dist/brief/constants.d.ts.map +1 -1
- package/dist/brief/constants.js +2 -0
- package/dist/brief/render.d.ts +7 -0
- package/dist/brief/render.d.ts.map +1 -1
- package/dist/brief/render.js +52 -1
- package/dist/brief/types.d.ts +17 -1
- package/dist/brief/types.d.ts.map +1 -1
- package/dist/cli/index.js +33 -0
- package/dist/discovery/compose.d.ts +28 -0
- package/dist/discovery/compose.d.ts.map +1 -0
- package/dist/discovery/compose.js +159 -0
- package/dist/discovery/contracts.d.ts +211 -0
- package/dist/discovery/contracts.d.ts.map +1 -0
- package/dist/discovery/contracts.js +555 -0
- package/dist/discovery/docs.d.ts +278 -0
- package/dist/discovery/docs.d.ts.map +1 -0
- package/dist/discovery/docs.js +1182 -0
- package/dist/discovery/facts.d.ts +49 -0
- package/dist/discovery/facts.d.ts.map +1 -0
- package/dist/discovery/facts.js +541 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +9 -0
- package/dist/discovery/orchestrate.d.ts +35 -0
- package/dist/discovery/orchestrate.d.ts.map +1 -0
- package/dist/discovery/orchestrate.js +32 -0
- package/dist/discovery/render.d.ts +36 -0
- package/dist/discovery/render.d.ts.map +1 -0
- package/dist/discovery/render.js +278 -0
- package/dist/discovery/traverse.d.ts +197 -0
- package/dist/discovery/traverse.d.ts.map +1 -0
- package/dist/discovery/traverse.js +510 -0
- package/dist/durable/errors.d.ts +6 -2
- package/dist/durable/errors.d.ts.map +1 -1
- package/dist/durable/errors.js +9 -3
- package/dist/durable/fs-store.d.ts +21 -0
- package/dist/durable/fs-store.d.ts.map +1 -1
- package/dist/durable/fs-store.js +103 -3
- package/dist/durable/prune-check.d.ts +17 -0
- package/dist/durable/prune-check.d.ts.map +1 -1
- package/dist/durable/prune-check.js +27 -0
- package/dist/durable/runtime.d.ts +5 -3
- package/dist/durable/runtime.d.ts.map +1 -1
- package/dist/durable/runtime.js +23 -6
- package/dist/durable/seams.d.ts +4 -0
- package/dist/durable/seams.d.ts.map +1 -1
- package/dist/durable/testing.js +2 -2
- package/dist/ideas/cli.d.ts +33 -0
- package/dist/ideas/cli.d.ts.map +1 -0
- package/dist/ideas/cli.js +158 -0
- package/dist/ideas/format.d.ts +38 -0
- package/dist/ideas/format.d.ts.map +1 -0
- package/dist/ideas/format.js +194 -0
- package/dist/ideas/index.d.ts +6 -0
- package/dist/ideas/index.d.ts.map +1 -0
- package/dist/ideas/index.js +3 -0
- package/dist/ideas/store.d.ts +55 -0
- package/dist/ideas/store.d.ts.map +1 -0
- package/dist/ideas/store.js +262 -0
- package/dist/ideas/types.d.ts +54 -0
- package/dist/ideas/types.d.ts.map +1 -0
- package/dist/ideas/types.js +26 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/init/scaffold.d.ts +4 -0
- package/dist/init/scaffold.d.ts.map +1 -1
- package/dist/init/scaffold.js +6 -2
- package/dist/managed/index.d.ts +73 -0
- package/dist/managed/index.d.ts.map +1 -0
- package/dist/managed/index.js +222 -0
- package/dist/root/index.d.ts +7 -0
- package/dist/root/index.d.ts.map +1 -1
- package/dist/root/index.js +13 -1
- package/dist/update/cli.d.ts +8 -0
- package/dist/update/cli.d.ts.map +1 -0
- package/dist/update/cli.js +115 -0
- package/dist/update/index.d.ts +78 -0
- package/dist/update/index.d.ts.map +1 -0
- package/dist/update/index.js +285 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/templates/default-instance/instruction/.cursorrules +6 -0
- package/templates/default-instance/instruction/.windsurfrules +6 -0
- package/templates/default-instance/instruction/CLAUDE.md +4 -0
- package/templates/default-instance/managed/agent.md +20 -0
- package/templates/default-instance/managed/bootstrap.md +7 -0
- package/templates/default-instance/managed/index-idea.md +5 -0
- package/templates/default-instance/managed/routing-idea.md +14 -0
- package/templates/default-instance/monolith/AGENT.md +1 -0
- package/templates/default-instance/monolith/context/maps/routing-map.md +4 -0
- package/templates/default-instance/monolith/context/maps/semantic-map.md +2 -0
- package/templates/default-instance/monolith/skills/INDEX.md +4 -0
- package/templates/default-instance/monolith/skills/idea-capture/SKILL.md +157 -0
- package/templates/default-instance/projectroot/AGENTS.md +4 -0
package/README.md
CHANGED
|
@@ -29,34 +29,64 @@ decisions — instead of relying on an assistant's unaided memory.
|
|
|
29
29
|
|
|
30
30
|
## Why MONOLITH?
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
You are deep in a task when a good idea arrives — one that has nothing to do
|
|
33
|
+
with what you are doing right now. Switching to it would break your focus, so
|
|
34
|
+
you postpone it. A few hours later it is buried in a long chat, or simply gone.
|
|
35
|
+
|
|
36
|
+
A conversation history is a poor place to keep it. It is tied to one session,
|
|
37
|
+
it is hard to search, and an AI assistant that starts a new session does not
|
|
38
|
+
carry it over. A thought that only lives in a chat lasts as long as the chat.
|
|
39
|
+
|
|
40
|
+
MONOLITH keeps that knowledge in the project itself: ideas, structured project
|
|
41
|
+
context and recorded decisions, as plain Markdown that both a person and an
|
|
42
|
+
assistant can read, check and commit. You save the idea, and keep working.
|
|
43
|
+
|
|
44
|
+
> **Your AI assistant may forget the conversation. Your project shouldn't forget the ideas.**
|
|
45
|
+
|
|
46
|
+
Ideas are where MONOLITH started, not where it ends: the same approach — durable,
|
|
47
|
+
inspectable, deterministic — also covers project state and execution governance.
|
|
36
48
|
|
|
37
49
|
## What MONOLITH Does
|
|
38
50
|
|
|
51
|
+
- **Idea Capture** — save a thought that is unrelated to the current task, without losing focus. You decide what to save; ideas are stored as one Markdown file each in `.monolith/memory/long-term/ideas/`, independent of the active story, and are managed with `monolith idea add|list|show` or by an assistant that follows your project's instructions.
|
|
39
52
|
- **Observable** — `monolith brief` summarizes project state in the terminal, including whether it looks stale; Mission Control shows it in a local, read-only web interface.
|
|
40
53
|
- **Structured** — structure is extracted from a project's own markdown (frontmatter, headings, explicit links) into an explicit relationship graph. No embeddings, no LLM, no fuzzy matching.
|
|
41
54
|
- **Governed** — protocols, skills and cognitive modes describe how an assistant reads and updates project memory; missions and checkpoints record progress. This is a recorded discipline, not a runtime gate.
|
|
42
55
|
- **Reproducible** — same input, same output. State lives in a schema-versioned file written with a lock and atomic replace; Durable State adds a hash-chained event log.
|
|
43
56
|
- **Bounded** — managed files are read and written through strict primitives that refuse symlinks; `monolith validate` never executes project code; Mission Control has no write endpoint.
|
|
44
57
|
|
|
58
|
+
## Idea Capture Example
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
monolith idea add \
|
|
62
|
+
--title 'Automatic Tool Tests' \
|
|
63
|
+
--description 'Generate tests from business tool contracts'
|
|
64
|
+
|
|
65
|
+
monolith idea list
|
|
66
|
+
monolith idea show IDEA-001
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`IDEA-001` is a demonstration ID; the real ID is printed by `idea add`, and
|
|
70
|
+
`idea list` shows every saved idea.
|
|
71
|
+
|
|
72
|
+
With an AI assistant, you can simply say: *"Save this idea for later."* The
|
|
73
|
+
assistant then saves it by running `monolith idea add` — it does not edit idea
|
|
74
|
+
files itself. This works because `monolith init` installs instructions and an
|
|
75
|
+
`idea-capture` skill for the assistant; it is not guaranteed for an arbitrary
|
|
76
|
+
model without that setup. Details and limits: [`monolith idea` in the CLI reference](./docs/reference/cli.md#monolith-idea).
|
|
77
|
+
|
|
78
|
+
Ideas are saved and recalled **manually**. There is no automatic idea detection,
|
|
79
|
+
no automatic recall or reminders, no idea status management and no idea UI yet.
|
|
80
|
+
|
|
45
81
|
## Quick Start
|
|
46
82
|
|
|
47
|
-
Requires Node.js >= 20.
|
|
48
|
-
to npm**. Build it from a clone:
|
|
83
|
+
Requires Node.js >= 20. Install the published alpha package from npm:
|
|
49
84
|
|
|
50
85
|
```bash
|
|
51
|
-
|
|
52
|
-
cd the_monolith
|
|
53
|
-
pnpm install
|
|
54
|
-
pnpm build
|
|
55
|
-
npm link # exposes the `monolith` command (or run: node dist/cli/index.js)
|
|
86
|
+
npm install -g @latysh-development/monolith@alpha
|
|
56
87
|
```
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
becomes the primary install path ([release notes](./docs/release/alpha-installation.md)).
|
|
89
|
+
To build from source instead, see the [installation and release guide](./docs/release/alpha-installation.md).
|
|
60
90
|
|
|
61
91
|
Then, in a project directory:
|
|
62
92
|
|
|
@@ -71,6 +101,23 @@ monolith ui # Mission Control at http://127.0.0.1:4173 ; Ctrl+C to stop
|
|
|
71
101
|
instance, and it does not open a browser — open the printed address yourself.
|
|
72
102
|
All commands: [CLI reference](./docs/reference/cli.md).
|
|
73
103
|
|
|
104
|
+
## Updating an Existing Project
|
|
105
|
+
|
|
106
|
+
If a project was initialised with an earlier MONOLITH version, bring it up to the current agent integration (agent entry point and Idea Capture) with:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
monolith update # preview: shows the plan, writes nothing
|
|
110
|
+
monolith update --apply # applies the safe changes
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- The first command only prints a plan and does not modify the project.
|
|
114
|
+
- The second applies the safe changes (missing MONOLITH-owned files and managed blocks).
|
|
115
|
+
- Your project memory (`active.md`, `decisions.md`, roadmap, stories, ideas) and your own files are preserved.
|
|
116
|
+
- A managed block you have edited is never overwritten automatically.
|
|
117
|
+
- On a conflict the command writes nothing to that file and reports that manual resolution is required.
|
|
118
|
+
|
|
119
|
+
Details and the full list of verdicts: [`monolith update` in the CLI reference](./docs/reference/cli.md#monolith-update).
|
|
120
|
+
|
|
74
121
|
## Architecture
|
|
75
122
|
|
|
76
123
|
```text
|
|
@@ -104,7 +151,7 @@ listing — not an official partnership, endorsement or certification.
|
|
|
104
151
|
|
|
105
152
|
## Documentation
|
|
106
153
|
|
|
107
|
-
- [CLI reference](./docs/reference/cli.md) — every command, `init`, `validate`, `brief`, `mission`, `durable`, `ui`
|
|
154
|
+
- [CLI reference](./docs/reference/cli.md) — every command, `init`, `validate`, `brief`, `idea`, `mission`, `durable`, `ui`
|
|
108
155
|
- [Mission Control](./docs/reference/mission-control.md) — sections, LIVE/DEMO, skins, limitations
|
|
109
156
|
- [Security & Limitations](./docs/reference/security-and-limitations.md) — threat model, platform and filesystem caveats
|
|
110
157
|
- [Programmatic API](./docs/reference/write-scope-api.md) · [Development](./docs/reference/development.md)
|
|
@@ -113,8 +160,8 @@ listing — not an official partnership, endorsement or certification.
|
|
|
113
160
|
|
|
114
161
|
## Project Status
|
|
115
162
|
|
|
116
|
-
**Alpha** (`0.1.0-alpha.
|
|
117
|
-
without notice.
|
|
163
|
+
**Alpha** (`0.1.0-alpha.1`). The API and CLI are experimental and may change
|
|
164
|
+
without notice. Published to npm under the alpha dist-tag. Durable State and the Coordinatorium
|
|
118
165
|
(`monolith durable`, `monolith mission`) are experimental and are not part of
|
|
119
166
|
the Quick Start.
|
|
120
167
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/brief/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwBH,OAAO,KAAK,EACV,UAAU,
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/brief/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAwBH,OAAO,KAAK,EACV,UAAU,EAOX,MAAM,YAAY,CAAC;AAiBpB,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,QAAa,CAAC;AA8TnD,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAqEpE"}
|
package/dist/brief/collect.js
CHANGED
|
@@ -18,9 +18,11 @@ import { discoverDocs } from './docs.js';
|
|
|
18
18
|
import { extractCurrentStory } from './story.js';
|
|
19
19
|
import { selectActiveMissions, selectRecentFinishedMissions, selectLatestCheckpoint, hasCheckpointOrderMismatch, } from './selectors.js';
|
|
20
20
|
import { stateFilePath, validateState, createInitialState, MAX_STATE_BYTES } from '../coordinatorium/index.js';
|
|
21
|
-
import { ACTIVE_MD_REL, PROTOCOLS_DIR, ROADMAP_INDEX_REL, STORY_INDEX_REL, HANDOFF_REL } from './constants.js';
|
|
21
|
+
import { ACTIVE_MD_REL, PARKED_IDEAS_LIMIT, PROTOCOLS_DIR, ROADMAP_INDEX_REL, STORY_INDEX_REL, HANDOFF_REL } from './constants.js';
|
|
22
22
|
import { evaluateFreshness, parseActiveStoryClaim, parseRoadmapActiveStory, parseRoadmapStatuses, parseStoryIndexStatuses, } from './freshness.js';
|
|
23
23
|
import { readGitSignals } from './git-signals.js';
|
|
24
|
+
import { ideaStore, listIdeas } from '../ideas/index.js';
|
|
25
|
+
import { ideaNumber } from '../ideas/format.js';
|
|
24
26
|
const RECENT_FINISHED_MISSIONS_LIMIT = 5;
|
|
25
27
|
/** SEC-001B.1: `active.md` is a short hand-written context file (~3 KB today). */
|
|
26
28
|
export const MAX_ACTIVE_CONTEXT_BYTES = 256 * 1024;
|
|
@@ -211,6 +213,42 @@ function collectStory(targetDir, monolithRoot, root, diagnostics) {
|
|
|
211
213
|
provenance: { source: extraction.lineRange ? `${relPath}:${extraction.lineRange}` : relPath },
|
|
212
214
|
};
|
|
213
215
|
}
|
|
216
|
+
const IDEAS_UNREADABLE_LISTED = 3;
|
|
217
|
+
/**
|
|
218
|
+
* IDEA-002A: the `inbox` ideas, via the Idea Capture reader (`listIdeas`: bounded,
|
|
219
|
+
* symlink/hardlink/special-file refusing, never writes). A file that cannot be read
|
|
220
|
+
* as an idea is not counted and is reported as one warning — it never breaks the
|
|
221
|
+
* brief. Idea content is untrusted; only ids and titles are kept.
|
|
222
|
+
*/
|
|
223
|
+
function collectParkedIdeas(targetDir, diagnostics) {
|
|
224
|
+
const empty = { total: 0, items: [] };
|
|
225
|
+
let entries;
|
|
226
|
+
try {
|
|
227
|
+
entries = listIdeas(ideaStore(targetDir));
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
const code = err instanceof Error && 'code' in err ? String(err.code) : 'ERROR';
|
|
231
|
+
diagnostics.push({
|
|
232
|
+
code: 'IDEAS_UNREADABLE',
|
|
233
|
+
severity: 'warn',
|
|
234
|
+
message: `The ideas directory was not read (${code}); parked ideas are not shown.`,
|
|
235
|
+
});
|
|
236
|
+
return empty;
|
|
237
|
+
}
|
|
238
|
+
const inbox = entries.flatMap((e) => (e.ok && e.idea.status === 'inbox' ? [e.idea] : []));
|
|
239
|
+
inbox.sort((a, b) => ideaNumber(b.id) - ideaNumber(a.id));
|
|
240
|
+
const bad = entries.flatMap((e) => (e.ok ? [] : [e]));
|
|
241
|
+
if (bad.length > 0) {
|
|
242
|
+
const listed = bad.slice(0, IDEAS_UNREADABLE_LISTED).map((e) => `${e.id} (${e.code})`).join(', ');
|
|
243
|
+
const more = bad.length > IDEAS_UNREADABLE_LISTED ? `, and ${bad.length - IDEAS_UNREADABLE_LISTED} more` : '';
|
|
244
|
+
diagnostics.push({
|
|
245
|
+
code: 'IDEAS_UNREADABLE',
|
|
246
|
+
severity: 'warn',
|
|
247
|
+
message: `${bad.length} idea file(s) could not be read and are not counted as parked: ${listed}${more}. Run \`monolith idea show <id>\`.`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return { total: inbox.length, items: inbox.slice(0, PARKED_IDEAS_LIMIT).map((i) => ({ id: i.id, title: i.title })) };
|
|
251
|
+
}
|
|
214
252
|
/**
|
|
215
253
|
* Best-effort read of one state document for the freshness check. A missing,
|
|
216
254
|
* refused or unreadable file simply contributes no evidence — freshness never
|
|
@@ -286,6 +324,7 @@ export async function collect(targetDir) {
|
|
|
286
324
|
items: discoverDocs(targetDir, adrDirs, (fields) => fields.get('status') === 'accepted', diagnostics),
|
|
287
325
|
provenance: { source: adrDirs.join(', ') },
|
|
288
326
|
};
|
|
327
|
+
const parkedIdeas = monolithInitialized ? collectParkedIdeas(targetDir, diagnostics) : { total: 0, items: [] };
|
|
289
328
|
return {
|
|
290
329
|
identity,
|
|
291
330
|
monolithInitialized,
|
|
@@ -295,6 +334,7 @@ export async function collect(targetDir) {
|
|
|
295
334
|
story,
|
|
296
335
|
mandatoryProtocols,
|
|
297
336
|
adrs,
|
|
337
|
+
parkedIdeas,
|
|
298
338
|
diagnostics,
|
|
299
339
|
freshness,
|
|
300
340
|
freshnessEvidence,
|
|
@@ -7,4 +7,6 @@ export declare const PROTOCOLS_DIR = "protocols";
|
|
|
7
7
|
export declare const ROADMAP_INDEX_REL = "context/maps/roadmap-index.md";
|
|
8
8
|
export declare const STORY_INDEX_REL = "memory/working/story-index.md";
|
|
9
9
|
export declare const HANDOFF_REL = "context/boot/HANDOFF.md";
|
|
10
|
+
/** IDEA-002A: most parked ideas the brief ever lists; the rest is a "+N more" count. */
|
|
11
|
+
export declare const PARKED_IDEAS_LIMIT = 3;
|
|
10
12
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/brief/constants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,6BAA6B,CAAC;AACxD,sIAAsI;AACtI,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,aAAa,cAAc,CAAC;AACzC,qGAAqG;AACrG,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AACjE,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,eAAO,MAAM,WAAW,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/brief/constants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,aAAa,6BAA6B,CAAC;AACxD,sIAAsI;AACtI,eAAO,MAAM,eAAe,aAAa,CAAC;AAC1C,eAAO,MAAM,aAAa,cAAc,CAAC;AACzC,qGAAqG;AACrG,eAAO,MAAM,iBAAiB,kCAAkC,CAAC;AACjE,eAAO,MAAM,eAAe,kCAAkC,CAAC;AAC/D,eAAO,MAAM,WAAW,4BAA4B,CAAC;AACrD,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,IAAI,CAAC"}
|
package/dist/brief/constants.js
CHANGED
|
@@ -7,3 +7,5 @@ export const PROTOCOLS_DIR = 'protocols';
|
|
|
7
7
|
export const ROADMAP_INDEX_REL = 'context/maps/roadmap-index.md';
|
|
8
8
|
export const STORY_INDEX_REL = 'memory/working/story-index.md';
|
|
9
9
|
export const HANDOFF_REL = 'context/boot/HANDOFF.md';
|
|
10
|
+
/** IDEA-002A: most parked ideas the brief ever lists; the rest is a "+N more" count. */
|
|
11
|
+
export const PARKED_IDEAS_LIMIT = 3;
|
package/dist/brief/render.d.ts
CHANGED
|
@@ -48,6 +48,13 @@
|
|
|
48
48
|
* two forces INCOMPLETE. P0 is never dropped. All project-controlled
|
|
49
49
|
* strings, provenance pointers included, pass through
|
|
50
50
|
* `sanitizeSingleLine` (N-1).
|
|
51
|
+
*
|
|
52
|
+
* Advisory section (IDEA-002B): Parked Ideas is the one data-holding section whose
|
|
53
|
+
* budget degradation does NOT force INCOMPLETE. It is user-saved side context, not
|
|
54
|
+
* project state; its degradation stays visible (fewer items, count, or a marker
|
|
55
|
+
* whenever they fit) but `Completeness` and `--strict` keep describing only the
|
|
56
|
+
* project context that existed before ideas did. Unreadable idea files are
|
|
57
|
+
* reported by the `IDEAS_UNREADABLE` warning, which is not an error.
|
|
51
58
|
*/
|
|
52
59
|
import type { BriefModel } from './types.js';
|
|
53
60
|
export declare const DEFAULT_MAX_CHARS = 6000;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/brief/render.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/brief/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA0F,MAAM,YAAY,CAAC;AAYrI,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAGtC,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,YAAY,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;CAC5B;AAwXD,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,GAAE,MAA0B,GAAG,YAAY,CAgF5F"}
|
package/dist/brief/render.js
CHANGED
|
@@ -48,8 +48,15 @@
|
|
|
48
48
|
* two forces INCOMPLETE. P0 is never dropped. All project-controlled
|
|
49
49
|
* strings, provenance pointers included, pass through
|
|
50
50
|
* `sanitizeSingleLine` (N-1).
|
|
51
|
+
*
|
|
52
|
+
* Advisory section (IDEA-002B): Parked Ideas is the one data-holding section whose
|
|
53
|
+
* budget degradation does NOT force INCOMPLETE. It is user-saved side context, not
|
|
54
|
+
* project state; its degradation stays visible (fewer items, count, or a marker
|
|
55
|
+
* whenever they fit) but `Completeness` and `--strict` keep describing only the
|
|
56
|
+
* project context that existed before ideas did. Unreadable idea files are
|
|
57
|
+
* reported by the `IDEAS_UNREADABLE` warning, which is not an error.
|
|
51
58
|
*/
|
|
52
|
-
import { ACTIVE_MD_REL } from './constants.js';
|
|
59
|
+
import { ACTIVE_MD_REL, PARKED_IDEAS_LIMIT } from './constants.js';
|
|
53
60
|
import { sanitizeSingleLine, sanitizeMultiLine } from './sanitize.js';
|
|
54
61
|
// Shorthand for the existing single-line sanitizer — applied to every string
|
|
55
62
|
// that originates in project files or configuration (N-1), including
|
|
@@ -352,6 +359,41 @@ function fitItemList(header, items, footer, remaining, omittedMarkerFn, minimalF
|
|
|
352
359
|
}
|
|
353
360
|
return { text: '', consumed: 0, lossFlag: true };
|
|
354
361
|
}
|
|
362
|
+
const PARKED_IDEA_TITLE_MAX = 80;
|
|
363
|
+
function formatParkedIdea(i) {
|
|
364
|
+
const title = san(i.title);
|
|
365
|
+
const shown = title.length > PARKED_IDEA_TITLE_MAX ? `${title.slice(0, PARKED_IDEA_TITLE_MAX - 1).replace(/[\ud800-\udbff]$/, '')}…` : title;
|
|
366
|
+
return ` ${san(i.id)} ${shown}\n`;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* IDEA-002A: Parked Ideas — the lowest-priority section. Nothing at all when there
|
|
370
|
+
* are none (so a project without ideas renders exactly as before). Under budget
|
|
371
|
+
* pressure it first lists fewer ideas, then only the count, then a one-line
|
|
372
|
+
* marker, then nothing. Each step is flagged in `lossFlag`, but `render` does not
|
|
373
|
+
* count it towards Completeness (advisory section, see the Completeness contract).
|
|
374
|
+
* Listing at most `PARKED_IDEAS_LIMIT` ideas is the design, not a loss.
|
|
375
|
+
*/
|
|
376
|
+
function buildParkedIdeasSection(ideas, remaining) {
|
|
377
|
+
if (!ideas || ideas.total === 0)
|
|
378
|
+
return { text: '', consumed: 0, lossFlag: false };
|
|
379
|
+
const header = `\n## Parked Ideas (${ideas.total})\n`;
|
|
380
|
+
const explore = ' Explore: `monolith idea list`\n';
|
|
381
|
+
const wanted = Math.min(PARKED_IDEAS_LIMIT, ideas.items.length);
|
|
382
|
+
const noun = (n) => (n === 1 ? 'idea' : 'ideas');
|
|
383
|
+
const candidates = [];
|
|
384
|
+
for (let k = wanted; k >= 1; k--) {
|
|
385
|
+
const rest = ideas.total - k;
|
|
386
|
+
const body = ideas.items.slice(0, k).map(formatParkedIdea).join('');
|
|
387
|
+
const more = rest > 0 ? `\n +${rest} more ${noun(rest)}\n` : '';
|
|
388
|
+
const text = header + body + more + explore;
|
|
389
|
+
candidates.push({ text, consumed: text.length, lossFlag: k < wanted });
|
|
390
|
+
}
|
|
391
|
+
const countOnly = `${header} ${ideas.total} ${noun(ideas.total)}, not listed (budget)\n${explore}`;
|
|
392
|
+
const marker = `\n[Parked Ideas (${ideas.total}) omitted — budget; \`monolith idea list\`]\n`;
|
|
393
|
+
for (const text of [countOnly, marker, BARE_OMIT_MARKER])
|
|
394
|
+
candidates.push({ text, consumed: text.length, lossFlag: true });
|
|
395
|
+
return candidates.find((c) => c.consumed <= remaining) ?? { text: '', consumed: 0, lossFlag: true };
|
|
396
|
+
}
|
|
355
397
|
const P3_TEXT = '\n## More\n' +
|
|
356
398
|
' Full checkpoint history: `monolith mission checkpoints <id>`\n' +
|
|
357
399
|
' Mission detail: `monolith mission show <id>`\n' +
|
|
@@ -394,6 +436,15 @@ export function render(model, maxChars = DEFAULT_MAX_CHARS) {
|
|
|
394
436
|
p2 += finishedSection.text;
|
|
395
437
|
remaining -= finishedSection.consumed;
|
|
396
438
|
budgetLoss = budgetLoss || finishedSection.lossFlag;
|
|
439
|
+
// Parked ideas are optional extra context: they get what is left after everything
|
|
440
|
+
// else, and never the room the static P3 pointer block would have had. Their
|
|
441
|
+
// budget loss is shown locally (fewer ideas / count / marker) but deliberately does
|
|
442
|
+
// not feed `budgetLoss`: saving an idea must not flip an unchanged project's brief
|
|
443
|
+
// to INCOMPLETE and break `--strict` checks of the main project context.
|
|
444
|
+
const p3Fits = P3_TEXT.length <= remaining;
|
|
445
|
+
const ideasSection = buildParkedIdeasSection(model.parkedIdeas, p3Fits ? remaining - P3_TEXT.length : remaining);
|
|
446
|
+
p2 += ideasSection.text;
|
|
447
|
+
remaining -= ideasSection.consumed;
|
|
397
448
|
if (P3_TEXT.length <= remaining) {
|
|
398
449
|
p3 = P3_TEXT;
|
|
399
450
|
remaining -= p3.length;
|
package/dist/brief/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type DiagnosticSeverity = 'info' | 'warn' | 'error';
|
|
|
17
17
|
* budget decision is made, which is what lets render() reserve space for
|
|
18
18
|
* them deterministically (see render.ts's two-phase budget accounting).
|
|
19
19
|
*/
|
|
20
|
-
export type BriefDiagnosticCode = 'MONOLITH_NOT_INITIALIZED' | 'STATE_NOT_INITIALIZED' | 'STATE_EMPTY' | 'STATE_UNREADABLE' | 'STATE_LEGACY_SCHEMA_MIGRATED' | 'MULTIPLE_ACTIVE_MISSIONS' | 'CHECKPOINT_ORDER_MISMATCH' | 'ACTIVE_MD_MISSING' | 'ACTIVE_MD_REJECTED' | 'ACTIVE_MD_UNREADABLE' | 'ACTIVE_MD_NO_CURRENT_STORY' | 'CONFIG_UNREADABLE' | 'CONFIG_ADR_DIRS_INVALID' | 'DOC_DIR_UNREADABLE' | 'DOC_FILE_UNREADABLE' | 'DOC_DIR_OUTSIDE_PROJECT' | 'PATH_OUTSIDE_PROJECT' | 'WRITE_ARTIFACTS_PRESENT';
|
|
20
|
+
export type BriefDiagnosticCode = 'MONOLITH_NOT_INITIALIZED' | 'STATE_NOT_INITIALIZED' | 'STATE_EMPTY' | 'STATE_UNREADABLE' | 'STATE_LEGACY_SCHEMA_MIGRATED' | 'MULTIPLE_ACTIVE_MISSIONS' | 'CHECKPOINT_ORDER_MISMATCH' | 'ACTIVE_MD_MISSING' | 'ACTIVE_MD_REJECTED' | 'ACTIVE_MD_UNREADABLE' | 'ACTIVE_MD_NO_CURRENT_STORY' | 'CONFIG_UNREADABLE' | 'CONFIG_ADR_DIRS_INVALID' | 'DOC_DIR_UNREADABLE' | 'DOC_FILE_UNREADABLE' | 'DOC_DIR_OUTSIDE_PROJECT' | 'PATH_OUTSIDE_PROJECT' | 'WRITE_ARTIFACTS_PRESENT' | 'IDEAS_UNREADABLE';
|
|
21
21
|
export interface BriefDiagnostic {
|
|
22
22
|
code: BriefDiagnosticCode;
|
|
23
23
|
severity: DiagnosticSeverity;
|
|
@@ -84,6 +84,20 @@ export interface ProjectIdentity {
|
|
|
84
84
|
monolithRoot: string;
|
|
85
85
|
provenance: Provenance;
|
|
86
86
|
}
|
|
87
|
+
/** IDEA-002A: one parked (status `inbox`) idea, as shown by the brief. */
|
|
88
|
+
export interface ParkedIdeaRef {
|
|
89
|
+
id: string;
|
|
90
|
+
title: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* IDEA-002A: the `inbox` ideas, projected from the Idea Capture store (never
|
|
94
|
+
* re-parsed here). `items` holds at most `PARKED_IDEAS_LIMIT` entries, newest
|
|
95
|
+
* numeric id first; `total` counts every readable `inbox` idea.
|
|
96
|
+
*/
|
|
97
|
+
export interface ParkedIdeasSummary {
|
|
98
|
+
total: number;
|
|
99
|
+
items: ParkedIdeaRef[];
|
|
100
|
+
}
|
|
87
101
|
export interface BriefModel {
|
|
88
102
|
identity: ProjectIdentity;
|
|
89
103
|
monolithInitialized: boolean;
|
|
@@ -93,6 +107,8 @@ export interface BriefModel {
|
|
|
93
107
|
story: StorySummary;
|
|
94
108
|
mandatoryProtocols: ProtocolIndex;
|
|
95
109
|
adrs: AdrIndex;
|
|
110
|
+
/** IDEA-002A: optional so a hand-built model without it renders as before. */
|
|
111
|
+
parkedIdeas?: ParkedIdeasSummary;
|
|
96
112
|
/** Collect-time diagnostics, in deterministic (collection-order) sequence. */
|
|
97
113
|
diagnostics: BriefDiagnostic[];
|
|
98
114
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/brief/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAC3B,0BAA0B,GAC1B,uBAAuB,GACvB,aAAa,GACb,kBAAkB,GAClB,8BAA8B,GAC9B,0BAA0B,GAC1B,2BAA2B,GAC3B,mBAAmB,GACnB,oBAAoB,GACpB,sBAAsB,GACtB,4BAA4B,GAC5B,mBAAmB,GACnB,yBAAyB,GACzB,oBAAoB,GACpB,qBAAqB,GACrB,yBAAyB,GACzB,sBAAsB,GACtB,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/brief/types.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* monolith mission — manage Tactical Coordinatorium mission lifecycle
|
|
12
12
|
* monolith brief — deterministic, bounded project context summary
|
|
13
13
|
* monolith durable — inspect/verify/maintain Durable State task logs
|
|
14
|
+
* monolith idea — capture and recall deferred ideas (add/list/show)
|
|
15
|
+
* monolith update — add missing Idea Capture infrastructure to an existing instance
|
|
14
16
|
* monolith ui — launch Mission Control (read-only dashboard)
|
|
15
17
|
*/
|
|
16
18
|
import { MONOLITH_VERSION } from '../version.js';
|
|
@@ -38,6 +40,13 @@ Commands:
|
|
|
38
40
|
accepted ADRs) — read-only, no LLM/network/ERG involved.
|
|
39
41
|
durable Inspect and maintain Durable State (long-running task logs):
|
|
40
42
|
list | show | assess | packet | verify | retention.
|
|
43
|
+
idea Capture a deferred thought without leaving the current task, and
|
|
44
|
+
recall it later: add | list | show. Plain Markdown files, no LLM.
|
|
45
|
+
update Bring an existing instance up to Idea Capture & Recall. Preview by
|
|
46
|
+
default (changes nothing). --apply creates MONOLITH-managed files
|
|
47
|
+
and may add or refresh managed sections inside your existing
|
|
48
|
+
files; your text outside those sections is kept, and conflicting
|
|
49
|
+
edits are never overwritten (see 'monolith update --help').
|
|
41
50
|
ui Launch Mission Control, the read-only web dashboard, on
|
|
42
51
|
127.0.0.1 (default port 4173). 'ui dev' adds Vite hot reload
|
|
43
52
|
(source checkout only). See 'monolith ui --help'.
|
|
@@ -60,6 +69,14 @@ Durable usage:
|
|
|
60
69
|
monolith durable verify [taskId] (exit 2 if a log fails verification)
|
|
61
70
|
monolith durable retention --retain-days N [--compact-after N] [--apply]
|
|
62
71
|
|
|
72
|
+
Idea usage:
|
|
73
|
+
monolith idea add --title "..." (--description "..." | --description-stdin) [--context "..."]
|
|
74
|
+
monolith idea list
|
|
75
|
+
monolith idea show <id>
|
|
76
|
+
|
|
77
|
+
Update usage:
|
|
78
|
+
monolith update [--apply]
|
|
79
|
+
|
|
63
80
|
UI usage:
|
|
64
81
|
monolith ui [--port N]
|
|
65
82
|
monolith ui dev [--port N]
|
|
@@ -72,6 +89,12 @@ async function main() {
|
|
|
72
89
|
const args = process.argv.slice(2);
|
|
73
90
|
// `ui` owns its own --help / --version-like flags (`monolith ui dev --help`).
|
|
74
91
|
const ownsHelp = args[0] === 'ui';
|
|
92
|
+
if (args[0] === 'update' && (args.includes('--help') || args.includes('-h'))) {
|
|
93
|
+
const { UPDATE_USAGE } = await import('../update/cli.js');
|
|
94
|
+
console.log(UPDATE_USAGE);
|
|
95
|
+
process.exit(0);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
75
98
|
if (args.length === 0 || (!ownsHelp && (args.includes('--help') || args.includes('-h')))) {
|
|
76
99
|
console.log(USAGE);
|
|
77
100
|
process.exit(0);
|
|
@@ -141,6 +164,16 @@ async function main() {
|
|
|
141
164
|
await runDurableCommand(args.slice(1));
|
|
142
165
|
break;
|
|
143
166
|
}
|
|
167
|
+
case 'idea': {
|
|
168
|
+
const { runIdeaCommand } = await import('../ideas/cli.js');
|
|
169
|
+
await runIdeaCommand(args.slice(1));
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case 'update': {
|
|
173
|
+
const { runUpdate } = await import('../update/cli.js');
|
|
174
|
+
process.exitCode = await runUpdate(args.slice(1));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
144
177
|
case 'ui': {
|
|
145
178
|
const { runUi } = await import('./ui.js');
|
|
146
179
|
process.exit(await runUi(args.slice(1)));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DISCOVERY-001C — unified Project Map composer (ADR-021). Internal.
|
|
3
|
+
*
|
|
4
|
+
* A pure transformation of TWO finished results into one Markdown text: a `DiscoveryResult`
|
|
5
|
+
* (structural facts, evidence OBSERVED_FILE-class) and a `DocumentationResult` (what documents
|
|
6
|
+
* claim, evidence DOCUMENT_TEXT). It reads no file, starts no analyzer, calls no model, opens no
|
|
7
|
+
* connection, keeps no clock and modifies neither input.
|
|
8
|
+
*
|
|
9
|
+
* Identity. Neither contract carries a project identity (no id, no root, no digest); both only say
|
|
10
|
+
* that they were derived from "a traversal". So the composer cannot confirm that the two results
|
|
11
|
+
* describe the same project and says so in the output. It does not guess from paths or names.
|
|
12
|
+
*
|
|
13
|
+
* Trust. The two sources are never merged: structure comes from the existing renderer, unchanged;
|
|
14
|
+
* documentation gets its own section, and every statement made by a document is shown as an
|
|
15
|
+
* unverified claim. The statuses of the two analyzers are shown side by side, never combined.
|
|
16
|
+
*
|
|
17
|
+
* Markdown safety. Every untrusted string goes through the renderer's `markdownCodeSpan` (control and
|
|
18
|
+
* bidi characters are shown as [U+XXXX], an inline code span cannot be closed by its content) and sits
|
|
19
|
+
* in a list item; headings and structure are fixed text. Document text is never interpreted.
|
|
20
|
+
*/
|
|
21
|
+
import { type DiscoveryResult } from './contracts.js';
|
|
22
|
+
import { type DocumentationResult } from './docs.js';
|
|
23
|
+
/**
|
|
24
|
+
* Composes the unified Project Map. Throws `DiscoveryError('RESULT_INVALID')` if either input fails
|
|
25
|
+
* its own validator (the message names which input and the first violations, never a value).
|
|
26
|
+
*/
|
|
27
|
+
export declare function composeProjectMap(discovery: DiscoveryResult, documentation: DocumentationResult): string;
|
|
28
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/discovery/compose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAqD,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACzG,OAAO,EAKL,KAAK,mBAAmB,EAEzB,MAAM,WAAW,CAAC;AAgCnB;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,GAAG,MAAM,CAoGxG"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DISCOVERY-001C — unified Project Map composer (ADR-021). Internal.
|
|
3
|
+
*
|
|
4
|
+
* A pure transformation of TWO finished results into one Markdown text: a `DiscoveryResult`
|
|
5
|
+
* (structural facts, evidence OBSERVED_FILE-class) and a `DocumentationResult` (what documents
|
|
6
|
+
* claim, evidence DOCUMENT_TEXT). It reads no file, starts no analyzer, calls no model, opens no
|
|
7
|
+
* connection, keeps no clock and modifies neither input.
|
|
8
|
+
*
|
|
9
|
+
* Identity. Neither contract carries a project identity (no id, no root, no digest); both only say
|
|
10
|
+
* that they were derived from "a traversal". So the composer cannot confirm that the two results
|
|
11
|
+
* describe the same project and says so in the output. It does not guess from paths or names.
|
|
12
|
+
*
|
|
13
|
+
* Trust. The two sources are never merged: structure comes from the existing renderer, unchanged;
|
|
14
|
+
* documentation gets its own section, and every statement made by a document is shown as an
|
|
15
|
+
* unverified claim. The statuses of the two analyzers are shown side by side, never combined.
|
|
16
|
+
*
|
|
17
|
+
* Markdown safety. Every untrusted string goes through the renderer's `markdownCodeSpan` (control and
|
|
18
|
+
* bidi characters are shown as [U+XXXX], an inline code span cannot be closed by its content) and sits
|
|
19
|
+
* in a list item; headings and structure are fixed text. Document text is never interpreted.
|
|
20
|
+
*/
|
|
21
|
+
import { DiscoveryError } from './contracts.js';
|
|
22
|
+
import { validateDiscoveryResult } from './contracts.js';
|
|
23
|
+
import { DOCUMENTATION_DIAGNOSTIC_AFFECTS_COMPLETENESS, validateDocumentationResult, } from './docs.js';
|
|
24
|
+
import { markdownCodeSpan as code, renderProjectMapProposal } from './render.js';
|
|
25
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
26
|
+
function textShown(text, status) {
|
|
27
|
+
if (status === 'WITHHELD')
|
|
28
|
+
return '(withheld: contains unsafe characters)';
|
|
29
|
+
return status === 'TRUNCATED' ? `${code(text)} (truncated)` : code(text);
|
|
30
|
+
}
|
|
31
|
+
function lines(o) {
|
|
32
|
+
const { startLine, endLine } = o.source;
|
|
33
|
+
if (startLine === undefined)
|
|
34
|
+
return 'whole file';
|
|
35
|
+
return endLine === undefined || endLine === startLine ? `line ${startLine}` : `lines ${startLine}-${endLine}`;
|
|
36
|
+
}
|
|
37
|
+
function docDiagnosticLine(d) {
|
|
38
|
+
const known = Object.prototype.hasOwnProperty.call(DOCUMENTATION_DIAGNOSTIC_AFFECTS_COMPLETENESS, d.code);
|
|
39
|
+
const origin = d.code.startsWith('TRAVERSAL_')
|
|
40
|
+
? 'reported by the traversal'
|
|
41
|
+
: known && DOCUMENTATION_DIAGNOSTIC_AFFECTS_COMPLETENESS[d.code]
|
|
42
|
+
? 'documentation analysis; makes the result PARTIAL'
|
|
43
|
+
: 'documentation analysis; does not by itself make the result PARTIAL';
|
|
44
|
+
const parts = [`${code(d.code)} (${origin})`];
|
|
45
|
+
if (d.path !== undefined)
|
|
46
|
+
parts.push(`path ${code(d.path)}`);
|
|
47
|
+
if (d.count !== undefined)
|
|
48
|
+
parts.push(`count ${d.count}`);
|
|
49
|
+
return `- ${parts.join(', ')}: ${code(d.message)}`;
|
|
50
|
+
}
|
|
51
|
+
/** Lowers every heading of the (trusted, fixed-text) renderer output by two levels. Project data cannot start a line with `#`: it only appears inside code spans, and line breaks inside it are escaped. */
|
|
52
|
+
const demote = (markdown) => markdown.replace(/^(#+) /gm, '##$1 ');
|
|
53
|
+
/**
|
|
54
|
+
* Composes the unified Project Map. Throws `DiscoveryError('RESULT_INVALID')` if either input fails
|
|
55
|
+
* its own validator (the message names which input and the first violations, never a value).
|
|
56
|
+
*/
|
|
57
|
+
export function composeProjectMap(discovery, documentation) {
|
|
58
|
+
const dv = validateDiscoveryResult(discovery);
|
|
59
|
+
if (dv.length > 0)
|
|
60
|
+
throw new DiscoveryError('RESULT_INVALID', `cannot compose: the DiscoveryResult failed validation (${dv.length} violation(s)): ${dv.slice(0, 3).join('; ')}`);
|
|
61
|
+
const cv = validateDocumentationResult(documentation);
|
|
62
|
+
if (cv.length > 0)
|
|
63
|
+
throw new DiscoveryError('RESULT_INVALID', `cannot compose: the DocumentationResult failed validation (${cv.length} violation(s)): ${cv.slice(0, 3).join('; ')}`);
|
|
64
|
+
const out = [];
|
|
65
|
+
const line = (s = '') => void out.push(s);
|
|
66
|
+
line('# PROJECT MAP');
|
|
67
|
+
line();
|
|
68
|
+
line('This document puts two separate results side by side: structure observed in the project tree, and what the project’s Markdown documents say. They are not merged and not compared with each other. It is a proposal for a reader to review.');
|
|
69
|
+
// ------------------------------------------------------------ structure
|
|
70
|
+
line();
|
|
71
|
+
line('## Observed Project Structure');
|
|
72
|
+
line();
|
|
73
|
+
line('- Source: a Discovery result (evidence from the traversal: directory entries, file names, package.json fields).');
|
|
74
|
+
line('- Where the text below says documentation was not analysed, it refers to this Discovery result only; see Documentation Overview for the separate documentation result.');
|
|
75
|
+
line();
|
|
76
|
+
out.push(demote(renderProjectMapProposal(discovery)).replace(/\n+$/, ''));
|
|
77
|
+
// ---------------------------------------------------------------- docs
|
|
78
|
+
const { stats, observations } = documentation;
|
|
79
|
+
line();
|
|
80
|
+
line('## Documentation Overview');
|
|
81
|
+
line();
|
|
82
|
+
line('- Source: a Documentation result. Everything below is the content of documents as written by their authors. It is UNVERIFIED: it has not been compared with the code and may be wrong or out of date.');
|
|
83
|
+
line(`- Analysis completeness: ${documentation.completeness}`);
|
|
84
|
+
line(documentation.completeness === 'PARTIAL'
|
|
85
|
+
? ' - Some documentation information was not extracted; see Diagnostics below. Counts are lower bounds.'
|
|
86
|
+
: ' - Everything inside the declared scope was handled. COMPLETE does not mean that every Markdown construct was recognised; see the supported subset in Trust & Coverage Notes.');
|
|
87
|
+
line(`- Reproducible: ${documentation.reproducible ? 'Yes' : 'No'}`);
|
|
88
|
+
line(`- Traversal behind it: completeness ${documentation.traversal.completeness}, reproducible ${documentation.traversal.reproducible ? 'Yes' : 'No'}`);
|
|
89
|
+
line(`- Schema version: ${documentation.schemaVersion}`);
|
|
90
|
+
line(`- Documents listed as present: ${stats.candidateDocuments}; analysed: ${stats.documentsAnalyzed}; present but not analysed: ${stats.documentsNotAnalyzed}`);
|
|
91
|
+
line(`- Bytes read: ${stats.bytesRead}; observations recorded: ${stats.observations}`);
|
|
92
|
+
const byPath = new Map();
|
|
93
|
+
for (const o of observations) {
|
|
94
|
+
const g = byPath.get(o.source.path);
|
|
95
|
+
if (g)
|
|
96
|
+
g.push(o);
|
|
97
|
+
else
|
|
98
|
+
byPath.set(o.source.path, [o]);
|
|
99
|
+
}
|
|
100
|
+
line();
|
|
101
|
+
line('### Documents');
|
|
102
|
+
line();
|
|
103
|
+
if (byPath.size === 0)
|
|
104
|
+
line('- No documents were recorded.');
|
|
105
|
+
for (const [path, group] of byPath) {
|
|
106
|
+
line(`- ${code(path)}`);
|
|
107
|
+
for (const o of group) {
|
|
108
|
+
if (o.kind === 'DOCUMENT_PRESENT') {
|
|
109
|
+
const size = [o.bytes === undefined ? '' : plural(o.bytes, 'byte', 'bytes'), o.lines === undefined ? '' : plural(o.lines, 'line', 'lines')].filter(Boolean).join(', ');
|
|
110
|
+
line(` - File observed (evidence ${o.evidence}); ${o.analysis === 'ANALYZED' ? 'content analysed' : 'content NOT analysed'}${size ? `; ${size}` : ''}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const headings = group.filter((o) => o.kind === 'HEADING');
|
|
114
|
+
const sections = group.filter((o) => o.kind === 'SECTION');
|
|
115
|
+
const links = group.filter((o) => o.kind === 'LINK');
|
|
116
|
+
if (headings.length > 0) {
|
|
117
|
+
line(` - Headings written in the document (${headings.length} recorded; evidence ${headings[0].evidence}):`);
|
|
118
|
+
for (const o of headings)
|
|
119
|
+
if (o.kind === 'HEADING')
|
|
120
|
+
line(` - ${lines(o)}, level ${o.level}: ${textShown(o.text, o.textStatus)}`);
|
|
121
|
+
}
|
|
122
|
+
if (sections.length > 0) {
|
|
123
|
+
line(` - Text ranges between headings (${sections.length} recorded; evidence ${sections[0].evidence}):`);
|
|
124
|
+
for (const o of sections)
|
|
125
|
+
if (o.kind === 'SECTION')
|
|
126
|
+
line(` - ${lines(o)}, opened by heading ${o.headingOrdinal}${o.parentHeadingOrdinal === undefined ? '' : ` under heading ${o.parentHeadingOrdinal}`}: ${plural(o.ownTextLineGroups, 'text line group', 'text line groups')} of its own`);
|
|
127
|
+
}
|
|
128
|
+
if (links.length > 0) {
|
|
129
|
+
line(` - Inline links written in the document (${links.length} recorded, not necessarily all; evidence ${links[0].evidence}; targets are not resolved or checked):`);
|
|
130
|
+
for (const o of links)
|
|
131
|
+
if (o.kind === 'LINK')
|
|
132
|
+
line(` - ${lines(o)}, ${o.linkType}: target ${code(o.target)}, text ${textShown(o.text, o.textStatus)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
line();
|
|
136
|
+
line('### Documentation Diagnostics');
|
|
137
|
+
line();
|
|
138
|
+
if (documentation.diagnostics.length === 0)
|
|
139
|
+
line('- No diagnostics were reported.');
|
|
140
|
+
for (const d of documentation.diagnostics)
|
|
141
|
+
line(docDiagnosticLine(d));
|
|
142
|
+
// --------------------------------------------------------------- trust
|
|
143
|
+
const limits = documentation.limits;
|
|
144
|
+
line();
|
|
145
|
+
line('## Trust & Coverage Notes');
|
|
146
|
+
line();
|
|
147
|
+
line('- Structural facts in Observed Project Structure were obtained by observing the file tree and, for package.json, declared fields. They are observations, not verified descriptions of how the project works.');
|
|
148
|
+
line('- Everything in Documentation Overview is unverified document content (evidence DOCUMENT_TEXT). The only observed item there is that a document file exists (evidence OBSERVED_FILE). No documentary statement has been confirmed by the code.');
|
|
149
|
+
line('- Common origin of the two results is NOT confirmed: neither result carries a project identity, so this document cannot tell whether they describe the same project or the same moment. No such link is asserted.');
|
|
150
|
+
line('- The two analyzers keep separate statuses and they are not combined:');
|
|
151
|
+
line(` - Structural Discovery: completeness ${discovery.completeness}, reproducible ${discovery.reproducible ? 'Yes' : 'No'}`);
|
|
152
|
+
line(` - Documentation Analysis: completeness ${documentation.completeness}, reproducible ${documentation.reproducible ? 'Yes' : 'No'}`);
|
|
153
|
+
line('- COMPLETE refers only to the scope each analyzer declares. It is not a statement that the project or its documents were fully understood.');
|
|
154
|
+
line('- Documentation scope: README.md at the project root and *.md files under docs/ that the traversal listed. Supported Markdown subset: ATX headings; one-line inline links; fenced code blocks, front matter, HTML comment lines and indented lines are skipped. Not recognised: setext headings, reference-style links, autolinks, images, raw HTML, constructs split over lines, headings or links inside quotes or lists.');
|
|
155
|
+
line('- Link and heading counts are counts of recognised constructs, not the number of links or headings in a document.');
|
|
156
|
+
line(`- Documentation limits: documents ${limits.maxDocuments}; bytes per document ${limits.maxDocumentBytes}; total bytes ${limits.maxTotalBytes}; headings per document ${limits.maxHeadingsPerDocument}; links per document ${limits.maxLinksPerDocument}; text length ${limits.maxTextLength}; link target length ${limits.maxTargetLength}; line length ${limits.maxLineLength}; observations ${limits.maxObservations}; diagnostics ${limits.maxDiagnostics}.`);
|
|
157
|
+
line('- Not observing something is not evidence that it does not exist.');
|
|
158
|
+
return `${out.join('\n')}\n`;
|
|
159
|
+
}
|