knodin 0.10.8 → 0.12.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/dist/bin/cli.js +41 -4
- package/dist/src/cli-model.js +1 -0
- package/dist/src/compact-structural.js +8 -3
- package/dist/src/credential-patterns.js +14 -1
- package/dist/src/engine/index.js +371 -74
- package/dist/src/engine/parse-pool.js +21 -4
- package/dist/src/engine/seal.js +122 -3
- package/dist/src/engine/sealed-open.js +125 -8
- package/dist/src/engine/text-matches.js +121 -79
- package/dist/src/graph-query-health.js +9 -3
- package/dist/src/init.js +109 -3
- package/dist/src/lifecycle-health.js +46 -3
- package/dist/src/mcp-worker-supervisor.js +10 -2
- package/dist/src/repair-progress.js +12 -0
- package/dist/src/structural-fast-path.js +64 -1
- package/dist/src/tools/knodin-tools.js +32 -9
- package/docs/releases/0.11.0.md +227 -0
- package/docs/releases/0.12.0.md +281 -0
- package/package.json +5 -2
package/dist/bin/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ import { diagnoseFailure, } from "../src/failure-diagnosis.js";
|
|
|
35
35
|
import { gitExecutable } from "../src/git-executable.js";
|
|
36
36
|
import { decorateGraphQueryResult, inspectGraphQueryHealth } from "../src/graph-query-health.js";
|
|
37
37
|
import { createIndexActivityReporter } from "../src/index-activity.js";
|
|
38
|
-
import { InitializationHealthError, initializeRepository, inspectRepositoryIntegrationStatus, readRepositoryIntegrationConfig, refreshFromGitEvent, } from "../src/init.js";
|
|
38
|
+
import { InitializationHealthError, initializeRepository, inspectRepositoryIntegrationStatus, readRepositoryIntegrationConfig, refreshFromGitEvent, repairLifecycleRouting, } from "../src/init.js";
|
|
39
39
|
import { createInitProgressRenderer } from "../src/init-progress.js";
|
|
40
40
|
import { attachLifecycleHealth, attachRepairLifecycle } from "../src/lifecycle-health.js";
|
|
41
41
|
import { acknowledgeUpdateFailure, queryOwnerAvailability, readManagerUpdateState, readUpdateJournal, resolveManagerOwnership, setManualPin, unpinUpdate, updateAttention, writeManagerUpdateState, } from "../src/manager-update.js";
|
|
@@ -60,7 +60,7 @@ import { KNODIN_VERSION } from "../src/version.js";
|
|
|
60
60
|
import { writeVisualization, } from "../src/visualization.js";
|
|
61
61
|
import { waitForFresh } from "../src/wait-for-fresh.js";
|
|
62
62
|
import { inspectWorktrees, reconcileWorktrees, removeManagedWorktree, } from "../src/worktree-lifecycle.js";
|
|
63
|
-
import { indexOrSeed } from "../src/worktree-seed.js";
|
|
63
|
+
import { indexOrSeed, seedWorktreeIndex } from "../src/worktree-seed.js";
|
|
64
64
|
function explicitScope(args) {
|
|
65
65
|
const index = args.indexOf("--scope");
|
|
66
66
|
if (index >= 0) {
|
|
@@ -138,7 +138,7 @@ function formatRepairHuman(result) {
|
|
|
138
138
|
}
|
|
139
139
|
if (result.verified) {
|
|
140
140
|
if (result.lifecycle?.status === "degraded")
|
|
141
|
-
return `Repair verified: graph is healthy (${coverage.indexedFiles} indexed files, ${coverage.filesWithSymbols} files with symbols), but lifecycle routing is degraded. Run \`knodin
|
|
141
|
+
return `Repair verified: graph is healthy (${coverage.indexedFiles} indexed files, ${coverage.filesWithSymbols} files with symbols), but lifecycle routing is degraded. Run \`knodin repair --lifecycle\`, then \`knodin status\`.\n`;
|
|
142
142
|
return `Repair verified: graph is healthy (${coverage.indexedFiles} indexed files, ${coverage.filesWithSymbols} files with symbols).\n`;
|
|
143
143
|
}
|
|
144
144
|
const outstanding = result.outstandingIssues?.total ??
|
|
@@ -198,7 +198,7 @@ function formatLifecycleLine(lifecycle, isMirror) {
|
|
|
198
198
|
if (lifecycle.status === "healthy")
|
|
199
199
|
return "Hooks: installed and executable.\n";
|
|
200
200
|
const issue = lifecycle.issues[0] ?? "refresh capability is not verified";
|
|
201
|
-
return `Lifecycle refresh: ${lifecycle.status}; ${issue}. Run \`knodin
|
|
201
|
+
return `Lifecycle refresh: ${lifecycle.status}; ${issue}. Run \`knodin repair --lifecycle\`.\n`;
|
|
202
202
|
}
|
|
203
203
|
/** Renders the top extensions of a tally as `.md 180, .yml 74, +3 more`. */
|
|
204
204
|
function formatTally(counts, limit = 4) {
|
|
@@ -1522,6 +1522,7 @@ async function main() {
|
|
|
1522
1522
|
let result;
|
|
1523
1523
|
let repairOutput;
|
|
1524
1524
|
let repairWasPlan = false;
|
|
1525
|
+
let repairWasLifecycle = false;
|
|
1525
1526
|
let repairExitCode = 0;
|
|
1526
1527
|
let statusWasWatched = false;
|
|
1527
1528
|
let agentEventOutput = false;
|
|
@@ -2381,6 +2382,27 @@ async function main() {
|
|
|
2381
2382
|
repairWasPlan = true;
|
|
2382
2383
|
break;
|
|
2383
2384
|
}
|
|
2385
|
+
if (options.lifecycle) {
|
|
2386
|
+
result = await repairLifecycleRouting(repo, { command: runtimeCommand });
|
|
2387
|
+
repairOutput = options.output;
|
|
2388
|
+
repairWasLifecycle = true;
|
|
2389
|
+
break;
|
|
2390
|
+
}
|
|
2391
|
+
// Cold start only. With no database at all there is nothing to rebuild
|
|
2392
|
+
// *from*, so repair would index the whole corpus while a sibling
|
|
2393
|
+
// worktree already holds a reusable baseline — reflink-copying it and
|
|
2394
|
+
// reconciling the differing paths is seconds against minutes
|
|
2395
|
+
// (KNODIN-14). Repair's contract is untouched: it still rebuilds from
|
|
2396
|
+
// this working tree, it just starts from a baseline instead of zero.
|
|
2397
|
+
//
|
|
2398
|
+
// A database that exists but is damaged is deliberately excluded. That
|
|
2399
|
+
// is the case repair exists for, and seeding it would replace a
|
|
2400
|
+
// verifiable local rebuild with another checkout's state.
|
|
2401
|
+
if (!fs.existsSync(resolveDbPath(repo))) {
|
|
2402
|
+
const seed = await seedWorktreeIndex(repo, engine, KNODIN_SCHEMA_VERSION);
|
|
2403
|
+
if (seed.seeded)
|
|
2404
|
+
process.stderr.write("[repair:seed] Seeded baseline from an indexed sibling worktree\n");
|
|
2405
|
+
}
|
|
2384
2406
|
const progressMode = resolveRepairProgressMode(options, process.env, process.stderr.isTTY);
|
|
2385
2407
|
const renderer = progressMode === "tty"
|
|
2386
2408
|
? createProgressWorkerRenderer("repair-progress-worker", {
|
|
@@ -2787,6 +2809,12 @@ async function main() {
|
|
|
2787
2809
|
}
|
|
2788
2810
|
result = decorateGraphQueryResult(result, verifiedQueryHealth.state, verifiedQueryHealth.graph.freshness);
|
|
2789
2811
|
}
|
|
2812
|
+
// A target that does not exist is operator error, not a negative
|
|
2813
|
+
// answer, so it exits non-zero like an unavailable graph rather than
|
|
2814
|
+
// like real dead code (KNODIN-28). `resolved` with zero rows keeps
|
|
2815
|
+
// exit 0: the symbol exists and genuinely has no callers.
|
|
2816
|
+
if (result?.targetResolution === "not-found")
|
|
2817
|
+
process.exitCode = 1;
|
|
2790
2818
|
break;
|
|
2791
2819
|
}
|
|
2792
2820
|
case "rename": {
|
|
@@ -2999,6 +3027,15 @@ async function main() {
|
|
|
2999
3027
|
process.stdout.write(formatConfigureStatusHuman(boundedResult));
|
|
3000
3028
|
return;
|
|
3001
3029
|
}
|
|
3030
|
+
if (cmd === "repair" && repairOutput === "human" && repairWasLifecycle) {
|
|
3031
|
+
// A lifecycle repair carries no graph coverage, so it cannot go through
|
|
3032
|
+
// the graph-repair renderer, and saying nothing about what it left alone
|
|
3033
|
+
// would omit the only reason to reach for it (KNODIN-31).
|
|
3034
|
+
const lifecycle = boundedResult;
|
|
3035
|
+
process.stdout.write(`Lifecycle routing rewritten: ${lifecycle.gitHooks.length} managed Git hook(s) and the background indexer, in ${lifecycle.hooksDirectory}. Agent configuration untouched. Run \`knodin status\` to confirm.\n`);
|
|
3036
|
+
process.exitCode = finalExitCode;
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3002
3039
|
if (cmd === "repair" && repairOutput === "human" && !repairWasPlan) {
|
|
3003
3040
|
process.stdout.write(formatRepairHuman(boundedResult));
|
|
3004
3041
|
process.exitCode = finalExitCode;
|
package/dist/src/cli-model.js
CHANGED
|
@@ -309,6 +309,7 @@ function createCliProgram(capture = () => { }) {
|
|
|
309
309
|
.addOption(option("--timeout <seconds>", "deadline", "number"));
|
|
310
310
|
leaf(program, "repair", "audit and reconcile graph state", capture)
|
|
311
311
|
.option("--plan", "report the repair plan without mutation")
|
|
312
|
+
.option("--lifecycle", "rewrite lifecycle hooks only, leaving agent configuration untouched")
|
|
312
313
|
.option("--jsonl", "stream JSONL progress")
|
|
313
314
|
.option("--progress <mode>", "tty, jsonl, plain, or none")
|
|
314
315
|
.option("--progress-interval <duration>", "progress interval such as 750ms, 30s, or 2m");
|
|
@@ -12,12 +12,17 @@ export function compactIdentity(identity) {
|
|
|
12
12
|
export function expandCompactIdentity(identity) {
|
|
13
13
|
return identity?.startsWith("~") ? `${IDENTITY_PREFIX}${identity.slice(1)}*` : identity;
|
|
14
14
|
}
|
|
15
|
+
// Missing evidence is "unknown", never "fresh" — the same default the engine's
|
|
16
|
+
// own `stalenessFor()` uses. Search stamps staleness per row and `SearchPage`
|
|
17
|
+
// carries no page-level field, so a zero-row page has no row to read it from:
|
|
18
|
+
// defaulting to "fresh" rendered an empty result on a stale or unprobed graph as
|
|
19
|
+
// `fresh 0/0`, an authoritative-looking negative (KNODIN-22).
|
|
15
20
|
function freshness(value) {
|
|
16
21
|
if (value === "reconciled")
|
|
17
22
|
return "reconciled";
|
|
18
|
-
if (value === "
|
|
19
|
-
return "
|
|
20
|
-
return "
|
|
23
|
+
if (value === "fresh")
|
|
24
|
+
return "fresh";
|
|
25
|
+
return "unknown";
|
|
21
26
|
}
|
|
22
27
|
function terseSignature(row) {
|
|
23
28
|
const signature = row.signature?.replace(/\s+/g, " ").trim() ?? "";
|
|
@@ -25,8 +25,21 @@ export function credentialPatterns() {
|
|
|
25
25
|
// consumed BEFORE the value, because `authorization: Bearer <token>`
|
|
26
26
|
// otherwise matches only the word "Bearer" — redacting the label and
|
|
27
27
|
// preserving the credential, exactly inverted (KNODIN-10).
|
|
28
|
+
//
|
|
29
|
+
// The keyword is surrounded by `[\w-]*` rather than anchored with `\b`,
|
|
30
|
+
// because `\b` does not exist between `_` and a letter: a leading `\b`
|
|
31
|
+
// could never match inside `AWS_SECRET_ACCESS_KEY`, and a trailing one
|
|
32
|
+
// stopped the name short of the `=`. That excluded the dominant spelling
|
|
33
|
+
// of every real secret — `AWS_SECRET_ACCESS_KEY=`, `DB_PASSWORD=`,
|
|
34
|
+
// `NPM_TOKEN=`, any `*_SECRET=` — while `password=` alone was redacted
|
|
35
|
+
// (KNODIN-19).
|
|
36
|
+
//
|
|
37
|
+
// The trailing half is `[_-]`-led rather than a bare `[\w-]*` so the
|
|
38
|
+
// keyword has to be a whole segment of the name. knodin echoes source
|
|
39
|
+
// into diagnostics and compressed output, and an unanchored tail
|
|
40
|
+
// redacted the value of a plain `const tokenCount = 5`.
|
|
28
41
|
label: "credential",
|
|
29
|
-
pattern:
|
|
42
|
+
pattern: /(?<![\w-])([\w-]*(?:authorization|password|passwd|secret|token|api[_-]?key)(?:[_-][\w-]*)?)(\s*[:=]\s*)(?:(?:bearer|basic|digest|token)\s+)?[^\s,;]+/gi,
|
|
30
43
|
preservedGroups: 2,
|
|
31
44
|
},
|
|
32
45
|
{
|