knodin 0.8.7 → 0.9.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 +307 -85
- package/dist/fixtures/update-verification/config/application.xml +4 -0
- package/dist/fixtures/update-verification/dbt/manifest.json +17 -0
- package/dist/fixtures/update-verification/dbt/models/accounts.sql +1 -0
- package/dist/fixtures/update-verification/dbt/models/contacts.sql +1 -0
- package/dist/fixtures/update-verification/fixture.json +147 -0
- package/dist/fixtures/update-verification/force-app/main/default/classes/UpdateFixture.cls +14 -0
- package/dist/fixtures/update-verification/force-app/main/default/flows/Update_Verification.flow-meta.xml +9 -0
- package/dist/fixtures/update-verification/package.json +5 -0
- package/dist/fixtures/update-verification/prisma/schema.prisma +10 -0
- package/dist/fixtures/update-verification/sql/functions.sql +16 -0
- package/dist/fixtures/update-verification/src/CsharpFixture.cs +6 -0
- package/dist/fixtures/update-verification/src/JavaFixture.java +9 -0
- package/dist/fixtures/update-verification/src/javascript.js +7 -0
- package/dist/fixtures/update-verification/src/python_fixture.py +6 -0
- package/dist/fixtures/update-verification/src/typescript.ts +7 -0
- package/dist/fixtures/update-verification/terraform/main.tf +7 -0
- package/dist/fixtures/update-verification/terraform/modules/verified-child/main.tf +3 -0
- package/dist/src/agent-integration.js +26 -3
- package/dist/src/backup-retention.js +811 -0
- package/dist/src/cli-model.js +35 -8
- package/dist/src/docs-sections.js +1 -0
- package/dist/src/doctor.js +142 -12
- package/dist/src/engine/embeddings.js +8 -1
- package/dist/src/engine/index.js +355 -292
- package/dist/src/init.js +222 -179
- package/dist/src/manager-update.js +455 -0
- package/dist/src/release-preflight.js +106 -95
- package/dist/src/repair-lease.js +77 -8
- package/dist/src/response-budget.js +5 -1
- package/dist/src/server.js +18 -5
- package/dist/src/shared-index/compatibility.js +4 -2
- package/dist/src/tools/knodin-tools.js +69 -10
- package/dist/src/update-ceremony.js +20 -24
- package/dist/src/update-coordination.js +158 -0
- package/dist/src/update-executor.js +336 -0
- package/dist/src/update-verifier.js +358 -0
- package/docs/BACKUP-RETENTION.md +54 -0
- package/docs/CLI.md +6 -0
- package/docs/DOCTOR-AND-UPDATES.md +44 -12
- package/docs/SIGNED-UPDATES.md +29 -22
- package/docs/releases/0.9.0.md +43 -0
- package/package.json +7 -1
- package/roadmap/competitive-roadmap.md +8 -0
package/dist/src/cli-model.js
CHANGED
|
@@ -175,6 +175,29 @@ function addGraphCommands(program, capture) {
|
|
|
175
175
|
.option("--apply", "apply the verified edit")
|
|
176
176
|
.option("--no-verify", "skip post-apply typecheck");
|
|
177
177
|
}
|
|
178
|
+
function addBackupCommands(program, capture) {
|
|
179
|
+
const backups = program.command("backups").description("inspect and prune local graph backups");
|
|
180
|
+
leaf(backups, "list [roots...]", "list exact completed backup snapshots", capture)
|
|
181
|
+
.addOption(option("--depth <count>", "maximum discovery depth", "integer"))
|
|
182
|
+
.option("--jsonl", "stream one record per line");
|
|
183
|
+
leaf(backups, "prune [roots...]", "preview or apply safe backup pruning", capture)
|
|
184
|
+
.option("--apply", "delete exact preview candidates after revalidation")
|
|
185
|
+
.addOption(option("--retention-days <count>", "retention window in days", "integer"))
|
|
186
|
+
.addOption(option("--keep-newest <count>", "newest backups retained per repository", "integer"))
|
|
187
|
+
.addOption(option("--depth <count>", "maximum discovery depth", "integer"))
|
|
188
|
+
.option("--jsonl", "stream one record per line");
|
|
189
|
+
const retention = backups.command("retention").description("manage opt-in ongoing retention");
|
|
190
|
+
leaf(retention, "install [roots...]", "install daily and opportunistic retention", capture)
|
|
191
|
+
.addOption(option("--retention-days <count>", "retention window in days", "integer"))
|
|
192
|
+
.addOption(option("--keep-newest <count>", "newest backups retained per repository", "integer"))
|
|
193
|
+
.addOption(option("--depth <count>", "maximum discovery depth", "integer"))
|
|
194
|
+
.option("--schedule <time>", "daily local HH:MM schedule")
|
|
195
|
+
.option("--dry-run", "install a policy that logs without deleting")
|
|
196
|
+
.option("--jsonl", "stream one record per line");
|
|
197
|
+
for (const action of ["status", "doctor", "remove"])
|
|
198
|
+
leaf(retention, action, `${action} installed backup retention`, capture);
|
|
199
|
+
leaf(retention, "run", "run the installed retention policy", capture);
|
|
200
|
+
}
|
|
178
201
|
function addArtifactCommands(program, capture) {
|
|
179
202
|
leaf(program, "evidence <level> <file>", "deliver progressive source evidence", capture)
|
|
180
203
|
.option("--continuation <handle>", "resume an exact prior response")
|
|
@@ -241,8 +264,8 @@ function createCliProgram(capture = () => { }) {
|
|
|
241
264
|
.exitOverride();
|
|
242
265
|
program.configureOutput({ writeErr: () => { } });
|
|
243
266
|
addGlobalOptions(program);
|
|
244
|
-
program.addHelpText("after", "\nMCP callers use the `prs` operation and corresponding operations on the single `knodin` gateway.\
|
|
245
|
-
leaf(program, "init", "initialize graph
|
|
267
|
+
program.addHelpText("after", "\nMCP callers use the `prs` operation and corresponding operations on the single `knodin` gateway.\nLegacy update status|check|explain|apply|rollback consumes only threshold-signed metadata. Manager-aware available|upgrade|pin|unpin|acknowledge|auto binds actions to the proven active owner.\n");
|
|
268
|
+
leaf(program, "init", "initialize graph and lifecycle hooks; configure agents only with --scope", capture)
|
|
246
269
|
.option("--scope <scope>", "personal, team, or cli-only")
|
|
247
270
|
.addHelpText("after", "\nUsage: knodin init [--scope personal|team|cli-only] [--json]\n\npersonal recommended; local/excluded agent adapters and a clean Git status\nteam commit-ready shared agent configuration\ncli-only no agent discovery; AI agents will not know to invoke knodin\n\nChange later with `knodin configure --scope <scope>`.\nTracked files are never added to Git exclude files.\n");
|
|
248
271
|
leaf(program, "configure", "change or inspect agent integration", capture)
|
|
@@ -286,6 +309,7 @@ function createCliProgram(capture = () => { }) {
|
|
|
286
309
|
leaf(program, "agent-event <event>", "internal coding-agent lifecycle event", capture);
|
|
287
310
|
leaf(program, "refresh-artifacts [event]", "refresh external graph artifacts", capture);
|
|
288
311
|
addRepositoryCommands(program, capture);
|
|
312
|
+
addBackupCommands(program, capture);
|
|
289
313
|
addRemoteCommands(program, capture);
|
|
290
314
|
addSharedIndexCommands(program, capture);
|
|
291
315
|
addGraphCommands(program, capture);
|
|
@@ -327,12 +351,15 @@ function createCliProgram(capture = () => { }) {
|
|
|
327
351
|
.option("--scope <scope>", "personal, team, or cli-only")
|
|
328
352
|
.addOption(option("--client <client>", "codex, claude, or gemini", "collect"))
|
|
329
353
|
.option("--dry-run", "preview skill changes without writing");
|
|
330
|
-
const update = program
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
update
|
|
354
|
+
const update = program.command("update").description("inspect or apply verified updates");
|
|
355
|
+
for (const action of ["status", "check", "explain", "apply", "rollback"])
|
|
356
|
+
leaf(update, action, `${action} threshold-signed updates`, capture);
|
|
357
|
+
leaf(update, "available", "query the active owner's release channel", capture);
|
|
358
|
+
leaf(update, "upgrade [version]", "plan or apply an exact verified upgrade", capture).option("--apply", "execute the owning package manager");
|
|
359
|
+
leaf(update, "pin <version>", "pin an exact version", capture).option("--apply", "install and verify the pinned version");
|
|
360
|
+
leaf(update, "unpin", "remove the active update pin", capture);
|
|
361
|
+
leaf(update, "acknowledge", "acknowledge the current rollback notice", capture);
|
|
362
|
+
leaf(update, "auto <action>", "status, enable, disable, or run automatic updates", capture);
|
|
336
363
|
leaf(program, "docs <topic>", "read canonical product documentation", capture);
|
|
337
364
|
leaf(program, "prs [action]", "audit pull requests, reviews, and checks", capture)
|
|
338
365
|
.option("--state <state>", "open, merged, closed, or all")
|
|
@@ -112,6 +112,7 @@ const DOC_TOPIC_FILES = {
|
|
|
112
112
|
provenance: "INDEXING-POLICY-AND-PROVENANCE.md",
|
|
113
113
|
"dead-code": "DEAD-CODE-AND-IMPACT.md",
|
|
114
114
|
doctor: "DOCTOR-AND-UPDATES.md",
|
|
115
|
+
backups: "BACKUP-RETENTION.md",
|
|
115
116
|
compression: "COMMAND-OUTPUT-COMPRESSION.md",
|
|
116
117
|
};
|
|
117
118
|
function packageRoot() {
|
package/dist/src/doctor.js
CHANGED
|
@@ -3,7 +3,9 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { detectSupportedAgents } from "./agent-integration.js";
|
|
6
|
+
import { listBackups, pruneBackups, retentionDoctor } from "./backup-retention.js";
|
|
6
7
|
import { inspectLifecycleHealth } from "./lifecycle-health.js";
|
|
8
|
+
import { readManagerUpdateState, readUpdateJournal, resolveManagerOwnership, updateAttention, } from "./manager-update.js";
|
|
7
9
|
import { trustedUpdateStatus } from "./update-policy.js";
|
|
8
10
|
function defaultRun(command, args, options = {}) {
|
|
9
11
|
const result = child_process.spawnSync(command, args, {
|
|
@@ -141,12 +143,83 @@ function sanitizeRegistry(raw) {
|
|
|
141
143
|
return "unknown";
|
|
142
144
|
}
|
|
143
145
|
}
|
|
146
|
+
const MANAGED_REPOSITORY_PATHS = [
|
|
147
|
+
"AGENTS.md",
|
|
148
|
+
"GEMINI.md",
|
|
149
|
+
".mcp.json",
|
|
150
|
+
".codex/config.toml",
|
|
151
|
+
".gemini/settings.json",
|
|
152
|
+
".vscode/mcp.json",
|
|
153
|
+
".agents/mcp_config.json",
|
|
154
|
+
];
|
|
155
|
+
function managedConfigurationRemediation(repo, run) {
|
|
156
|
+
const paths = [...MANAGED_REPOSITORY_PATHS];
|
|
157
|
+
for (const root of [".agents/skills", ".claude/skills", ".gemini/skills"]) {
|
|
158
|
+
const absolute = path.join(repo, root);
|
|
159
|
+
let entries = [];
|
|
160
|
+
try {
|
|
161
|
+
entries = fs.readdirSync(absolute, { withFileTypes: true }).slice(0, 64);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
if (!entry.isDirectory())
|
|
168
|
+
continue;
|
|
169
|
+
const relative = path.posix.join(root, entry.name);
|
|
170
|
+
if (fs.existsSync(path.join(repo, relative, ".knodin-managed.json")))
|
|
171
|
+
paths.push(relative);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return paths
|
|
175
|
+
.map((relative) => {
|
|
176
|
+
const absolute = path.join(repo, relative);
|
|
177
|
+
const present = fs.existsSync(absolute);
|
|
178
|
+
const trackedResult = run("git", ["-C", repo, "ls-files", "--error-unmatch", "--", relative]);
|
|
179
|
+
const tracked = trackedResult.status === 0;
|
|
180
|
+
const diffResult = tracked
|
|
181
|
+
? run("git", ["-C", repo, "diff", "--no-ext-diff", "--unified=3", "--", relative])
|
|
182
|
+
: { status: 0, stdout: "", stderr: "" };
|
|
183
|
+
const receipted = fs.existsSync(path.join(absolute, ".knodin-managed.json"));
|
|
184
|
+
let entryOwned = false;
|
|
185
|
+
if (present && !receipted) {
|
|
186
|
+
try {
|
|
187
|
+
entryOwned = fs.readFileSync(absolute, "utf8").includes("knodin");
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
entryOwned = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const instructions = [];
|
|
194
|
+
if (tracked && diffResult.stdout)
|
|
195
|
+
instructions.push(`Review the diff, then restore manually with: git restore --source=HEAD -- ${relative}`);
|
|
196
|
+
if (!tracked && receipted)
|
|
197
|
+
instructions.push(`Review the Knodin receipt, then remove manually if unwanted: ${relative}`);
|
|
198
|
+
if (!tracked && present && (receipted || entryOwned))
|
|
199
|
+
instructions.push(`To retain it locally without committing, add ${relative} to .git/info/exclude manually.`);
|
|
200
|
+
return {
|
|
201
|
+
path: relative,
|
|
202
|
+
tracked,
|
|
203
|
+
present,
|
|
204
|
+
ownership: receipted ? "knodin-receipt" : entryOwned ? "knodin-entry" : "none",
|
|
205
|
+
diff: diffResult.stdout ? diffResult.stdout.slice(0, 32_768) : null,
|
|
206
|
+
instructions,
|
|
207
|
+
};
|
|
208
|
+
})
|
|
209
|
+
.filter((entry) => entry.present || entry.tracked || entry.diff !== null || entry.ownership !== "none");
|
|
210
|
+
}
|
|
144
211
|
function readAgentConfig(agent, file) {
|
|
145
212
|
try {
|
|
146
213
|
const content = fs.readFileSync(file, "utf-8");
|
|
147
214
|
if (agent === "claude" && path.basename(file) === ".claude.json") {
|
|
148
215
|
const present = content.includes('"knodin"');
|
|
149
|
-
return {
|
|
216
|
+
return {
|
|
217
|
+
agent,
|
|
218
|
+
file,
|
|
219
|
+
present,
|
|
220
|
+
command: present ? "knodin" : null,
|
|
221
|
+
args: ["serve"],
|
|
222
|
+
};
|
|
150
223
|
}
|
|
151
224
|
if (file.endsWith(".toml")) {
|
|
152
225
|
const sectionStart = content.indexOf('[mcp_servers."knodin"]');
|
|
@@ -273,15 +346,27 @@ function mcpProbe(command, run) {
|
|
|
273
346
|
clientInfo: { name: "knodin-doctor", version: "1" },
|
|
274
347
|
},
|
|
275
348
|
}),
|
|
276
|
-
JSON.stringify({
|
|
349
|
+
JSON.stringify({
|
|
350
|
+
jsonrpc: "2.0",
|
|
351
|
+
method: "notifications/initialized",
|
|
352
|
+
params: {},
|
|
353
|
+
}),
|
|
277
354
|
JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }),
|
|
278
355
|
"",
|
|
279
356
|
].join("\n");
|
|
280
357
|
const [executable, ...args] = command;
|
|
281
358
|
if (!executable)
|
|
282
|
-
return {
|
|
359
|
+
return {
|
|
360
|
+
initialize: "failed",
|
|
361
|
+
toolsList: "failed",
|
|
362
|
+
toolNames: [],
|
|
363
|
+
stderr: "no command",
|
|
364
|
+
};
|
|
283
365
|
const result = run(executable, args, { input });
|
|
284
|
-
return {
|
|
366
|
+
return {
|
|
367
|
+
...parseMcp(result.stdout),
|
|
368
|
+
stderr: result.status === 0 ? undefined : result.stderr,
|
|
369
|
+
};
|
|
285
370
|
}
|
|
286
371
|
export async function diagnoseInstallation(repoPath, options) {
|
|
287
372
|
const repo = path.resolve(repoPath);
|
|
@@ -298,6 +383,7 @@ export async function diagnoseInstallation(repoPath, options) {
|
|
|
298
383
|
const detectedAgents = detectSupportedAgents();
|
|
299
384
|
const repositoryCommands = configuredAgentCommands(repo);
|
|
300
385
|
const personalCommands = personalAgentCommands(options.homeDir ?? os.homedir());
|
|
386
|
+
const configurationRemediation = managedConfigurationRemediation(repo, run);
|
|
301
387
|
const clients = repositoryCommands
|
|
302
388
|
.filter(({ agent }) => options.client === undefined || options.client === agent)
|
|
303
389
|
.map((repository) => {
|
|
@@ -323,7 +409,16 @@ export async function diagnoseInstallation(repoPath, options) {
|
|
|
323
409
|
};
|
|
324
410
|
});
|
|
325
411
|
const manager = managerEvidence(chain.at(-1) ?? executable, env);
|
|
412
|
+
const owner = resolveManagerOwnership({
|
|
413
|
+
runtimeCommand: options.runtimeCommand,
|
|
414
|
+
executablePath: chain.at(-1) ?? executable,
|
|
415
|
+
env,
|
|
416
|
+
});
|
|
417
|
+
const stateHome = env.XDG_STATE_HOME;
|
|
418
|
+
const managerState = readManagerUpdateState(stateHome);
|
|
326
419
|
const packageVersion = installedPackageVersion(chain.at(-1) ?? executable);
|
|
420
|
+
const backupInventory = listBackups([repo], { depth: 0 });
|
|
421
|
+
const backupPrunePreview = pruneBackups([repo], { depth: 0 });
|
|
327
422
|
return {
|
|
328
423
|
schemaVersion: 1,
|
|
329
424
|
status: options.graph.status === "healthy" &&
|
|
@@ -360,19 +455,54 @@ export async function diagnoseInstallation(repoPath, options) {
|
|
|
360
455
|
configuredCommands: repositoryCommands,
|
|
361
456
|
clients,
|
|
362
457
|
},
|
|
458
|
+
configurationRemediation,
|
|
363
459
|
mcp,
|
|
364
460
|
lifecycle,
|
|
365
461
|
graph: options.graph,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
462
|
+
backups: {
|
|
463
|
+
inventory: backupInventory,
|
|
464
|
+
reclaimableBytes: backupPrunePreview.reclaimableBytes,
|
|
465
|
+
skippedRepositories: backupPrunePreview.skipped,
|
|
466
|
+
retention: retentionDoctor(),
|
|
467
|
+
remediation: "Preview with `knodin backups prune`; deletion requires `--apply` or installed retention.",
|
|
468
|
+
},
|
|
469
|
+
update: {
|
|
470
|
+
...trustedUpdateStatus({
|
|
471
|
+
currentVersion: options.currentVersion,
|
|
472
|
+
stateHome: options.cacheHome,
|
|
473
|
+
installMethod: manager.name,
|
|
474
|
+
env,
|
|
475
|
+
}),
|
|
476
|
+
owner: {
|
|
477
|
+
method: owner.method,
|
|
478
|
+
status: owner.status,
|
|
479
|
+
executable: owner.executable,
|
|
480
|
+
evidence: owner.evidence,
|
|
481
|
+
},
|
|
482
|
+
pin: managerState.pin ?? null,
|
|
483
|
+
auto: {
|
|
484
|
+
enabled: managerState.autoEnabled,
|
|
485
|
+
platformEligible: managerState.platformAutoEligible,
|
|
486
|
+
},
|
|
487
|
+
attention: updateAttention(managerState),
|
|
488
|
+
journal: readUpdateJournal(stateHome),
|
|
489
|
+
},
|
|
372
490
|
remediation: {
|
|
373
491
|
manager: managerRemediation(manager.name, options.currentVersion),
|
|
374
|
-
duplicates:
|
|
375
|
-
?
|
|
492
|
+
duplicates: owner.duplicates.length > 0 || owner.staleLinks.length > 0 || owner.pathShadowing
|
|
493
|
+
? {
|
|
494
|
+
instructions: [
|
|
495
|
+
...owner.duplicates.map((entry) => `Use the package manager that owns ${entry.path} to remove only that competing installation.`),
|
|
496
|
+
...owner.staleLinks.map((entry) => `Inspect and manually remove the stale executable link ${entry}.`),
|
|
497
|
+
...(owner.pathShadowing
|
|
498
|
+
? [
|
|
499
|
+
"Correct PATH ordering so the intended manager-owned executable resolves first.",
|
|
500
|
+
]
|
|
501
|
+
: []),
|
|
502
|
+
"Rerun `knodin doctor`; Knodin will not remove another installation automatically.",
|
|
503
|
+
],
|
|
504
|
+
ownershipEvidence: owner.evidence,
|
|
505
|
+
}
|
|
376
506
|
: null,
|
|
377
507
|
graph: options.graph.status === "healthy"
|
|
378
508
|
? null
|
|
@@ -18,6 +18,7 @@ let embedderReady = false;
|
|
|
18
18
|
export const EMBEDDING_DIMENSIONS = 384;
|
|
19
19
|
export const EMBEDDING_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
20
20
|
export const EMBEDDING_MODEL_REVISION = "751bff37182d3f1213fa05d7196b954e230abad9";
|
|
21
|
+
export const EMBEDDING_MODEL_DTYPE = "q8";
|
|
21
22
|
const DEFAULT_MODEL_REMOTE_HOST = "https://huggingface.co/";
|
|
22
23
|
/**
|
|
23
24
|
* One machine-level model cache shared by every repository and every installed
|
|
@@ -137,6 +138,9 @@ export function getEmbedder(onProgress) {
|
|
|
137
138
|
notify({ status: "loading", name: EMBEDDING_MODEL_ID });
|
|
138
139
|
const pipe = await pipeline("feature-extraction", EMBEDDING_MODEL_ID, {
|
|
139
140
|
revision: EMBEDDING_MODEL_REVISION,
|
|
141
|
+
// q8 preserves the model's 384-dimensional normalized-vector contract
|
|
142
|
+
// while avoiding the 86 MiB FP32 graph load on every cold CLI process.
|
|
143
|
+
dtype: EMBEDDING_MODEL_DTYPE,
|
|
140
144
|
progress_callback: notify,
|
|
141
145
|
});
|
|
142
146
|
realEmbedderLoadCount++;
|
|
@@ -162,7 +166,10 @@ export async function generateEmbeddings(texts, onModelProgress) {
|
|
|
162
166
|
if (texts.length === 0)
|
|
163
167
|
return [];
|
|
164
168
|
const extractor = await getEmbedder(onModelProgress);
|
|
165
|
-
const result = await extractor([...texts], {
|
|
169
|
+
const result = await extractor([...texts], {
|
|
170
|
+
pooling: "mean",
|
|
171
|
+
normalize: true,
|
|
172
|
+
});
|
|
166
173
|
const data = result.data instanceof Float32Array ? result.data : Float32Array.from(result.data);
|
|
167
174
|
const dims = result.dims;
|
|
168
175
|
if (dims?.length !== 2 || dims[0] !== texts.length || dims[1] !== EMBEDDING_DIMENSIONS) {
|