unknown-knowledge 2.1.0 → 3.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,12 @@ branched and merged by your normal PRs.
14
14
 
15
15
  ## Quickstart
16
16
 
17
+ The 3.0 pilot is available explicitly with
18
+ `npx unknown-knowledge@3.0.0-rc.1 init`. The stable `latest` channel remains
19
+ 2.1.0. Existing installations should follow the
20
+ [migration guide](https://github.com/doterodesign/unknown-knowledge/blob/main/docs/migration-3.md);
21
+ init refuses existing roots and does not overwrite client-owned records.
22
+
17
23
  ```bash
18
24
  cd your-repo
19
25
  npx unknown-knowledge init # seeds unknown-knowledge/ and an agent wrapper
@@ -52,13 +58,15 @@ wrong parse is a false all-clear. What it could not read is recorded in
52
58
 
53
59
  ## The engine
54
60
 
55
- Ten command-line surfaces. JavaScript with JSDoc types, zero build step, one
61
+ Twelve command-line surfaces. JavaScript with JSDoc types, zero build step, one
56
62
  dependency (D-022).
57
63
 
58
64
  | Command | Answers |
59
65
  | --- | --- |
60
66
  | `validate.js` | is the store structurally sound? |
61
67
  | `validate-values.js` | do the Concepts still match the code they point at? |
68
+ | `commit-check.js` | do both whole-store validators pass the commit gate? |
69
+ | `reverse-staged.js` | what governed each staged path before and after this commit? |
62
70
  | `preflight.js` | which Concepts may this agent trust, right now? |
63
71
  | `resolve.js` | what does the store know about these terms or paths? |
64
72
  | `survey-map.js` | what is in this repo, and what could not be surveyed? |
@@ -84,6 +92,12 @@ exit `1`, that agent would walk straight past a check that never happened. So a
84
92
  crash always exits `2`, and a test enumerates every surface, forces a bug into
85
93
  each, and proves it.
86
94
 
95
+ The resolver also accepts repeatable `--path` values for lossless filename
96
+ transport, for example `--path 'src/a,b.ts' --path 'src/my file.ts'`. Legacy
97
+ comma-separated `--paths` remains supported; mixing the forms fails with
98
+ exit 2. See the [complete-filename and safe programmatic invocation guide](payload/docs/README.md#reverse-lookup-for-complete-filenames).
99
+ This is an additive MINOR CLI surface change under D-021.
100
+
87
101
  The reverse audit is advisory: its findings are proposals for human review, and
88
102
  never a gate. A human may opt in with `--fail-on-findings`, and that is never a
89
103
  shipped CI default.
@@ -91,8 +105,8 @@ shipped CI default.
91
105
  ## Guarantees
92
106
 
93
107
  - **The engine never executes your code** (D-014). No `eval`, no importing your
94
- modules, no spawning your build. Parsing is lexical; the one subprocess it
95
- runs is `git ls-files`.
108
+ modules, no spawning your build. Parsing is lexical; subprocesses invoke
109
+ Git only, to list tracked files and read the proposed commit snapshot.
96
110
  - **No network, ever.** Nothing is uploaded, and nothing is fetched.
97
111
  - **Deterministic.** Same tree in, byte-identical output out. Dates are
98
112
  injected, never read from the wall clock, so a report is reproducible from
@@ -102,13 +116,49 @@ shipped CI default.
102
116
 
103
117
  ## Hooks — the protocol, enforced mechanically
104
118
 
105
- Two POSIX-sh hooks seed under `hooks/`: `pre-commit` runs the blocking
106
- validator before a commit exists, and `reverse-lookup` runs the `--paths`
107
- lookup over your staged diff, so the knowledge governing the files you touched
119
+ Two POSIX-sh hooks seed under `hooks/`: `pre-commit` runs both whole-store
120
+ validators through `engine/commit-check.js` before a commit exists, and `reverse-lookup` invokes `engine/reverse-staged.js`
121
+ over your staged diff, so the knowledge governing the files you touched
108
122
  surfaces without anyone remembering to ask. Each is a thin wrapper — it invokes
109
123
  one engine command and exits with its code, unchanged — and neither reads a
110
124
  bypass variable, because a hook with an off switch enforces nothing.
111
125
 
126
+ The gate reports each check by name. It exits 0 only when both checks pass;
127
+ findings exit 1, and a failed or never-run check exits 2 even if the other check
128
+ is clean or has findings. Both checks always run over the whole store (D-012).
129
+ Reverse lookup is advisory attribution; its results never restrict validation
130
+ or prove that the agent updated the store. It reads NUL-delimited Git records
131
+ and includes additions, modifications, type changes, deletions and both paths
132
+ of detected copies and renames. Detection uses 50% similarity and a fixed
133
+ 1000-candidate exhaustive-search limit; above it, Git may report additions and
134
+ deletions instead of a rename/copy relationship. Local Git limits cannot
135
+ change these settings. Each complete path is passed to the resolver
136
+ with `--path=value`, preserving spaces, commas, quotes, tabs and newlines.
137
+
138
+ Attribution prints a `staged attribution: candidate <tree-id>` section followed
139
+ by resolver JSON, then a `before <tree-id>` section for HEAD when it exists.
140
+ Both sections use the same complete path set; each reports only that snapshot's
141
+ own pointers. A staged pointer repair therefore cannot erase the old path's
142
+ previous governance. Before evidence is historical navigation, not a current
143
+ trust verdict. An empty staged diff produces no output and exits 0, including
144
+ an unborn repository with nothing staged. Git, resolver and snapshot failures
145
+ exit 2; attribution may be incomplete and must not be treated as a clean lookup.
146
+ These lexical checks detect store and vocabulary drift. Application behavior
147
+ remains the application's test suite's responsibility.
148
+
149
+ The gate validates an isolated copy of the Git index. Source, stores and
150
+ repo-relative rules come from the same candidate, so unstaged repairs cannot
151
+ hide broken staged bytes and unstaged edits cannot introduce findings. It
152
+ never stashes, resets or restages local work. Installed engine code, schemas
153
+ and runtime dependencies stay on the host; untracked evidence is never copied
154
+ into the candidate. Snapshot preparation and cleanup failures block with exit 2.
155
+ Real installed-hook tests check partial staging, committed bytes and cleanup.
156
+
157
+ Git submodules, symlinks that escape the snapshot, and non-UTF-8 path names
158
+ or symlink targets are refused explicitly when their evidence cannot be represented faithfully. It reads raw blobs without
159
+ checkout filters or archive attributes. Install required runtime dependencies
160
+ before committing; keep whole-store checks on the actual merge candidate in CI.
161
+
112
162
  They **seed but do not install**: `init` never writes `.git/`, so wiring them is
113
163
  your act, not the kit's. Git runs a hook only if it is executable, and the copy
114
164
  engine seeds bytes rather than modes — so set the bit when you wire it:
@@ -60,9 +60,8 @@ unconditional:
60
60
  # Git hooks that enforce the protocol mechanically (UCS-1157): blocking
61
61
  # validation before a commit exists, and automatic reverse lookup over
62
62
  # the staged diff. They are THIN WRAPPERS — each invokes one engine
63
- # command and exits with its code, unchanged so the tested surface is
64
- # the wrapped command, and the wiring is reviewed the way the per-IDE
65
- # wrappers are.
63
+ # command and exits with its code, unchanged. Real installed-hook Git
64
+ # tests exercise the pre-commit gate as well as the engine commands.
66
65
  #
67
66
  # They SEED but do not INSTALL. init never writes .git/ (the client's own
68
67
  # config is the client's), so these land at <root>/hooks/ and the client
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unknown-knowledge",
3
- "version": "2.1.0",
3
+ "version": "3.0.0-rc.1",
4
4
  "description": "Free OSS kit that stands up self-improving knowledge-base + ontology structures in any codebase: three governed YAML stores, a deterministic engine, and an agent protocol.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -27,6 +27,40 @@ paths arrived from the kit and which your own loop produced.
27
27
 
28
28
  ## Running the gates
29
29
 
30
+ ### Following replacements from an old result
31
+
32
+ Resolver JSON leaf projections include `superseded-by`, a stable array that
33
+ is empty when no loaded leaf directly supersedes the match. It appears on
34
+ query leaves, concept knowledge entry points, path knowledge, scope exclusions
35
+ and document gather rows. Each reference has these fields:
36
+
37
+ | Fields | Meaning |
38
+ |---|---|
39
+ | `id`, `notation`, `heading`, `file` | Accession and navigational metadata; notation is only a legacy display label |
40
+ | `stage`, `time`, `downranked`, `demotions` | The target's normal lifecycle and freshness metadata; `time.verdict` checks freshness only, not trust |
41
+ | `applies` | Declared jurisdictions, sorted; an empty array means universal |
42
+
43
+ This is an additive output contract under D-021. Existing fields and scores
44
+ retain their meaning. Authors write only `relates.supersedes` on the successor;
45
+ the loader derives the inverse from the store on every load, without reading
46
+ or storing reciprocal edges in `knowledge/derived/`.
47
+
48
+ References are deduplicated and sorted by accession. They contain no body,
49
+ excerpt, score or nested edges. Follow one hop at a time, keeping visited IDs
50
+ to avoid revisiting a cycle. Multiple successors remain explicit; order never
51
+ selects a winner. The predecessor remains available for historical requests.
52
+
53
+ Successor scope is declared metadata, not an applicability verdict. Compare
54
+ it with the request's jurisdiction; without a jurisdiction, establish whether
55
+ the target applies before selecting an answer. A scoped-out successor stays
56
+ visible as navigation and does not enter the ranked results merely because
57
+ it supersedes a match. Draft and stale successors keep their normal flags.
58
+ Run `preflight.js --leaves <IDs>` for consulted targets and read their cited
59
+ sources. Existing health gates report dangling references; structural
60
+ `ref-cycle` findings quarantine every leaf in a supersession cycle.
61
+
62
+ ### Validation commands
63
+
30
64
  The engine is plain Node (≥ 22, no build step) with one library
31
65
  dependency, `js-yaml`, resolved from your repo like any other package: if
32
66
  your repo does not already carry it, run `npm install --save-dev js-yaml`
@@ -48,6 +82,51 @@ proposes draft concepts for anchors the map does not cover yet; a human
48
82
  reviews every draft. Run it on the steward cadence, never as a gate
49
83
  (`docs/steward-guide.md`).
50
84
 
85
+ ## Reverse lookup for complete filenames
86
+
87
+ Use one `--path` per filename to find the concepts and knowledge leaves that
88
+ govern it:
89
+
90
+ ```sh
91
+ node unknown-knowledge/engine/resolve.js --root . --path 'src/a,b.ts' --path 'src/my file.ts' --json
92
+ ```
93
+
94
+ Both `--path value` and `--path=value` work. Use the equals form for a name
95
+ beginning with `--`, for example `--path=--draft.ts`. Each value stays one
96
+ complete path: commas, quotes, tabs, newlines, Unicode, leading/trailing
97
+ whitespace and POSIX backslashes are literal data. Paths still use POSIX
98
+ normalization for `.`, `..`, repeated `/` and absolute paths relative to
99
+ `--root`. Complete normalized paths are deduplicated and sorted.
100
+
101
+ Programmatic callers should pass an argument array with no shell. This
102
+ example assumes `repoRoot` and `changedPaths` contain the root and filename
103
+ strings already obtained by the caller:
104
+
105
+ ```js
106
+ import { spawnSync } from 'node:child_process';
107
+ import { resolve } from 'node:path';
108
+
109
+ const result = spawnSync(process.execPath, [
110
+ resolve(repoRoot, 'unknown-knowledge/engine/resolve.js'),
111
+ '--root', repoRoot, '--json',
112
+ ...changedPaths.map((path) => `--path=${path}`),
113
+ ], { encoding: 'utf8', shell: false });
114
+ if (result.error) throw result.error;
115
+ if (result.status !== 0) throw new Error(result.stderr || 'Reverse lookup did not complete');
116
+ const attribution = JSON.parse(result.stdout);
117
+ ```
118
+
119
+ Do not join filenames into a shell command or split them on commas or
120
+ newlines. Skip the invocation when there are no changed paths. An empty or
121
+ missing `--path` value, a repo-root-only path, or mixing `--path` with
122
+ `--paths`, query terms or `--doc` fails with a usage message and exit 2.
123
+ An unmatched path is a normal result: exit 0 with empty attribution.
124
+
125
+ Legacy `--paths a.ts,b.ts` remains supported, including its comma splitting,
126
+ whitespace trimming and backslash conversion. For equivalent ordinary path
127
+ sets, both inputs return the same attribution and deterministic output.
128
+ The repeatable flag is an additive MINOR surface change under D-021.
129
+
51
130
  ## How the loop works
52
131
 
53
132
  The runtime contract — `RESOLVE → PREFLIGHT → GATHER → ACT → RECORD` — lives
@@ -68,6 +147,62 @@ authoring README seeded beside the packs) — there is no update channel. The
68
147
  governed path for teaching the engine a new anchor shape is
69
148
  `protocol/new-kind-pipeline.md`.
70
149
 
150
+ ## Commit gate (opt-in)
151
+
152
+ The seeded `hooks/pre-commit` invokes `engine/commit-check.js`, which runs both
153
+ whole-store structural and value validation. Each check reports its name and
154
+ status. Exit 0 means both pass; 1 means findings; 2 means a check failed or
155
+ could not run. Failure dominates findings and success. There is no bypass
156
+ variable. Validators inspect source lexically and never execute application
157
+ code; application behavior still needs its own tests.
158
+
159
+ Install from the repo root when you choose (init never edits `.git/`):
160
+
161
+ ```sh
162
+ chmod +x unknown-knowledge/hooks/pre-commit unknown-knowledge/hooks/reverse-lookup
163
+ ln -s ../../unknown-knowledge/hooks/pre-commit .git/hooks/pre-commit
164
+ ln -s ../../unknown-knowledge/hooks/reverse-lookup .git/hooks/prepare-commit-msg
165
+ ```
166
+
167
+ Use your chosen kit directory name if different; `KIT_DIR` selects that name.
168
+ Preserve existing hooks when integrating this invocation into your setup.
169
+ `hooks/reverse-lookup` invokes `engine/reverse-staged.js --root .` for advisory
170
+ attribution. Git runs it through the event-named `prepare-commit-msg` link;
171
+ putting `reverse-lookup` beside `pre-commit` under `core.hooksPath` alone does
172
+ not run it. Preserve your existing hooks and call it explicitly from an event
173
+ hook when using that configuration.
174
+
175
+ The engine parses NUL-delimited Git status/path records, including deletions
176
+ and both paths of detected renames and copies. Detection uses 50% similarity
177
+ and a fixed 1000-candidate exhaustive-search limit. Beyond it, Git may emit
178
+ additions/deletions instead of rename/copy relationships; whole-store checks
179
+ remain authoritative. It passes complete filenames
180
+ to the resolver as repeated `--path=value` arguments, with no shell expansion.
181
+ It prints `staged attribution: candidate <tree-id>` followed by resolver JSON,
182
+ then a `before <tree-id>` section when HEAD exists. Both sections attribute the
183
+ same deduplicated path set against their own snapshot. Previous pointers stay
184
+ visible even when the staged store removes or repairs them. Historical results
185
+ are navigation evidence, not current trust verdicts or proof of a store update.
186
+
187
+ No staged changes means quiet exit 0, including an empty unborn repository.
188
+ A failed Git read, resolver or snapshot operation exits 2, never a zero-hit
189
+ success. Unsupported history can also refuse attribution even when candidate
190
+ validation passes. Attribution never selects a subset for the whole-store
191
+ gate. Real installed-hook commits test this behavior, including pointer repairs,
192
+ partial staging and unusual filenames.
193
+
194
+ The gate checks an isolated Git index snapshot. Both validators read staged
195
+ source, stores and repo-relative rules; unstaged and untracked evidence cannot
196
+ affect their findings. Local work is never stashed, reset or restaged. Installed
197
+ engine code, schemas and dependencies run from the host and are not linked into
198
+ the evidence snapshot. Missing dependencies, snapshot preparation failures and
199
+ cleanup failures block with exit 2. Git submodules and escaping symlinks are
200
+ unsupported and refused explicitly, as are non-UTF-8 path names or symlink targets. Raw blob reads
201
+ do not invoke checkout
202
+ filters or apply archive attributes. Run from the repository root and install
203
+ the runtime dependencies before committing. Check the actual merge candidate
204
+ in CI as described below.
205
+
71
206
  ## CI
72
207
 
73
208
  Session-level preflight is a sufficient gate for a small team, not for
@@ -33,6 +33,31 @@ never executes your code and never touches the network
33
33
  (`docs/boundaries.md`) — so the jobs need no secrets, tokens, or extra
34
34
  permissions.
35
35
 
36
+ ## Commit gate and CI evidence
37
+
38
+ The opt-in pre-commit hook calls `engine/commit-check.js --root .`. This runs
39
+ both whole-store validators, reports each check, and returns 2 for any failed
40
+ or never-run check, otherwise 1 for findings, otherwise 0. It reads an isolated snapshot of the Git index, including staged source
41
+ files and layout selection. An unstaged repair cannot hide a broken candidate.
42
+ The installed engine and dependencies remain separate runtime inputs.
43
+
44
+ In CI, check out the actual committed merge candidate and run both validators
45
+ against that checkout, with no generated or repaired governed files between
46
+ checkout and validation. `node unknown-knowledge/engine/commit-check.js --root .`
47
+ also runs both checks even if one fails. Reverse lookup remains advisory;
48
+ changed-path attribution never limits the whole-store correctness gate.
49
+ Application behavior tests remain separate from these lexical checks.
50
+
51
+ ## Existing application store names
52
+
53
+ If root-level application `knowledge/` or `ontology/` coexists with the seeded
54
+ kit, commit `.unknown-knowledge.json` at the repository root containing
55
+ `{"kitRoot":"unknown-knowledge"}`. Use `{"kitRoot":"."}` to select root-level
56
+ stores instead. These are the two supported layouts. Keep `--root` at the
57
+ repository root: source pointers are repository-relative. Missing selection in
58
+ an ambiguous layout or invalid configuration stops the check. Stage selection
59
+ with the migration; each candidate/before tree uses its own tracked choice.
60
+
36
61
  ## GitHub Actions
37
62
 
38
63
  ```yaml
@@ -0,0 +1,49 @@
1
+ /** Whole-store commit gate over one isolated proposed Git snapshot. */
2
+ import process from 'node:process';
3
+ import { existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { parseArgs as parseFlags, rethrowIfBug, runCli } from '../lib/cli.js';
6
+ import { EXIT_CODES } from '../lib/exit-codes.js';
7
+ import { withCommitSnapshot } from '../lib/commit-snapshot.js';
8
+ import { locateKitRoot } from '../lib/kit-root.js';
9
+
10
+ export const USAGE = 'usage: commit-check [--root <repo-root>]';
11
+
12
+ /** @param {string[]} argv @returns {Promise<number>} */
13
+ export async function main(argv) {
14
+ const { options } = parseFlags(argv, { value: ['root'] });
15
+ return withCommitSnapshot(options.root ?? process.cwd(), ({ candidate }) => checkCandidate(candidate.root));
16
+ }
17
+
18
+ async function checkCandidate(root) {
19
+ let kitRoot;
20
+ try {
21
+ kitRoot = locateKitRoot(root);
22
+ } catch (error) {
23
+ rethrowIfBug(error);
24
+ // Layout refusals describe candidate evidence, not the random directory
25
+ // used to hold it. Operational cleanup failures still name their artifact.
26
+ process.stderr.write(`commit-check: snapshot: ${error.message.replaceAll(root, '<candidate>')}\n`);
27
+ return EXIT_CODES.FAILURE;
28
+ }
29
+ if (!['ontology', 'knowledge', 'decisions'].some((store) => existsSync(join(kitRoot, store)))) {
30
+ throw new Error('snapshot: no governed stores in the candidate; stage the kit before checking a commit');
31
+ }
32
+ const args = ['--root', root];
33
+ let outcome = EXIT_CODES.CLEAN;
34
+ // Literal imports load only versioned engine code (D-005/D-014). Each check
35
+ // loads independently, so a broken validator cannot hide the other's result.
36
+ for (const [name, load] of [
37
+ ['validate', () => import('./validate.js')],
38
+ ['validate-values', () => import('./validate-values.js')],
39
+ ]) {
40
+ const status = await runCli(name, async (argv) => {
41
+ const command = await load();
42
+ return command.main(argv);
43
+ }, { usage: USAGE, argv: args });
44
+ process.stderr.write(`commit-check: ${name}: ${status === EXIT_CODES.CLEAN ? 'clean' : status === EXIT_CODES.FINDINGS ? 'findings' : 'failure'} (exit ${status})\n`);
45
+ // Failure (2) dominates findings (1), which dominate clean (0).
46
+ outcome = Math.max(outcome, status);
47
+ }
48
+ return outcome;
49
+ }