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.
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.md +167 -0
- package/cli/.gitkeep +0 -0
- package/cli/commands/init-copy.js +90 -0
- package/cli/commands/init.js +386 -0
- package/cli/init-copy.js +24 -0
- package/cli/init.js +24 -0
- package/cli/kit.manifest.yaml +256 -0
- package/cli/lib/copy-payload.js +334 -0
- package/cli/lib/generate-wrappers.js +162 -0
- package/package.json +36 -0
- package/payload/adapter-fixtures/README.md +53 -0
- package/payload/adapter-fixtures/html/EXPECTED.yaml +50 -0
- package/payload/adapter-fixtures/html/sample.html +38 -0
- package/payload/adapter-fixtures/md/EXPECTED.yaml +65 -0
- package/payload/adapter-fixtures/md/sample.md +32 -0
- package/payload/adapter-fixtures/pdf/EXPECTED.yaml +45 -0
- package/payload/adapter-fixtures/pdf/sample.pdf +0 -0
- package/payload/adapter-fixtures/txt/EXPECTED.yaml +31 -0
- package/payload/adapter-fixtures/txt/sample.txt +18 -0
- package/payload/docs/README.md +102 -0
- package/payload/docs/boundaries.md +60 -0
- package/payload/docs/ci-wiring.md +109 -0
- package/payload/docs/steward-guide.md +238 -0
- package/payload/engine/audit.js +34 -0
- package/payload/engine/commands/audit.js +289 -0
- package/payload/engine/commands/derive.js +334 -0
- package/payload/engine/commands/ingest.js +124 -0
- package/payload/engine/commands/log-entry.js +85 -0
- package/payload/engine/commands/phoenix.js +206 -0
- package/payload/engine/commands/preflight.js +530 -0
- package/payload/engine/commands/resolve.js +1678 -0
- package/payload/engine/commands/survey-map.js +351 -0
- package/payload/engine/commands/validate-values.js +315 -0
- package/payload/engine/commands/validate.js +1426 -0
- package/payload/engine/derive.js +34 -0
- package/payload/engine/ingest.js +34 -0
- package/payload/engine/lib/anchor-signatures.js +126 -0
- package/payload/engine/lib/boot.js +39 -0
- package/payload/engine/lib/call-numbers.js +133 -0
- package/payload/engine/lib/cli.js +147 -0
- package/payload/engine/lib/coverage.js +849 -0
- package/payload/engine/lib/decomposition.js +225 -0
- package/payload/engine/lib/derived.js +494 -0
- package/payload/engine/lib/engine-refusal.js +40 -0
- package/payload/engine/lib/exit-codes.js +14 -0
- package/payload/engine/lib/extractor-kinds.js +955 -0
- package/payload/engine/lib/format-adapters.js +802 -0
- package/payload/engine/lib/id-grammars.js +178 -0
- package/payload/engine/lib/iso-date.js +55 -0
- package/payload/engine/lib/kit-root.js +101 -0
- package/payload/engine/lib/load-stores.js +1624 -0
- package/payload/engine/lib/log-entry.js +196 -0
- package/payload/engine/lib/phoenix.js +628 -0
- package/payload/engine/lib/scoring.js +150 -0
- package/payload/engine/lib/suppressions.js +172 -0
- package/payload/engine/lib/time-verdicts.js +282 -0
- package/payload/engine/lib/usage-error.js +14 -0
- package/payload/engine/lib/validate-record.js +504 -0
- package/payload/engine/log-entry.js +34 -0
- package/payload/engine/phoenix.js +39 -0
- package/payload/engine/preflight.js +34 -0
- package/payload/engine/resolve.js +34 -0
- package/payload/engine/survey-map.js +34 -0
- package/payload/engine/validate-values.js +34 -0
- package/payload/engine/validate.js +34 -0
- package/payload/extractor-fixtures/.gitkeep +0 -0
- package/payload/extractor-fixtures/README.md +29 -0
- package/payload/extractor-fixtures/swift/strings-keys/EXPECTED.yaml +8 -0
- package/payload/extractor-fixtures/swift/strings-keys/sample.strings +15 -0
- package/payload/extractor-fixtures/swift/swift-const-array/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/swift/swift-const-array/sample.swift +21 -0
- package/payload/extractor-fixtures/swift/swift-enum/EXPECTED.yaml +8 -0
- package/payload/extractor-fixtures/swift/swift-enum/sample.swift +30 -0
- package/payload/extractor-fixtures/swift/yaml-keys/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/swift/yaml-keys/sample.yaml +23 -0
- package/payload/extractor-fixtures/swift/yaml-map-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/swift/yaml-map-keys/sample.yaml +15 -0
- package/payload/extractor-fixtures/ts/dir-modules/EXPECTED.yaml +12 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/alpha.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/beta.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/gamma.widget.ts +1 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/helpers/format.ts +3 -0
- package/payload/extractor-fixtures/ts/dir-modules/sample-modules/widgets.test.ts +2 -0
- package/payload/extractor-fixtures/ts/json-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/json-keys/sample.json +5 -0
- package/payload/extractor-fixtures/ts/json-map-keys/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/json-map-keys/sample.json +13 -0
- package/payload/extractor-fixtures/ts/ts-const-array/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/ts/ts-const-array/sample.ts +13 -0
- package/payload/extractor-fixtures/ts/ts-enum/EXPECTED.yaml +7 -0
- package/payload/extractor-fixtures/ts/ts-enum/sample.ts +11 -0
- package/payload/extractor-fixtures/ts/ts-object-keys/EXPECTED.yaml +6 -0
- package/payload/extractor-fixtures/ts/ts-object-keys/sample.tsx +23 -0
- package/payload/extractor-fixtures/ts/ts-union/EXPECTED.yaml +5 -0
- package/payload/extractor-fixtures/ts/ts-union/sample.ts +9 -0
- package/payload/hooks/pre-commit +37 -0
- package/payload/hooks/reverse-lookup +66 -0
- package/payload/package.json +3 -0
- package/payload/protocol/.gitkeep +0 -0
- package/payload/protocol/AGENTS.md +239 -0
- package/payload/protocol/derived-layer.md +174 -0
- package/payload/protocol/new-kind-pipeline.md +179 -0
- package/payload/protocol/registry-warrant.md +162 -0
- package/payload/protocol/skills/kb-build.md +303 -0
- package/payload/protocol/skills/knowledge-audit.md +183 -0
- package/payload/protocol/skills/knowledge-bootstrap.md +229 -0
- package/payload/protocol/skills/knowledge-reflect.md +397 -0
- package/payload/schemas/catalog.schema.json +32 -0
- package/payload/schemas/decision-entry.schema.json +122 -0
- package/payload/schemas/finding.schema.json +77 -0
- package/payload/schemas/gap.schema.json +52 -0
- package/payload/schemas/graduation-categories.schema.json +64 -0
- package/payload/schemas/knowledge-leaf.schema.json +194 -0
- package/payload/schemas/miss.schema.json +45 -0
- package/payload/schemas/ontology-concept.schema.json +115 -0
- package/payload/schemas/phoenix-event.schema.json +76 -0
- package/payload/schemas/registry.schema.json +57 -0
- package/payload/schemas/rules.schema.json +14 -0
- package/payload/schemas/survey-scope.schema.json +23 -0
- package/payload/templates/decisions/_catalog.yaml +7 -0
- package/payload/templates/decisions/_registries/graduation-categories.yaml +42 -0
- package/payload/templates/decisions/phoenix-event.yaml +74 -0
- package/payload/templates/decisions/reflect-mint-proposal.yaml +100 -0
- package/payload/templates/decisions/registry-minting.yaml +58 -0
- package/payload/templates/decisions/trust-graduation.yaml +120 -0
- package/payload/templates/decisions/trust-revocation.yaml +106 -0
- package/payload/templates/knowledge/_catalog.yaml +9 -0
- package/payload/templates/knowledge/_registries/anchor.yaml +42 -0
- package/payload/templates/knowledge/_registries/authority-tiers.yaml +32 -0
- package/payload/templates/knowledge/_registries/domains.yaml +43 -0
- package/payload/templates/knowledge/_registries/form.yaml +38 -0
- package/payload/templates/knowledge/_registries/jurisdictions.yaml +20 -0
- package/payload/templates/knowledge/_registries/operations.yaml +18 -0
- package/payload/templates/knowledge/_registries/stage.yaml +53 -0
- package/payload/templates/knowledge/_rules.yaml +6 -0
- package/payload/templates/new-kind/README.md +107 -0
- package/payload/templates/new-kind/descriptor.example.yaml +18 -0
- package/payload/templates/new-kind/fixture/EXPECTED.yaml +6 -0
- package/payload/templates/new-kind/fixture/demo-anchor.list +2 -0
- package/payload/templates/new-kind/fixture/sample.list +7 -0
- package/payload/templates/new-kind/parser.example.js +98 -0
- package/payload/templates/ontology/_catalog.yaml +6 -0
- package/payload/templates/ontology/_rules.yaml +6 -0
- package/payload/wrappers/cursor.mdc +15 -0
- 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
|
+
}
|
package/cli/init-copy.js
ADDED
|
@@ -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
|
+
}
|