unknown-knowledge 2.1.0

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 (147) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +5 -0
  3. package/README.md +167 -0
  4. package/cli/.gitkeep +0 -0
  5. package/cli/commands/init-copy.js +90 -0
  6. package/cli/commands/init.js +386 -0
  7. package/cli/init-copy.js +24 -0
  8. package/cli/init.js +24 -0
  9. package/cli/kit.manifest.yaml +256 -0
  10. package/cli/lib/copy-payload.js +334 -0
  11. package/cli/lib/generate-wrappers.js +162 -0
  12. package/package.json +36 -0
  13. package/payload/adapter-fixtures/README.md +53 -0
  14. package/payload/adapter-fixtures/html/EXPECTED.yaml +50 -0
  15. package/payload/adapter-fixtures/html/sample.html +38 -0
  16. package/payload/adapter-fixtures/md/EXPECTED.yaml +65 -0
  17. package/payload/adapter-fixtures/md/sample.md +32 -0
  18. package/payload/adapter-fixtures/pdf/EXPECTED.yaml +45 -0
  19. package/payload/adapter-fixtures/pdf/sample.pdf +0 -0
  20. package/payload/adapter-fixtures/txt/EXPECTED.yaml +31 -0
  21. package/payload/adapter-fixtures/txt/sample.txt +18 -0
  22. package/payload/docs/README.md +102 -0
  23. package/payload/docs/boundaries.md +60 -0
  24. package/payload/docs/ci-wiring.md +109 -0
  25. package/payload/docs/steward-guide.md +238 -0
  26. package/payload/engine/audit.js +34 -0
  27. package/payload/engine/commands/audit.js +289 -0
  28. package/payload/engine/commands/derive.js +334 -0
  29. package/payload/engine/commands/ingest.js +124 -0
  30. package/payload/engine/commands/log-entry.js +85 -0
  31. package/payload/engine/commands/phoenix.js +206 -0
  32. package/payload/engine/commands/preflight.js +530 -0
  33. package/payload/engine/commands/resolve.js +1678 -0
  34. package/payload/engine/commands/survey-map.js +351 -0
  35. package/payload/engine/commands/validate-values.js +315 -0
  36. package/payload/engine/commands/validate.js +1426 -0
  37. package/payload/engine/derive.js +34 -0
  38. package/payload/engine/ingest.js +34 -0
  39. package/payload/engine/lib/anchor-signatures.js +126 -0
  40. package/payload/engine/lib/boot.js +39 -0
  41. package/payload/engine/lib/call-numbers.js +133 -0
  42. package/payload/engine/lib/cli.js +147 -0
  43. package/payload/engine/lib/coverage.js +849 -0
  44. package/payload/engine/lib/decomposition.js +225 -0
  45. package/payload/engine/lib/derived.js +494 -0
  46. package/payload/engine/lib/engine-refusal.js +40 -0
  47. package/payload/engine/lib/exit-codes.js +14 -0
  48. package/payload/engine/lib/extractor-kinds.js +955 -0
  49. package/payload/engine/lib/format-adapters.js +802 -0
  50. package/payload/engine/lib/id-grammars.js +178 -0
  51. package/payload/engine/lib/iso-date.js +55 -0
  52. package/payload/engine/lib/kit-root.js +101 -0
  53. package/payload/engine/lib/load-stores.js +1624 -0
  54. package/payload/engine/lib/log-entry.js +196 -0
  55. package/payload/engine/lib/phoenix.js +628 -0
  56. package/payload/engine/lib/scoring.js +150 -0
  57. package/payload/engine/lib/suppressions.js +172 -0
  58. package/payload/engine/lib/time-verdicts.js +282 -0
  59. package/payload/engine/lib/usage-error.js +14 -0
  60. package/payload/engine/lib/validate-record.js +504 -0
  61. package/payload/engine/log-entry.js +34 -0
  62. package/payload/engine/phoenix.js +39 -0
  63. package/payload/engine/preflight.js +34 -0
  64. package/payload/engine/resolve.js +34 -0
  65. package/payload/engine/survey-map.js +34 -0
  66. package/payload/engine/validate-values.js +34 -0
  67. package/payload/engine/validate.js +34 -0
  68. package/payload/extractor-fixtures/.gitkeep +0 -0
  69. package/payload/extractor-fixtures/README.md +29 -0
  70. package/payload/extractor-fixtures/swift/strings-keys/EXPECTED.yaml +8 -0
  71. package/payload/extractor-fixtures/swift/strings-keys/sample.strings +15 -0
  72. package/payload/extractor-fixtures/swift/swift-const-array/EXPECTED.yaml +7 -0
  73. package/payload/extractor-fixtures/swift/swift-const-array/sample.swift +21 -0
  74. package/payload/extractor-fixtures/swift/swift-enum/EXPECTED.yaml +8 -0
  75. package/payload/extractor-fixtures/swift/swift-enum/sample.swift +30 -0
  76. package/payload/extractor-fixtures/swift/yaml-keys/EXPECTED.yaml +6 -0
  77. package/payload/extractor-fixtures/swift/yaml-keys/sample.yaml +23 -0
  78. package/payload/extractor-fixtures/swift/yaml-map-keys/EXPECTED.yaml +7 -0
  79. package/payload/extractor-fixtures/swift/yaml-map-keys/sample.yaml +15 -0
  80. package/payload/extractor-fixtures/ts/dir-modules/EXPECTED.yaml +12 -0
  81. package/payload/extractor-fixtures/ts/dir-modules/sample-modules/alpha.widget.ts +1 -0
  82. package/payload/extractor-fixtures/ts/dir-modules/sample-modules/beta.widget.ts +1 -0
  83. package/payload/extractor-fixtures/ts/dir-modules/sample-modules/gamma.widget.ts +1 -0
  84. package/payload/extractor-fixtures/ts/dir-modules/sample-modules/helpers/format.ts +3 -0
  85. package/payload/extractor-fixtures/ts/dir-modules/sample-modules/widgets.test.ts +2 -0
  86. package/payload/extractor-fixtures/ts/json-keys/EXPECTED.yaml +7 -0
  87. package/payload/extractor-fixtures/ts/json-keys/sample.json +5 -0
  88. package/payload/extractor-fixtures/ts/json-map-keys/EXPECTED.yaml +7 -0
  89. package/payload/extractor-fixtures/ts/json-map-keys/sample.json +13 -0
  90. package/payload/extractor-fixtures/ts/ts-const-array/EXPECTED.yaml +6 -0
  91. package/payload/extractor-fixtures/ts/ts-const-array/sample.ts +13 -0
  92. package/payload/extractor-fixtures/ts/ts-enum/EXPECTED.yaml +7 -0
  93. package/payload/extractor-fixtures/ts/ts-enum/sample.ts +11 -0
  94. package/payload/extractor-fixtures/ts/ts-object-keys/EXPECTED.yaml +6 -0
  95. package/payload/extractor-fixtures/ts/ts-object-keys/sample.tsx +23 -0
  96. package/payload/extractor-fixtures/ts/ts-union/EXPECTED.yaml +5 -0
  97. package/payload/extractor-fixtures/ts/ts-union/sample.ts +9 -0
  98. package/payload/hooks/pre-commit +37 -0
  99. package/payload/hooks/reverse-lookup +66 -0
  100. package/payload/package.json +3 -0
  101. package/payload/protocol/.gitkeep +0 -0
  102. package/payload/protocol/AGENTS.md +239 -0
  103. package/payload/protocol/derived-layer.md +174 -0
  104. package/payload/protocol/new-kind-pipeline.md +179 -0
  105. package/payload/protocol/registry-warrant.md +162 -0
  106. package/payload/protocol/skills/kb-build.md +303 -0
  107. package/payload/protocol/skills/knowledge-audit.md +183 -0
  108. package/payload/protocol/skills/knowledge-bootstrap.md +229 -0
  109. package/payload/protocol/skills/knowledge-reflect.md +397 -0
  110. package/payload/schemas/catalog.schema.json +32 -0
  111. package/payload/schemas/decision-entry.schema.json +122 -0
  112. package/payload/schemas/finding.schema.json +77 -0
  113. package/payload/schemas/gap.schema.json +52 -0
  114. package/payload/schemas/graduation-categories.schema.json +64 -0
  115. package/payload/schemas/knowledge-leaf.schema.json +194 -0
  116. package/payload/schemas/miss.schema.json +45 -0
  117. package/payload/schemas/ontology-concept.schema.json +115 -0
  118. package/payload/schemas/phoenix-event.schema.json +76 -0
  119. package/payload/schemas/registry.schema.json +57 -0
  120. package/payload/schemas/rules.schema.json +14 -0
  121. package/payload/schemas/survey-scope.schema.json +23 -0
  122. package/payload/templates/decisions/_catalog.yaml +7 -0
  123. package/payload/templates/decisions/_registries/graduation-categories.yaml +42 -0
  124. package/payload/templates/decisions/phoenix-event.yaml +74 -0
  125. package/payload/templates/decisions/reflect-mint-proposal.yaml +100 -0
  126. package/payload/templates/decisions/registry-minting.yaml +58 -0
  127. package/payload/templates/decisions/trust-graduation.yaml +120 -0
  128. package/payload/templates/decisions/trust-revocation.yaml +106 -0
  129. package/payload/templates/knowledge/_catalog.yaml +9 -0
  130. package/payload/templates/knowledge/_registries/anchor.yaml +42 -0
  131. package/payload/templates/knowledge/_registries/authority-tiers.yaml +32 -0
  132. package/payload/templates/knowledge/_registries/domains.yaml +43 -0
  133. package/payload/templates/knowledge/_registries/form.yaml +38 -0
  134. package/payload/templates/knowledge/_registries/jurisdictions.yaml +20 -0
  135. package/payload/templates/knowledge/_registries/operations.yaml +18 -0
  136. package/payload/templates/knowledge/_registries/stage.yaml +53 -0
  137. package/payload/templates/knowledge/_rules.yaml +6 -0
  138. package/payload/templates/new-kind/README.md +107 -0
  139. package/payload/templates/new-kind/descriptor.example.yaml +18 -0
  140. package/payload/templates/new-kind/fixture/EXPECTED.yaml +6 -0
  141. package/payload/templates/new-kind/fixture/demo-anchor.list +2 -0
  142. package/payload/templates/new-kind/fixture/sample.list +7 -0
  143. package/payload/templates/new-kind/parser.example.js +98 -0
  144. package/payload/templates/ontology/_catalog.yaml +6 -0
  145. package/payload/templates/ontology/_rules.yaml +6 -0
  146. package/payload/wrappers/cursor.mdc +15 -0
  147. package/payload/wrappers/pointer.md +10 -0
@@ -0,0 +1,386 @@
1
+ /**
2
+ * `npx unknown-knowledge init` (KK-19, PRD §6 Phase 1) — the interactive,
3
+ * npx-facing wrapper over the deterministic seams KK-17/KK-18 landed:
4
+ * cli/lib/copy-payload.js (manifest-driven copy engine) and
5
+ * cli/lib/generate-wrappers.js (platform pointer files). This file owns the
6
+ * cold-run UX ONLY — prompts (node:readline, zero-dependency D-002), repo
7
+ * stack auto-detection, the git check-ignore sweep, and the exit messaging.
8
+ * The copy/collision semantics live (and are tested) in the seams; this CLI
9
+ * never reimplements them.
10
+ *
11
+ * EVERY prompt is flag-drivable for non-interactive use (--root,
12
+ * --platforms, --stacks, --yes accepting the detected defaults) — CI and
13
+ * the acceptance harness drive it headlessly.
14
+ *
15
+ * NO CI MUTATION, EVER (D-006): init never writes workflow files — nothing
16
+ * under .github/workflows/ (or any other CI config) is created or edited by
17
+ * this command. The only .github/ path init may touch is the Copilot
18
+ * wrapper (.github/copilot-instructions.md), and only when the user selects
19
+ * the copilot platform.
20
+ *
21
+ * npx-ability: package.json maps bin "unknown-knowledge" → this file. The
22
+ * is published through GitHub Actions with provenance (docs/publishing.md).
23
+ * For local development, run `node cli/init.js init`; release verification
24
+ * runs `npx unknown-knowledge@<version> init` from a fresh npm cache.
25
+ *
26
+ * Exit codes (same contract as cli/init-copy.js):
27
+ * 0 — seeded clean. Wrapper skips and gitignore WARNs are reported
28
+ * results, not failures — the seed happened.
29
+ * 2 — refusal (existing/partial seed, dotted root), usage error, a target
30
+ * that is not an existing directory, or any engine failure — including
31
+ * an unexpected throw, which the entry point maps here rather than
32
+ * letting it exit 1. A seed that did not happen is never a silent pass,
33
+ * and never wears the FINDINGS code.
34
+ */
35
+ import { createInterface } from 'node:readline';
36
+ import { spawnSync } from 'node:child_process';
37
+ import { createRequire } from 'node:module';
38
+ import { readdirSync, statSync } from 'node:fs';
39
+ import { join, resolve } from 'node:path';
40
+ import { fileURLToPath } from 'node:url';
41
+ import process from 'node:process';
42
+ import { copyPayload, loadManifest, DEFAULT_ROOT, SeedRefusal } from '../lib/copy-payload.js';
43
+ import { assertKnownPlatforms, generateWrappers } from '../lib/generate-wrappers.js';
44
+ import { EXIT_CODES } from '../../payload/engine/lib/exit-codes.js';
45
+ import { parseArgs as parseFlags, rethrowIfBug, UsageError } from '../../payload/engine/lib/cli.js';
46
+
47
+ export const USAGE = 'usage: npx unknown-knowledge init [--root <name>] [--platforms <ids|none>] '
48
+ + '[--stacks <ids|none>] [--target <dir>] [--yes]';
49
+ // The kit root, two levels up: cli/commands/<this>.js (UCS-950).
50
+ const kitRoot = fileURLToPath(new URL('../..', import.meta.url));
51
+
52
+ // The D-009 later-stacks warning — printed at the end of every init AND
53
+ // carried by the seeded README (payload/docs/README.md, KK-24).
54
+ const LATER_STACKS_WARNING = 'Extractor fixtures for your selected stacks are included. If you adopt '
55
+ + 'another stack later, you author your own pack from the included template '
56
+ + '(templates/new-kind/) — there is no update channel. (D-001, D-009)';
57
+
58
+ // ----------------------------------------------------------------- flags
59
+
60
+ /** Split a multi-select answer: comma/whitespace separated; "none" → []. */
61
+ function parseSelection(value) {
62
+ const items = value.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean);
63
+ return items.length === 1 && items[0].toLowerCase() === 'none' ? [] : items;
64
+ }
65
+
66
+ function parseArgs(argv) {
67
+ const { options, positionals } = parseFlags(argv, {
68
+ boolean: ['yes', 'help'],
69
+ value: ['target', 'root', 'stacks', 'platforms'],
70
+ positionals: true,
71
+ });
72
+ // `npx unknown-knowledge init` — the subcommand is optional and the only one.
73
+ if (positionals.length > 1 || (positionals.length === 1 && positionals[0] !== 'init')) {
74
+ throw new UsageError(`unknown command: ${positionals.join(' ')}`);
75
+ }
76
+ return {
77
+ target: options.target ?? '.',
78
+ root: options.root ?? null,
79
+ stacks: options.stacks === undefined ? null : parseSelection(options.stacks),
80
+ platforms: options.platforms === undefined ? null : parseSelection(options.platforms),
81
+ yes: !!options.yes,
82
+ help: !!options.help,
83
+ };
84
+ }
85
+
86
+ // ------------------------------------------------- stack auto-detection
87
+
88
+ /** Repo file list for detection: git ls-files when a repo, fs walk otherwise. */
89
+ function listRepoFiles(targetDir) {
90
+ const git = spawnSync('git', ['-C', targetDir, 'ls-files', '--cached', '--others', '--exclude-standard', '-z'],
91
+ { encoding: 'utf8' });
92
+ if (git.status === 0) return git.stdout.split('\0').filter(Boolean);
93
+ const out = [];
94
+ const walk = (dir, rel) => {
95
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
96
+ if (entry.name === '.git' || entry.name === 'node_modules') continue;
97
+ const path = rel ? `${rel}/${entry.name}` : entry.name;
98
+ if (entry.isDirectory()) walk(join(dir, entry.name), path);
99
+ else out.push(path);
100
+ }
101
+ };
102
+ walk(resolve(targetDir), '');
103
+ return out;
104
+ }
105
+
106
+ /**
107
+ * Auto-detect the target repo's stacks (PRD §6: the multi-select's
108
+ * pre-selection). Heuristics, per file list:
109
+ * ts — tsconfig.json / package.json anywhere, or any *.ts / *.tsx
110
+ * swift — Package.swift anywhere, any *.xcodeproj, or any *.swift
111
+ * Nothing detected = config-only: an empty default, not an error.
112
+ */
113
+ export function detectStacks(targetDir) {
114
+ const files = listRepoFiles(targetDir);
115
+ const detected = [];
116
+ if (files.some((f) => /(^|\/)(Package\.swift)$/.test(f) || /\.xcodeproj(\/|$)/.test(f) || /\.swift$/.test(f))) {
117
+ detected.push('swift');
118
+ }
119
+ if (files.some((f) => /(^|\/)(tsconfig\.json|package\.json)$/.test(f) || /\.tsx?$/.test(f))) {
120
+ detected.push('ts');
121
+ }
122
+ return detected.sort();
123
+ }
124
+
125
+ // ------------------------------------------------------------- prompts
126
+
127
+ /**
128
+ * readline ask: empty answer (or closed/exhausted stdin) accepts the
129
+ * default. Lines are BUFFERED into a queue — with piped stdin (the headless
130
+ * test path) all answers can arrive before the next question registers, and
131
+ * bare rl.question() would drop them.
132
+ */
133
+ function makeAsker(rl, output) {
134
+ const queue = [];
135
+ const waiters = [];
136
+ let closed = false;
137
+ rl.on('line', (line) => {
138
+ const waiter = waiters.shift();
139
+ if (waiter) waiter(line);
140
+ else queue.push(line);
141
+ });
142
+ rl.on('close', () => {
143
+ closed = true;
144
+ while (waiters.length) waiters.shift()(null);
145
+ });
146
+ return (prompt) => {
147
+ output.write(prompt);
148
+ if (queue.length) return Promise.resolve(queue.shift().trim());
149
+ if (closed) return Promise.resolve('');
150
+ return new Promise((resolveAnswer) => {
151
+ waiters.push((line) => resolveAnswer(line === null ? '' : line.trim()));
152
+ });
153
+ };
154
+ }
155
+
156
+ /** Multi-select prompt loop: empty accepts the default, invalid ids re-ask. */
157
+ async function askSelection(ask, out, { label, known, defaults, validate }) {
158
+ for (;;) {
159
+ const answer = await ask(`${label} [${defaults.join(', ') || 'none'}]: `);
160
+ const selection = answer === '' ? [...defaults] : parseSelection(answer);
161
+ try {
162
+ validate(selection);
163
+ return selection;
164
+ } catch (error) {
165
+ out(` ${error.message.split('\n')[0]}\n available: ${known.join(', ') || '(none)'} — or "none"\n`);
166
+ }
167
+ }
168
+ }
169
+
170
+ // ------------------------------------------------ git check-ignore sweep
171
+
172
+ /**
173
+ * PRD §6: run `git check-ignore` across all seeded paths (batched via
174
+ * --stdin) — a gitignored findings log kills the improvement loop silently.
175
+ * Not a git repo → { skipped: true }; the caller says so and moves on.
176
+ */
177
+ export function sweepGitIgnore({ targetDir, paths }) {
178
+ const inside = spawnSync('git', ['-C', targetDir, 'rev-parse', '--is-inside-work-tree'], { encoding: 'utf8' });
179
+ if (inside.status !== 0 || inside.stdout.trim() !== 'true') return { skipped: true, ignored: [] };
180
+ const r = spawnSync('git', ['-C', targetDir, 'check-ignore', '--stdin'],
181
+ { input: `${paths.join('\n')}\n`, encoding: 'utf8' });
182
+ // check-ignore exit contract: 0 = some path ignored, 1 = none, other = error.
183
+ if (r.status === 1) return { skipped: false, ignored: [] };
184
+ if (r.status !== 0) return { skipped: false, ignored: [], error: (r.stderr || 'git check-ignore failed').trim() };
185
+ return { skipped: false, ignored: r.stdout.split('\n').filter(Boolean) };
186
+ }
187
+
188
+ /**
189
+ * The seeded engine's one runtime dependency (D-002: js-yaml, resolved from
190
+ * the client's repo like any other package). Resolution is checked from the
191
+ * seeded engine file itself, so this answers the question the engine will
192
+ * ask at import time — not an approximation of it.
193
+ *
194
+ * Unresolvable is a WARN, never a refusal: the seed is correct and the fix
195
+ * is one `npm install` away. It stays a LOUD warn because every engine command
196
+ * refuses to start until the dependency is there — the whole kit is inert.
197
+ *
198
+ * It used to be louder still: an unresolved import exited 1, which the §5
199
+ * contract reads as "findings present" rather than "engine failure", and an
200
+ * agent riding those codes would quarantine-and-continue past an engine that
201
+ * never ran. The entry shims closed that (UCS-956): a module-load failure now
202
+ * exits 2, so the codes mean what they say even before the install.
203
+ */
204
+ export function checkRuntimeDependency(seededRoot, dependency = 'js-yaml') {
205
+ const anchor = join(seededRoot, 'engine', 'lib', 'load-stores.js');
206
+ try {
207
+ createRequire(anchor).resolve(dependency);
208
+ return { resolved: true, dependency };
209
+ } catch {
210
+ return { resolved: false, dependency };
211
+ }
212
+ }
213
+
214
+ /** Negation rules to offer: one `!<root>/<dir>/**` per affected seeded dir. */
215
+ export function negationRules(ignored, rootName) {
216
+ const rules = new Set();
217
+ for (const path of ignored) {
218
+ if (path.startsWith(`${rootName}/`)) {
219
+ const segments = path.split('/');
220
+ rules.add(segments.length > 2 ? `!${rootName}/${segments[1]}/**` : `!${path}`);
221
+ } else {
222
+ rules.add(`!${path}`);
223
+ }
224
+ }
225
+ return [...rules].sort();
226
+ }
227
+
228
+ // ----------------------------------------------------------------- main
229
+
230
+ export async function main(argv, { stdin = process.stdin, stdout = process.stdout, stderr = process.stderr } = {}) {
231
+ const opts = parseArgs(argv); // a UsageError reaches the harness
232
+ if (opts.help) {
233
+ stdout.write(`${USAGE}\n`);
234
+ return EXIT_CODES.CLEAN;
235
+ }
236
+ const out = (text) => stdout.write(text);
237
+ const warn = (text) => stderr.write(text);
238
+
239
+ let manifest;
240
+ try {
241
+ manifest = loadManifest(kitRoot);
242
+ } catch (error) {
243
+ // The manifest is malformed or missing — an anticipated refusal, and the
244
+ // message says which. A bug inside loadManifest is not, and goes to the
245
+ // harness with its stack. Both exit 2; nothing was seeded either way.
246
+ stderr.write(`unknown-knowledge init: ${error.message}\n`);
247
+ rethrowIfBug(error);
248
+ return EXIT_CODES.FAILURE;
249
+ }
250
+ const knownStacks = Object.keys(manifest.sections.stacks).sort();
251
+ const knownPlatforms = Object.keys(manifest.platforms).sort();
252
+
253
+ // The target must exist before anything reads it: auto-detection walks it
254
+ // (below) long before the copy engine's own guard would fire, and a typo'd
255
+ // --target should say so, not surface an ENOENT stack trace.
256
+ const target = resolve(opts.target);
257
+ if (!statSync(target, { throwIfNoEntry: false })?.isDirectory()) {
258
+ stderr.write(`unknown-knowledge init: target ${JSON.stringify(target)} is not an existing directory — `
259
+ + 'create it (or fix --target) and re-run; a seed that did not happen is never a silent pass\n');
260
+ return EXIT_CODES.FAILURE;
261
+ }
262
+ const detected = detectStacks(opts.target).filter((s) => knownStacks.includes(s));
263
+
264
+ // Resolve the three answers: flags win; --yes accepts every default
265
+ // (root: unknown-knowledge, platforms: none, stacks: the detection);
266
+ // otherwise prompt interactively (node:readline, D-002).
267
+ let rootName = opts.root ?? DEFAULT_ROOT;
268
+ let platforms = opts.platforms ?? [];
269
+ let stacks = opts.stacks ?? detected;
270
+ out(`stack auto-detection: ${detected.join(', ') || 'none (config-only)'}\n`);
271
+
272
+ const needPrompts = !opts.yes && (opts.root === null || opts.platforms === null || opts.stacks === null);
273
+ if (needPrompts) {
274
+ const rl = createInterface({ input: stdin, terminal: false });
275
+ const ask = makeAsker(rl, stdout);
276
+ try {
277
+ if (opts.root === null) {
278
+ for (;;) {
279
+ const answer = await ask(`root dir name (always visible, never dotted) [${DEFAULT_ROOT}]: `);
280
+ rootName = answer === '' ? DEFAULT_ROOT : answer;
281
+ // The engine validates (copy-payload.js §6 rules); we present.
282
+ if (!rootName.startsWith('.') && !/[/\\]/.test(rootName)) break;
283
+ out(' root dir names are single, visible path segments — never dotted (PRD §6)\n');
284
+ }
285
+ }
286
+ if (opts.platforms === null) {
287
+ out('agent platforms (thin wrappers pointing at the protocol):\n');
288
+ for (const id of knownPlatforms) out(` ${id.padEnd(12)} ${manifest.platforms[id].name}\n`);
289
+ platforms = await askSelection(ask, out, {
290
+ label: 'platforms (comma/space separated ids)',
291
+ known: knownPlatforms,
292
+ defaults: [],
293
+ validate: (sel) => assertKnownPlatforms(manifest, sel),
294
+ });
295
+ }
296
+ if (opts.stacks === null) {
297
+ out(`stacks (drives which extractor-fixture packs ship, D-009): ${knownStacks.join(', ')}\n`);
298
+ stacks = await askSelection(ask, out, {
299
+ label: 'stacks (pre-selected by auto-detection)',
300
+ known: knownStacks,
301
+ defaults: detected,
302
+ validate: (sel) => {
303
+ for (const s of sel) {
304
+ if (!knownStacks.includes(s)) throw new Error(`unknown stack ${JSON.stringify(s)}`);
305
+ }
306
+ },
307
+ });
308
+ }
309
+ } finally {
310
+ rl.close();
311
+ }
312
+ }
313
+
314
+ // ---- scaffold: one call into each landed seam (KK-17 copy engine,
315
+ // KK-18 wrapper generator) — their refusal/collision semantics apply.
316
+ let result;
317
+ let wrappers;
318
+ try {
319
+ assertKnownPlatforms(manifest, platforms); // refuse BEFORE seeding
320
+ result = copyPayload({ kitRoot, targetDir: opts.target, rootName, stacks });
321
+ try {
322
+ wrappers = generateWrappers({ kitRoot, targetDir: opts.target, rootName, platforms });
323
+ } catch (error) {
324
+ // The seed already landed; shared files may carry sentinel appends, so
325
+ // a silent rollback could destroy user bytes — name the partial state
326
+ // instead (a retry refuses on the existing root by design, §6).
327
+ error.message = `${error.message}\n the store seed was already created at ${result.root} — remove it `
328
+ + '(and any wrapper sentinel blocks) before retrying; init refuses on an existing root';
329
+ throw error;
330
+ }
331
+ } catch (error) {
332
+ // Name the partial state FIRST, whatever went wrong: the seed may already
333
+ // have landed, and a silent rollback could destroy user bytes. The message
334
+ // carries that note (annotated above), and a stack trace would bury it.
335
+ const kind = error instanceof SeedRefusal ? 'refused' : 'error';
336
+ stderr.write(`unknown-knowledge init: ${kind}: ${error.message}\n`);
337
+ // Then: a SeedRefusal and the copy engine's plain Errors are anticipated,
338
+ // and this catch speaks for them. A TypeError is a bug — the harness gets
339
+ // it, and prints the stack nobody could debug without. Both exit 2.
340
+ rethrowIfBug(error);
341
+ return EXIT_CODES.FAILURE;
342
+ }
343
+
344
+ out(`seeded ${result.rootName}/ (kit ${result.version}, stacks: ${result.stacks.join(', ') || 'none'}) `
345
+ + `— ${result.files.length} files at ${result.root}\n`);
346
+ for (const w of wrappers) {
347
+ out(w.action === 'skipped'
348
+ ? `wrapper ${w.platform}: skipped ${w.target} — ${w.reason}\n`
349
+ : `wrapper ${w.platform}: ${w.action} ${w.target}\n`);
350
+ }
351
+
352
+ // ---- git check-ignore sweep over every seeded path (PRD §6).
353
+ const seededPaths = [
354
+ ...result.files.map((f) => `${result.rootName}/${f}`),
355
+ ...wrappers.filter((w) => w.action !== 'skipped').map((w) => w.target),
356
+ ];
357
+ const sweep = sweepGitIgnore({ targetDir: opts.target, paths: seededPaths });
358
+ if (sweep.skipped) {
359
+ warn('WARN: not a git repo — skipping the git check-ignore sweep; once the repo exists, '
360
+ + `verify none of the seeded paths (especially ${result.rootName}/logs/) are gitignored\n`);
361
+ } else if (sweep.error) {
362
+ warn(`WARN: git check-ignore sweep failed (${sweep.error}) — verify the seeded paths are not gitignored\n`);
363
+ } else if (sweep.ignored.length > 0) {
364
+ warn(`WARN: ${sweep.ignored.length} seeded path(s) are gitignored — a gitignored findings log kills `
365
+ + 'the improvement loop silently. Ignored:\n');
366
+ for (const path of sweep.ignored) warn(` ${path}\n`);
367
+ warn('add negation rule(s) to your .gitignore:\n');
368
+ for (const rule of negationRules(sweep.ignored, result.rootName)) warn(` ${rule}\n`);
369
+ }
370
+
371
+ // ---- runtime-dependency preflight: the handoff below tells the human to
372
+ // run phase 2, and every engine command it reaches for imports js-yaml.
373
+ const dep = checkRuntimeDependency(result.root);
374
+ if (!dep.resolved) {
375
+ warn(`WARN: the seeded engine's one runtime dependency, ${dep.dependency}, does not resolve `
376
+ + `from ${result.rootName}/engine/ — every engine command will fail to start until it does.\n`);
377
+ warn(` install it before phase 2: npm install --save-dev ${dep.dependency}\n`);
378
+ warn(' (until then every engine command exits 2 — engine failure, never a silent pass)\n');
379
+ }
380
+
381
+ // ---- D-009 later-stacks warning (also carried by the seeded README) +
382
+ // the phase-2 handoff (D-019 skill name). PRD §6, verbatim intent.
383
+ out(`\nwarning: ${LATER_STACKS_WARNING}\n`);
384
+ out('\nnow run /knowledge-bootstrap in your agent — phase 2 surveys the repo and populates the stores.\n');
385
+ return EXIT_CODES.CLEAN;
386
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli/init-copy.js — the entry shim (UCS-956/UCS-950).
4
+ *
5
+ * Statically imports NOTHING. Node exits 1 on an unhandled ES module load
6
+ * error, and exit 1 means FINDINGS (PRD §5) — so a SyntaxError or a missing
7
+ * dependency in the init path would tell an agent the seed ran and found
8
+ * problems. Init never emits findings at all: exit 1 must be unreachable.
9
+ *
10
+ * The command lives in commands/init-copy.js. The invocation path is unchanged —
11
+ * this file is what `npx unknown-knowledge` runs.
12
+ */
13
+ try {
14
+ const [{ boot }, command] = await Promise.all([
15
+ import('../payload/engine/lib/boot.js'),
16
+ import('./commands/init-copy.js'),
17
+ ]);
18
+ process.exitCode = await boot('init-copy', command);
19
+ } catch (error) {
20
+ // The CLI could not be loaded, so nothing was seeded. Exit 2 — never 1.
21
+ // Hardcoded: reading it from lib/exit-codes.js is what may have just failed.
22
+ process.stderr.write(`init-copy: internal failure — nothing was seeded\n${error?.stack ?? error}\n`);
23
+ process.exitCode = 2;
24
+ }
package/cli/init.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli/init.js — the entry shim (UCS-956/UCS-950).
4
+ *
5
+ * Statically imports NOTHING. Node exits 1 on an unhandled ES module load
6
+ * error, and exit 1 means FINDINGS (PRD §5) — so a SyntaxError or a missing
7
+ * dependency in the init path would tell an agent the seed ran and found
8
+ * problems. Init never emits findings at all: exit 1 must be unreachable.
9
+ *
10
+ * The command lives in commands/init.js. The invocation path is unchanged —
11
+ * this file is what `npx unknown-knowledge` runs.
12
+ */
13
+ try {
14
+ const [{ boot }, command] = await Promise.all([
15
+ import('../payload/engine/lib/boot.js'),
16
+ import('./commands/init.js'),
17
+ ]);
18
+ process.exitCode = await boot('unknown-knowledge init', command);
19
+ } catch (error) {
20
+ // The CLI could not be loaded, so nothing was seeded. Exit 2 — never 1.
21
+ // Hardcoded: reading it from lib/exit-codes.js is what may have just failed.
22
+ process.stderr.write(`unknown-knowledge init: internal failure — nothing was seeded\n${error?.stack ?? error}\n`);
23
+ process.exitCode = 2;
24
+ }