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 +56 -6
- package/cli/kit.manifest.yaml +2 -3
- package/package.json +1 -1
- package/payload/docs/README.md +135 -0
- package/payload/docs/ci-wiring.md +25 -0
- package/payload/engine/commands/commit-check.js +49 -0
- package/payload/engine/commands/preflight.js +29 -430
- package/payload/engine/commands/resolve.js +71 -35
- package/payload/engine/commands/reverse-staged.js +30 -0
- package/payload/engine/commands/validate-values.js +19 -3
- package/payload/engine/commands/validate.js +51 -1
- package/payload/engine/commit-check.js +12 -0
- package/payload/engine/lib/commit-snapshot.js +155 -0
- package/payload/engine/lib/coverage.js +1 -0
- package/payload/engine/lib/kit-root.js +25 -3
- package/payload/engine/lib/load-stores.js +14 -0
- package/payload/engine/lib/preflight.js +122 -0
- package/payload/engine/lib/time-verdicts.js +5 -6
- package/payload/engine/lib/verdicts.js +260 -0
- package/payload/engine/reverse-staged.js +12 -0
- package/payload/hooks/pre-commit +7 -7
- package/payload/hooks/reverse-lookup +7 -61
- package/payload/protocol/AGENTS.md +321 -52
- package/payload/protocol/skills/kb-build.md +58 -3
- package/payload/protocol/skills/knowledge-reflect.md +95 -8
- package/payload/wrappers/cursor.mdc +7 -8
- package/payload/wrappers/pointer.md +6 -7
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
|
-
|
|
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;
|
|
95
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
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:
|
package/cli/kit.manifest.yaml
CHANGED
|
@@ -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
|
|
64
|
-
#
|
|
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": "
|
|
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": {
|
package/payload/docs/README.md
CHANGED
|
@@ -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
|
+
}
|