@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
@@ -0,0 +1,32 @@
1
+ /**
2
+ * DISCOVERY-001C-G — unified discovery orchestration (ADR-021). Internal.
3
+ *
4
+ * One entry point that runs the whole Project Discovery chain: ONE safe traversal, then fact
5
+ * extraction and documentation analysis over that same `TraversalResult` object and the same
6
+ * `projectRoot` string, then the composer.
7
+ *
8
+ * Guarantees, by construction of this function:
9
+ * - same invocation: both results come from one call;
10
+ * - same traversal: exactly one `traverseProject` call; both analyzers receive that object;
11
+ * - same project root: the one string given here is passed unchanged to all three steps.
12
+ *
13
+ * NOT guaranteed: same file system snapshot. The traversal records a listing; the analyzers read
14
+ * file contents (package.json, Markdown documents) later and independently, so a file may change
15
+ * between the listing and either read, or between the two reads. `reproducible`/`completeness`
16
+ * remain the only signals about that.
17
+ *
18
+ * Errors from any step propagate unchanged; nothing is retried, caught or replaced by a fallback.
19
+ * No other data source, no network, no model, no write. The existing path-safety and bounded-read
20
+ * rules live inside the steps and are not bypassed here.
21
+ */
22
+ import { analyzeDocumentation } from './docs.js';
23
+ import { composeProjectMap } from './compose.js';
24
+ import { extractProjectFacts } from './facts.js';
25
+ import { traverseProject } from './traverse.js';
26
+ /** Runs traversal → facts + documentation (same traversal object) → composer and returns the Project Map Markdown. */
27
+ export function discoverProjectMap(projectRoot, options = {}) {
28
+ const traversal = traverseProject(projectRoot, options.traversal);
29
+ const discovery = extractProjectFacts(projectRoot, traversal, options.facts);
30
+ const documentation = analyzeDocumentation(projectRoot, traversal, options.documentation);
31
+ return composeProjectMap(discovery, documentation);
32
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * DISCOVERY-001B-3 — deterministic Markdown renderer for a `DiscoveryResult` (ADR-021). Internal.
3
+ *
4
+ * A pure transformation: it takes a result that `validateDiscoveryResult` accepts and returns text.
5
+ * It reads no file, opens no connection, starts no process, calls no model, keeps no clock and writes
6
+ * nothing. It reports what the result contains and nothing else; in particular it draws no
7
+ * architectural conclusion. A valid result is not independent evidence of what the project contains,
8
+ * so every statement is phrased as an observation, a declaration, a candidate or a report.
9
+ *
10
+ * Markdown safety. Every string that comes from the scanned project or from the result (paths,
11
+ * names, languages, rules, messages, codes) goes through `code()` and is only ever placed inside an
12
+ * inline code span in a list item. Headings, list structure and section titles are fixed text, so
13
+ * project data cannot create a heading, a list item, a link, HTML, a code fence or a new section.
14
+ */
15
+ import { type DiscoveryResult } from './contracts.js';
16
+ /**
17
+ * The one way an untrusted string enters the document: an inline code span. Inside a code span
18
+ * Markdown and HTML are not interpreted. The fence is one backtick longer than the longest backtick
19
+ * run in the text, so the text cannot close it; padding keeps leading/trailing backticks and spaces.
20
+ */
21
+ declare function code(text: string): string;
22
+ /**
23
+ * Renders a Project Map Proposal for a `DiscoveryResult`.
24
+ *
25
+ * Throws `DiscoveryError('RESULT_INVALID')` if the result does not satisfy `validateDiscoveryResult`
26
+ * (which includes the canonical order of facts, sources and diagnostics). The message carries the
27
+ * first violations, which by the validator's own design name a location and a rule and never repeat
28
+ * a value from the input. The input is not modified.
29
+ *
30
+ * The size of the output is linear in the size of the (bounded) result: every untrusted string is cut
31
+ * at `MAX_SHOWN_CODE_POINTS` and no section lists more than the result holds.
32
+ */
33
+ export declare function renderProjectMapProposal(result: DiscoveryResult): string;
34
+ /** Internal (DISCOVERY-001C): the one safe way to place an untrusted string in Markdown, shared with the composer. */
35
+ export { code as markdownCodeSpan };
36
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/discovery/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAWL,KAAK,eAAe,EAGrB,MAAM,gBAAgB,CAAC;AA6BxB;;;;GAIG;AACH,iBAAS,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUlC;AAsDD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA2JxE;AAED,sHAAsH;AACtH,OAAO,EAAE,IAAI,IAAI,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * DISCOVERY-001B-3 — deterministic Markdown renderer for a `DiscoveryResult` (ADR-021). Internal.
3
+ *
4
+ * A pure transformation: it takes a result that `validateDiscoveryResult` accepts and returns text.
5
+ * It reads no file, opens no connection, starts no process, calls no model, keeps no clock and writes
6
+ * nothing. It reports what the result contains and nothing else; in particular it draws no
7
+ * architectural conclusion. A valid result is not independent evidence of what the project contains,
8
+ * so every statement is phrased as an observation, a declaration, a candidate or a report.
9
+ *
10
+ * Markdown safety. Every string that comes from the scanned project or from the result (paths,
11
+ * names, languages, rules, messages, codes) goes through `code()` and is only ever placed inside an
12
+ * inline code span in a list item. Headings, list structure and section titles are fixed text, so
13
+ * project data cannot create a heading, a list item, a link, HTML, a code fence or a new section.
14
+ */
15
+ import { DEPENDENCY_SECTIONS, DiscoveryError, EXTRACTION_DIAGNOSTIC_AFFECTS_COMPLETENESS, FACT_LEVEL_BY_KIND, sourceCoverage, validateDiscoveryResult, } from './contracts.js';
16
+ /**
17
+ * Longest untrusted string shown, in code points. Not a new limit: it is the contract's own maximum
18
+ * for a project-relative path (`isSafeRelativePath` in contracts.ts, 4096). Dependency and script names are already
19
+ * bounded by `limits.maxNameLength`; this bound covers the strings the contract leaves free
20
+ * (diagnostic messages, language names, rule names).
21
+ */
22
+ const MAX_SHOWN_CODE_POINTS = 4096;
23
+ // Control, format (bidirectional marks and overrides, zero-width, soft hyphen, tags), line and paragraph
24
+ // separators, and surrogates. Shown as `[U+XXXX]` so that they cannot reorder, hide or break text.
25
+ const NOT_SHOWN_AS_IS = /^[\p{Cc}\p{Cf}\p{Zl}\p{Zp}\p{Cs}]$/u;
26
+ function visible(text) {
27
+ let out = '';
28
+ let n = 0;
29
+ for (const ch of text) {
30
+ if (n === MAX_SHOWN_CODE_POINTS) {
31
+ out += `…[truncated; ${text.length} UTF-16 units in total]`;
32
+ break;
33
+ }
34
+ n++;
35
+ if (NOT_SHOWN_AS_IS.test(ch))
36
+ out += `[U+${ch.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}]`;
37
+ else
38
+ out += ch;
39
+ }
40
+ return out;
41
+ }
42
+ /**
43
+ * The one way an untrusted string enters the document: an inline code span. Inside a code span
44
+ * Markdown and HTML are not interpreted. The fence is one backtick longer than the longest backtick
45
+ * run in the text, so the text cannot close it; padding keeps leading/trailing backticks and spaces.
46
+ */
47
+ function code(text) {
48
+ const shown = visible(text);
49
+ if (shown.length === 0)
50
+ return '(empty)';
51
+ let longest = 0;
52
+ for (const run of shown.match(/`+/g) ?? [])
53
+ longest = Math.max(longest, run.length);
54
+ const fence = '`'.repeat(longest + 1);
55
+ // CommonMark strips one space from each side only when the content is not all spaces, so a
56
+ // spaces-only value gets no padding: it is shown with exactly the spaces it has.
57
+ const pad = !/^ +$/.test(shown) && /^[` ]|[` ]$/.test(shown) ? ' ' : '';
58
+ return `${fence}${pad}${shown}${pad}${fence}`;
59
+ }
60
+ const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
61
+ function sourceText(s) {
62
+ return s.field === undefined ? code(s.path) : `${code(s.path)} at ${code(s.field)}`;
63
+ }
64
+ const LIMIT_LABELS = {
65
+ maxDirectoryFacts: 'directory facts',
66
+ maxDirectoryDepth: 'directory depth',
67
+ maxLanguageFacts: 'language facts',
68
+ maxManifestFacts: 'manifest facts',
69
+ maxManifestReads: 'manifest reads',
70
+ maxManifestBytes: 'manifest size, bytes',
71
+ maxDependenciesPerManifest: 'dependency facts per manifest',
72
+ maxScriptsPerManifest: 'script facts per manifest',
73
+ maxEntrypointFacts: 'entrypoint candidate facts',
74
+ maxFacts: 'facts in total',
75
+ maxDiagnostics: 'diagnostics',
76
+ maxNameLength: 'dependency or script name length',
77
+ maxSourcesPerFact: 'sources listed for a language aggregate',
78
+ };
79
+ const KIND_ORDER = Object.keys(FACT_LEVEL_BY_KIND);
80
+ function ofKind(facts, kind) {
81
+ return facts.filter((f) => f.kind === kind);
82
+ }
83
+ /** Groups facts by subject, keeping the (already canonical) order of first appearance. */
84
+ function bySubject(facts) {
85
+ const groups = new Map();
86
+ for (const f of facts) {
87
+ const g = groups.get(f.subject);
88
+ if (g)
89
+ g.push(f);
90
+ else
91
+ groups.set(f.subject, [f]);
92
+ }
93
+ return groups;
94
+ }
95
+ function diagnosticLine(d) {
96
+ const known = Object.prototype.hasOwnProperty.call(EXTRACTION_DIAGNOSTIC_AFFECTS_COMPLETENESS, d.code);
97
+ const origin = d.code.startsWith('TRAVERSAL_')
98
+ ? 'reported by the traversal'
99
+ : known && EXTRACTION_DIAGNOSTIC_AFFECTS_COMPLETENESS[d.code]
100
+ ? 'extraction; makes the result PARTIAL'
101
+ : 'extraction; does not by itself make the result PARTIAL';
102
+ const parts = [`${code(d.code)} (${origin})`];
103
+ if (d.path !== undefined)
104
+ parts.push(`path ${code(d.path)}`);
105
+ if (d.count !== undefined)
106
+ parts.push(`count ${d.count}`);
107
+ return `- ${parts.join(', ')}: ${code(d.message)}`;
108
+ }
109
+ /**
110
+ * Renders a Project Map Proposal for a `DiscoveryResult`.
111
+ *
112
+ * Throws `DiscoveryError('RESULT_INVALID')` if the result does not satisfy `validateDiscoveryResult`
113
+ * (which includes the canonical order of facts, sources and diagnostics). The message carries the
114
+ * first violations, which by the validator's own design name a location and a rule and never repeat
115
+ * a value from the input. The input is not modified.
116
+ *
117
+ * The size of the output is linear in the size of the (bounded) result: every untrusted string is cut
118
+ * at `MAX_SHOWN_CODE_POINTS` and no section lists more than the result holds.
119
+ */
120
+ export function renderProjectMapProposal(result) {
121
+ const violations = validateDiscoveryResult(result);
122
+ if (violations.length > 0) {
123
+ throw new DiscoveryError('RESULT_INVALID', `cannot render: the result failed validation (${violations.length} violation(s)): ${violations.slice(0, 3).join('; ')}`);
124
+ }
125
+ const { facts, diagnostics, stats, limits } = result;
126
+ const partial = result.completeness === 'PARTIAL';
127
+ const out = [];
128
+ const line = (s = '') => void out.push(s);
129
+ line('# Project Map Proposal');
130
+ line();
131
+ line('This document reports the contents of a Discovery result. It lists observed, declared and candidate items; it is a proposal for a reader to review, not a verified description of the project. A valid result is not independent evidence of what the project contains.');
132
+ // ---------------------------------------------------------------- status
133
+ line();
134
+ line('## Discovery Status');
135
+ line();
136
+ line(`- Completeness: ${result.completeness}`);
137
+ line(partial
138
+ ? ' - Some information may have been skipped. Facts that were extracted are shown below; see Limitations for the diagnostics.'
139
+ : ' - The scan covered the scope it declared. This does not mean that every aspect of the project was examined.');
140
+ line(`- Reproducible: ${result.reproducible ? 'Yes' : 'No'}`);
141
+ if (!result.reproducible)
142
+ line(' - A second scan of the unchanged tree might not return this result.');
143
+ line(`- Traversal: completeness ${result.traversal.completeness}, reproducible ${result.traversal.reproducible ? 'Yes' : 'No'}`);
144
+ line(`- Schema version: ${result.schemaVersion}`);
145
+ line('- Analysed manifest format: package.json only.');
146
+ line(' - Other recognised manifest names are reported as present; their content was not analysed. Documentation files were not analysed.');
147
+ line(`- Traversal entries received: ${stats.inputEntries} (${stats.excludedEntries} did not reach extraction)`);
148
+ line(`- Manifests found: ${stats.manifestsFound}; package.json reads attempted: ${stats.manifestsRead} (an attempt may have failed; see Limitations)`);
149
+ line(`- Facts recorded: ${stats.facts}`);
150
+ // ------------------------------------------------------------- structure
151
+ line();
152
+ line('## Project Structure');
153
+ line();
154
+ const directories = ofKind(facts, 'DIRECTORY');
155
+ if (directories.length === 0)
156
+ line('- No directory entries were observed.');
157
+ else {
158
+ line(`- Observed directories (${directories.length}, down to depth ${limits.maxDirectoryDepth}):`);
159
+ for (const f of directories)
160
+ line(` - ${code(f.subject)}`);
161
+ }
162
+ // ------------------------------------------------------------- languages
163
+ line();
164
+ line('## Observed Languages');
165
+ line();
166
+ const languages = ofKind(facts, 'LANGUAGE_BY_EXTENSION');
167
+ if (languages.length === 0)
168
+ line('- No language-by-extension observations were recorded.');
169
+ for (const f of languages) {
170
+ const count = Number(f.value);
171
+ const full = sourceCoverage(f) === 'FULL';
172
+ line(`- ${code(f.subject)} (extension ${code(f.object)}): ${plural(count, 'observed file', 'observed files')}; source coverage ${full ? 'FULL' : 'SAMPLE'}`);
173
+ line(full ? ` - Sources (all ${plural(f.sources.length, 'file', 'files')}):` : ` - Source examples (${f.sources.length} listed of ${count} observed files; not a complete list):`);
174
+ for (const s of f.sources)
175
+ line(` - ${sourceText(s)}`);
176
+ }
177
+ if (languages.length > 0) {
178
+ line();
179
+ line('- Language is derived from the file extension only; file contents were not inspected.');
180
+ if (partial)
181
+ line('- The result is PARTIAL: counts are lower bounds and some languages may be missing.');
182
+ }
183
+ if (stats.filesWithoutKnownLanguage > 0) {
184
+ if (languages.length === 0)
185
+ line();
186
+ line(`- ${plural(stats.filesWithoutKnownLanguage, 'file has', 'files have')} an extension with no known language mapping (reported, not guessed).`);
187
+ }
188
+ // ------------------------------------------------------------- manifests
189
+ line();
190
+ line('## Detected Manifests');
191
+ line();
192
+ const manifests = ofKind(facts, 'MANIFEST_PRESENT');
193
+ if (manifests.length === 0)
194
+ line('- No known manifest file names were observed.');
195
+ for (const f of manifests) {
196
+ const analysed = f.subject.slice(f.subject.lastIndexOf('/') + 1) === 'package.json';
197
+ line(`- ${code(f.subject)} (ecosystem hint ${code(f.object ?? '')}): file name observed; ${analysed ? 'content analysis is supported for this format; whether it was read is shown by the diagnostics' : 'content not analysed in this version'}`);
198
+ }
199
+ // ---------------------------------------------------------- dependencies
200
+ line();
201
+ line('## Declared Dependencies');
202
+ line();
203
+ const dependencies = ofKind(facts, 'DEPENDENCY_DECLARED');
204
+ if (dependencies.length === 0)
205
+ line('- No dependency declarations were observed.');
206
+ else {
207
+ line('- These are names declared in a manifest. They are not resolved, not installed and not checked against use in the code.');
208
+ for (const [manifest, group] of bySubject(dependencies)) {
209
+ line(`- Declared in ${code(manifest)}:`);
210
+ for (const section of DEPENDENCY_SECTIONS) {
211
+ const inSection = group.filter((f) => f.value === section);
212
+ if (inSection.length === 0)
213
+ continue;
214
+ line(` - ${code(section)} (${inSection.length}):`);
215
+ for (const f of inSection)
216
+ line(` - ${code(f.object)} — ${sourceText(f.sources[0])}`);
217
+ }
218
+ }
219
+ }
220
+ // --------------------------------------------------------------- scripts
221
+ line();
222
+ line('## Declared Scripts');
223
+ line();
224
+ const scripts = ofKind(facts, 'SCRIPT_DECLARED');
225
+ if (scripts.length === 0)
226
+ line('- No script declarations were observed.');
227
+ else {
228
+ line('- Only script names are recorded. Commands are neither stored nor run.');
229
+ for (const [manifest, group] of bySubject(scripts)) {
230
+ line(`- Declared in ${code(manifest)} (${group.length}):`);
231
+ for (const f of group)
232
+ line(` - ${code(f.object)} — ${sourceText(f.sources[0])}`);
233
+ }
234
+ }
235
+ // ----------------------------------------------------------- entrypoints
236
+ line();
237
+ line('## Entrypoint Candidates');
238
+ line();
239
+ const entrypoints = ofKind(facts, 'ENTRYPOINT_CANDIDATE');
240
+ if (entrypoints.length === 0)
241
+ line('- No entrypoint candidates were observed.');
242
+ else {
243
+ line('- A candidate is a file whose name follows a common convention. It is not confirmed to start the program.');
244
+ for (const f of entrypoints)
245
+ line(` - Candidate: ${code(f.subject)} (interpreted by rule ${code(f.rule)} from a file observed at this path)`);
246
+ }
247
+ // -------------------------------------------------------------- evidence
248
+ line();
249
+ line('## Evidence & Provenance');
250
+ line();
251
+ line('- Fact counts by kind:');
252
+ for (const kind of KIND_ORDER) {
253
+ const note = kind === 'FILE_PRESENT' ? ' (file evidence for entrypoint candidates only; not a count of project files)' : '';
254
+ line(` - ${kind} (${FACT_LEVEL_BY_KIND[kind]}): ${ofKind(facts, kind).length}${note}`);
255
+ }
256
+ const sampled = languages.filter((f) => sourceCoverage(f) === 'SAMPLE').length;
257
+ line(`- Source coverage: ${plural(languages.length - sampled, 'aggregate is', 'aggregates are')} FULL and ${plural(sampled, 'aggregate is', 'aggregates are')} SAMPLE. Every other fact rests on exactly the source listed with it.`);
258
+ line('- DETECTED: observed in the project’s file names, directory entries or manifest fields. INTERPRETED: derived from DETECTED facts by a named rule.');
259
+ line('- Source paths are project-relative as reported by the result; none was checked again while rendering.');
260
+ // ----------------------------------------------------------- limitations
261
+ line();
262
+ line('## Limitations');
263
+ line();
264
+ line('- Not observing something is not evidence that it does not exist in the project.');
265
+ line('- Only package.json content is analysed; other manifest formats and documentation are not.');
266
+ line('- Dependencies and scripts are declarations; entrypoints are name-based candidates.');
267
+ line(`- Configured limits: ${Object.keys(LIMIT_LABELS).map((k) => `${LIMIT_LABELS[k]} ${limits[k]}`).join('; ')}.`);
268
+ line();
269
+ line('### Diagnostics');
270
+ line();
271
+ if (diagnostics.length === 0)
272
+ line('- No diagnostics were reported.');
273
+ for (const d of diagnostics)
274
+ line(diagnosticLine(d));
275
+ return `${out.join('\n')}\n`;
276
+ }
277
+ /** Internal (DISCOVERY-001C): the one safe way to place an untrusted string in Markdown, shared with the composer. */
278
+ export { code as markdownCodeSpan };
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Bounded, safe project traversal (DISCOVERY-001B-1, ADR-021).
3
+ *
4
+ * Lists the directory tree of a project and nothing else: it never reads file
5
+ * content, never writes, never runs project code and never touches the network.
6
+ * It is the only code in Discovery that lists directories.
7
+ *
8
+ * What it does:
9
+ * - lists each directory through `opendir` + `Dir#readSync` (never `readdirSync`),
10
+ * one open handle at a time, so a hostile directory cannot be loaded whole before
11
+ * a cap applies; the handle is always closed (`finally`);
12
+ * - never follows a symlink (it is listed as kind `symlink` and counted; its target is
13
+ * never looked at); FIFOs, sockets and devices are listed as `other`, never opened;
14
+ * - excludes the real `<monolithRoot>` (resolved with `resolveMonolithRoot`, not by
15
+ * the literal name `.monolith`), a fixed list of dependency/build directories, nested
16
+ * repositories (a directory holding `.git`) and directories on another file system;
17
+ * - bounds the work and the retained result: directory entries READ (every listing counts,
18
+ * including the one of a directory later found to be unreadable, replaced or opaque),
19
+ * entries per directory, depth, wall-clock time and the UTF-8 bytes of the retained
20
+ * paths; every bound that fires yields `PARTIAL` with a typed diagnostic, never a
21
+ * silent cut. A nested repository is recognised by one `lstat` of `<dir>/.git` BEFORE
22
+ * the directory is listed, so its content is neither read nor counted;
23
+ * - is deterministic for an unchanged tree: names are ordered by UTF-16 code units
24
+ * (never `localeCompare`), breadth-first, and the result is sorted before it is
25
+ * returned. No clock value, absolute path or `readdir` order reaches the result.
26
+ *
27
+ * What it does NOT do (ADR-013, SEC-001B.3 open; TM-C unsupported): it does not prevent
28
+ * and does not reliably detect a process that replaces a directory (e.g. with a symlink)
29
+ * while the walk runs. The window runs from the `lstat` that queues a directory until
30
+ * the walk has listed it, i.e. the breadth-first distance in between. Comparing the
31
+ * identity (`dev`/`ino`) of a directory before and after listing it (`DIR_CHANGED`) may
32
+ * notice SOME replacements; a replacement that is put back before the second `lstat`
33
+ * goes unnoticed, and names read from the substitute can then appear as COMPLETE.
34
+ * Do not run it on a tree an untrusted process is modifying.
35
+ *
36
+ * Root: `projectRoot` must be absolute and is the caller's explicit choice. A symlink as
37
+ * root is accepted and its target is walked (nothing below the root is ever followed, so
38
+ * the boundary is the directory the caller named). A missing, non-directory, unreadable
39
+ * or otherwise unresolvable root throws `ROOT_UNSAFE` (from `resolveMonolithRoot`, before
40
+ * any listing); `ROOT_NOT_DIRECTORY` is the defensive second check. Errors other than a
41
+ * root-containment error from `resolveMonolithRoot` propagate unchanged.
42
+ *
43
+ * Nested MONOLITH instances: only the actual `<monolithRoot>` is excluded. A sub-project
44
+ * that has its own `.monolith` stays part of the inventory (its sources belong to this
45
+ * project); that directory is not recognised by name. Excluding the confirmed internals
46
+ * of nested instances is a decision for DISCOVERY-001B-2.
47
+ *
48
+ * The time budget is checked between entries; a synchronous syscall that hangs in the
49
+ * kernel (e.g. a dead network mount) is not interrupted.
50
+ */
51
+ import { type Dirent } from 'fs';
52
+ export type TraversalEntryKind = 'file' | 'directory' | 'symlink' | 'other';
53
+ export interface TraversalEntry {
54
+ /** Project-relative, `/`-separated; never absolute, never contains `..`. */
55
+ path: string;
56
+ kind: TraversalEntryKind;
57
+ }
58
+ export interface TraversalLimits {
59
+ /**
60
+ * Directory entries READ from the file system in total, across every listing (included in
61
+ * the result or not); see `LIMIT_ENTRIES`. A listing is charged for the entries it read,
62
+ * so a directory that does not fit in the remaining budget is not enumerated.
63
+ */
64
+ maxEntries: number;
65
+ /** A directory with more entries than this is opaque (not enumerated). */
66
+ maxEntriesPerDir: number;
67
+ /** Deepest path (in segments) that is listed; a directory at this depth is not entered. */
68
+ maxDepth: number;
69
+ /** Wall-clock budget in milliseconds (monotonic). */
70
+ deadlineMs: number;
71
+ /**
72
+ * Total UTF-8 bytes of the paths kept in `entries` (`Buffer.byteLength`, checked before an
73
+ * entry is added; see `LIMIT_PATH_BYTES`). It bounds the retained path text, not the memory
74
+ * of the process: JS strings, entry objects and the queue add overhead proportional to
75
+ * `maxEntries`, and the figure is a proxy for heap use (about one byte per ASCII character).
76
+ */
77
+ maxPathBytes: number;
78
+ }
79
+ /** Starting values (ADR-021 Q-10); validated by the 001B-1 benchmark, see the story report. */
80
+ export declare const DEFAULT_TRAVERSAL_LIMITS: Readonly<TraversalLimits>;
81
+ /** Directories never entered, matched by name at any depth. */
82
+ export declare const EXCLUDED_DIR_NAMES: readonly string[];
83
+ /** Directories never entered, matched by project-relative path. */
84
+ export declare const EXCLUDED_DIR_PATHS: readonly string[];
85
+ /**
86
+ * Closed list. `affectsCompleteness` codes make the result PARTIAL (something inside
87
+ * the declared scope could not be done). The others report the scope policy.
88
+ * An unknown code is treated as affecting completeness (fail closed).
89
+ */
90
+ declare const DIAGNOSTIC_AFFECTS_COMPLETENESS: {
91
+ readonly LIMIT_ENTRIES: true;
92
+ readonly LIMIT_DEPTH: true;
93
+ readonly LIMIT_DIR_ENTRIES: true;
94
+ readonly LIMIT_DEADLINE: true;
95
+ readonly LIMIT_PATH_BYTES: true;
96
+ readonly DIR_UNREADABLE: true;
97
+ readonly DIR_VANISHED: true;
98
+ readonly DIR_CHANGED: true;
99
+ readonly DIR_ERROR: true;
100
+ readonly NAME_UNREPRESENTABLE: true;
101
+ readonly ENTRY_UNCLASSIFIED: true;
102
+ readonly EXCLUDED_DIR: false;
103
+ readonly MONOLITH_ROOT_EXCLUDED: false;
104
+ readonly SYMLINK_SKIPPED: false;
105
+ readonly NESTED_REPO_SKIPPED: false;
106
+ readonly MOUNT_SKIPPED: false;
107
+ };
108
+ export type TraversalDiagnosticCode = keyof typeof DIAGNOSTIC_AFFECTS_COMPLETENESS;
109
+ export interface TraversalDiagnostic {
110
+ code: TraversalDiagnosticCode;
111
+ /** Project-relative path, when the diagnostic concerns one. */
112
+ path?: string;
113
+ /** Total occurrences, present on the summary line of a code that exceeded the listed paths. */
114
+ count?: number;
115
+ message: string;
116
+ }
117
+ export interface TraversalStats {
118
+ /** Entries included in `entries`. */
119
+ entries: number;
120
+ files: number;
121
+ directories: number;
122
+ symlinks: number;
123
+ other: number;
124
+ /** Directory entries read from the file system by all listings (the figure `maxEntries` bounds). */
125
+ examined: number;
126
+ /** UTF-8 bytes of the paths in `entries` (the figure `maxPathBytes` bounds). */
127
+ pathBytes: number;
128
+ /** Directories whose contents were listed. */
129
+ listedDirectories: number;
130
+ }
131
+ export interface TraversalResult {
132
+ /** `PARTIAL` as soon as something inside the declared scope could not be done. */
133
+ completeness: 'COMPLETE' | 'PARTIAL';
134
+ /**
135
+ * `true` when a second walk of the same, unchanged tree with the same limits would return
136
+ * this exact result. It is `false` when any diagnostic whose cause is not a function of the
137
+ * tree alone was recorded: the time budget (`LIMIT_DEADLINE`), a tree that changed or a
138
+ * transient failure (`DIR_CHANGED`, `DIR_VANISHED`, `DIR_ERROR`) or an entry whose type
139
+ * could not be determined (`ENTRY_UNCLASSIFIED`). It is not a synonym of `COMPLETE`: a
140
+ * walk cut by a deterministic limit is `PARTIAL` and still reproducible. It is a statement
141
+ * about the result, not a guarantee about a tree that changes during the walk.
142
+ */
143
+ reproducible: boolean;
144
+ limits: TraversalLimits;
145
+ stats: TraversalStats;
146
+ /** Sorted by `path` in UTF-16 code-unit order. */
147
+ entries: TraversalEntry[];
148
+ /** Sorted: completeness-affecting first, then by code, path, message. */
149
+ diagnostics: TraversalDiagnostic[];
150
+ }
151
+ export type TraversalErrorCode = 'INVALID_LIMITS' | 'ROOT_UNSAFE' | 'ROOT_NOT_DIRECTORY' | 'UNSUPPORTED_PLATFORM';
152
+ /** The traversal cannot start or cannot be trusted; no result exists (FAILED). */
153
+ export declare class TraversalError extends Error {
154
+ readonly code: TraversalErrorCode;
155
+ constructor(code: TraversalErrorCode, message: string, options?: {
156
+ cause?: unknown;
157
+ });
158
+ }
159
+ export interface TraversalDirHandle {
160
+ readSync(): Dirent | null;
161
+ closeSync(): void;
162
+ }
163
+ export interface TraversalIdentity {
164
+ isFile(): boolean;
165
+ isDirectory(): boolean;
166
+ isSymbolicLink(): boolean;
167
+ dev: bigint;
168
+ ino: bigint;
169
+ }
170
+ export interface TraversalFs {
171
+ opendir(path: string): TraversalDirHandle;
172
+ lstat(path: string): TraversalIdentity;
173
+ stat(path: string): TraversalIdentity;
174
+ }
175
+ export interface TraverseOptions {
176
+ limits?: Partial<TraversalLimits>;
177
+ /** @internal Test seam. */
178
+ fs?: TraversalFs;
179
+ /** @internal Test seam: monotonic milliseconds. */
180
+ now?: () => number;
181
+ /** @internal Test seam: the `open(2)` flags the platform provides. */
182
+ platformFlags?: {
183
+ noFollow?: number;
184
+ nonBlock?: number;
185
+ };
186
+ }
187
+ /**
188
+ * Walk the project at `projectRoot` and return its structure.
189
+ *
190
+ * `projectRoot` is the explicit, caller-resolved project directory (for the CLI: `cwd`).
191
+ * The instance root is resolved with `resolveMonolithRoot` and excluded. Throws
192
+ * `TraversalError` when the walk cannot start or be trusted; otherwise returns a
193
+ * result that says whether it is `COMPLETE` or `PARTIAL`.
194
+ */
195
+ export declare function traverseProject(projectRoot: string, options?: TraverseOptions): TraversalResult;
196
+ export {};
197
+ //# sourceMappingURL=traverse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../../src/discovery/traverse.ts"],"names":[],"mappings":"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"}