knodin 0.5.0 → 0.6.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/README.md +166 -534
- package/dist/bin/cli.js +5 -5
- package/dist/src/agent-integration.js +1 -15
- package/dist/src/artifact-refresh.js +1 -1
- package/dist/src/competitive-sandbox.js +4 -4
- package/dist/src/context-export.js +1 -1
- package/dist/src/docs-sections.js +5 -5
- package/dist/src/engine/ann-hnsw.js +5 -5
- package/dist/src/engine/embeddings.js +4 -4
- package/dist/src/engine/index.js +33 -33
- package/dist/src/engine/prune.js +2 -2
- package/dist/src/engine/symbol-delete.js +1 -1
- package/dist/src/index-activity.js +1 -1
- package/dist/src/init-progress-worker.js +1 -1
- package/dist/src/init.js +42 -59
- package/dist/src/lifecycle-health.js +10 -11
- package/dist/src/lsp-readonly.js +1 -1
- package/dist/src/output-compression.js +1 -1
- package/dist/src/output-telemetry.js +4 -4
- package/dist/src/pr-triage.js +2 -2
- package/dist/src/repair-progress-worker.js +1 -1
- package/dist/src/repository-init-process.js +1 -1
- package/dist/src/repository-management.js +3 -3
- package/dist/src/server.js +1 -1
- package/dist/src/system-config.js +7 -7
- package/dist/src/tools/knodin-tools.js +2 -2
- package/dist/src/update-policy.js +4 -4
- package/dist/src/wait-for-fresh.js +2 -2
- package/dist/src/worktree-lifecycle.js +2 -2
- package/docs/COMMAND-OUTPUT-COMPRESSION.md +1 -1
- package/docs/DOCTOR-AND-UPDATES.md +2 -2
- package/docs/INSTALLATION.md +5 -5
- package/docs/REPOSITORIES-AND-WORKTREES.md +1 -1
- package/docs/SYSTEMS-AND-RELATIONSHIPS.md +2 -2
- package/docs/TELEMETRY.md +2 -2
- package/docs/assets/knodin-favicon.svg +4 -0
- package/docs/releases/0.5.1.md +17 -0
- package/docs/releases/0.6.0.md +18 -0
- package/package.json +8 -6
- package/schemas/release-attestation-v1.schema.json +1 -1
- package/dist/src/tools/reckon-tools.js +0 -5
package/dist/src/init.js
CHANGED
|
@@ -5,7 +5,6 @@ import { configureProjectAgents, } from "./agent-integration.js";
|
|
|
5
5
|
import { isIndexableSourcePath } from "./engine/source-policy.js";
|
|
6
6
|
import { registerInitializedWorktree } from "./worktree-lifecycle.js";
|
|
7
7
|
const MANAGED_MARKER = "KNODIN MANAGED HOOK";
|
|
8
|
-
const LEGACY_MANAGED_MARKER = "RECKON-GRAPH MANAGED HOOK";
|
|
9
8
|
const HOOK_NAMES = ["post-commit", "post-checkout", "post-merge", "post-rewrite"];
|
|
10
9
|
const HOOK_LOG_MAX_BYTES = 1_048_576;
|
|
11
10
|
const HOOK_FAILURE_FILE = "last-refresh-failure";
|
|
@@ -78,14 +77,14 @@ export async function refreshFromGitEvent(repo, event, index) {
|
|
|
78
77
|
changed = diffPaths(resolvedRepo, event.before, event.after);
|
|
79
78
|
}
|
|
80
79
|
else {
|
|
81
|
-
const hooksRoot = path.join(resolvedRepo, ".
|
|
80
|
+
const hooksRoot = path.join(resolvedRepo, ".knodin", "hooks");
|
|
82
81
|
const inputPath = path.resolve(event.inputPath);
|
|
83
82
|
const relativeInput = path.relative(hooksRoot, inputPath);
|
|
84
83
|
if (relativeInput === "" ||
|
|
85
84
|
relativeInput === ".." ||
|
|
86
85
|
relativeInput.startsWith(`..${path.sep}`) ||
|
|
87
86
|
path.isAbsolute(relativeInput)) {
|
|
88
|
-
throw new Error("knodin hook refresh: rewrite input must be inside .
|
|
87
|
+
throw new Error("knodin hook refresh: rewrite input must be inside .knodin/hooks");
|
|
89
88
|
}
|
|
90
89
|
const [realHooksRoot, realInputPath, inputStat] = await Promise.all([
|
|
91
90
|
fs.promises.realpath(hooksRoot),
|
|
@@ -99,7 +98,7 @@ export async function refreshFromGitEvent(repo, event, index) {
|
|
|
99
98
|
realRelativeInput === ".." ||
|
|
100
99
|
realRelativeInput.startsWith(`..${path.sep}`) ||
|
|
101
100
|
path.isAbsolute(realRelativeInput)) {
|
|
102
|
-
throw new Error("knodin hook refresh: rewrite input must be a regular file inside .
|
|
101
|
+
throw new Error("knodin hook refresh: rewrite input must be a regular file inside .knodin/hooks");
|
|
103
102
|
}
|
|
104
103
|
rewriteInputToRemove = inputPath;
|
|
105
104
|
const pairs = (await fs.promises.readFile(inputPath, "utf-8"))
|
|
@@ -130,11 +129,11 @@ set -u
|
|
|
130
129
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
131
130
|
[ -n "$REPO_ROOT" ] || exit 0
|
|
132
131
|
cd "$REPO_ROOT" || exit 0
|
|
133
|
-
mkdir -p "$REPO_ROOT/.
|
|
134
|
-
LOCK_DIR="$REPO_ROOT/.
|
|
135
|
-
EVENT_DIR="$REPO_ROOT/.
|
|
136
|
-
LAST_EVENT_PATH="$REPO_ROOT/.
|
|
137
|
-
FAILURE_PATH="$REPO_ROOT/.
|
|
132
|
+
mkdir -p "$REPO_ROOT/.knodin"
|
|
133
|
+
LOCK_DIR="$REPO_ROOT/.knodin/hooks/refresh.lock"
|
|
134
|
+
EVENT_DIR="$REPO_ROOT/.knodin/hooks/events"
|
|
135
|
+
LAST_EVENT_PATH="$REPO_ROOT/.knodin/hooks/last-refresh-event"
|
|
136
|
+
FAILURE_PATH="$REPO_ROOT/.knodin/hooks/${HOOK_FAILURE_FILE}"
|
|
138
137
|
FAILURE_TMP="$FAILURE_PATH.$$"
|
|
139
138
|
mkdir -p "$EVENT_DIR"
|
|
140
139
|
if [ "$#" -gt 0 ]; then
|
|
@@ -165,7 +164,7 @@ if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
|
165
164
|
fi
|
|
166
165
|
printf '%s\\n' "$$" > "$LOCK_DIR/pid"
|
|
167
166
|
trap 'rm -rf "$LOCK_DIR"; rm -f "$FAILURE_TMP"' EXIT HUP INT TERM
|
|
168
|
-
LOG_PATH="$REPO_ROOT/.
|
|
167
|
+
LOG_PATH="$REPO_ROOT/.knodin/indexer.log"
|
|
169
168
|
if [ -f "$LOG_PATH" ]; then
|
|
170
169
|
LOG_BYTES="$(wc -c < "$LOG_PATH" 2>/dev/null | tr -d '[:space:]')"
|
|
171
170
|
case "$LOG_BYTES" in ''|*[!0-9]*) LOG_BYTES=0 ;; esac
|
|
@@ -197,7 +196,7 @@ while :; do
|
|
|
197
196
|
{
|
|
198
197
|
printf 'knodin background refresh failed with exit code %s.\\n' "$STATUS"
|
|
199
198
|
printf 'Pending event retained at %s.\\n' "$EVENT"
|
|
200
|
-
printf 'See .
|
|
199
|
+
printf 'See .knodin/indexer.log for details.\\n'
|
|
201
200
|
} > "$FAILURE_TMP"
|
|
202
201
|
mv -f "$FAILURE_TMP" "$FAILURE_PATH"
|
|
203
202
|
exit "$STATUS"
|
|
@@ -206,7 +205,7 @@ done
|
|
|
206
205
|
`;
|
|
207
206
|
}
|
|
208
207
|
function queuedLifecycleEvents(repo) {
|
|
209
|
-
const eventDirectory = path.join(repo, ".
|
|
208
|
+
const eventDirectory = path.join(repo, ".knodin", "hooks", "events");
|
|
210
209
|
try {
|
|
211
210
|
return fs.readdirSync(eventDirectory).filter((entry) => entry.endsWith(".event"));
|
|
212
211
|
}
|
|
@@ -216,7 +215,7 @@ function queuedLifecycleEvents(repo) {
|
|
|
216
215
|
}
|
|
217
216
|
function refreshProcessorIsRunning(repo) {
|
|
218
217
|
try {
|
|
219
|
-
const pid = Number(fs.readFileSync(path.join(repo, ".
|
|
218
|
+
const pid = Number(fs.readFileSync(path.join(repo, ".knodin", "hooks", "refresh.lock", "pid"), "utf-8"));
|
|
220
219
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
221
220
|
return false;
|
|
222
221
|
process.kill(pid, 0);
|
|
@@ -238,7 +237,7 @@ function drainQueuedLifecycleEvents(repo, backgroundPath) {
|
|
|
238
237
|
});
|
|
239
238
|
if (result.status !== 0) {
|
|
240
239
|
const detail = result.error?.message || result.stderr.trim() || `exit ${result.status ?? "unknown"}`;
|
|
241
|
-
throw new Error(`knodin init: queued lifecycle refresh could not be drained (${detail}); run ${backgroundPath} after resolving .
|
|
240
|
+
throw new Error(`knodin init: queued lifecycle refresh could not be drained (${detail}); run ${backgroundPath} after resolving .knodin/hooks/last-refresh-failure`);
|
|
242
241
|
}
|
|
243
242
|
const remaining = queuedLifecycleEvents(repo);
|
|
244
243
|
if (remaining.length === 0)
|
|
@@ -279,7 +278,7 @@ AFTER="$(git rev-parse HEAD 2>/dev/null || true)"
|
|
|
279
278
|
? `if [ ! -x "$BACKGROUND" ]; then
|
|
280
279
|
${rewriteWithoutIndexer}
|
|
281
280
|
fi
|
|
282
|
-
REWRITE_INPUT="$(mktemp "$REPO_ROOT/.
|
|
281
|
+
REWRITE_INPUT="$(mktemp "$REPO_ROOT/.knodin/hooks/rewrite-input.XXXXXX")" || exit 0
|
|
283
282
|
cat > "$REWRITE_INPUT"
|
|
284
283
|
`
|
|
285
284
|
: "";
|
|
@@ -289,7 +288,7 @@ cat > "$REWRITE_INPUT"
|
|
|
289
288
|
# ${MANAGED_MARKER}: ${hookName}
|
|
290
289
|
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
291
290
|
[ -n "$REPO_ROOT" ] || exit 0
|
|
292
|
-
BACKGROUND="$REPO_ROOT/.
|
|
291
|
+
BACKGROUND="$REPO_ROOT/.knodin/hooks/background-index.sh"
|
|
293
292
|
ORIGINAL_STATUS=0
|
|
294
293
|
trigger_knodin() {
|
|
295
294
|
[ -x "$BACKGROUND" ] || return 0
|
|
@@ -304,7 +303,7 @@ trigger_knodin() {
|
|
|
304
303
|
EVENT_BEFORE="$(sed -n '1{s/ .*//;p;}' "$2")"
|
|
305
304
|
EVENT_AFTER="$(sed -n '1{s/^[^ ]* //;s/ .*//;p;}' "$2")"
|
|
306
305
|
fi
|
|
307
|
-
EVENT_DIR="$REPO_ROOT/.
|
|
306
|
+
EVENT_DIR="$REPO_ROOT/.knodin/hooks/events"
|
|
308
307
|
mkdir -p "$EVENT_DIR" || return 0
|
|
309
308
|
EVENT_TMP="$EVENT_DIR/$(date +%s).$$.$PPID.tmp"
|
|
310
309
|
EVENT_PATH="$EVENT_TMP.event"
|
|
@@ -319,7 +318,7 @@ trigger_knodin() {
|
|
|
319
318
|
"$EVENT_SEQUENCE" "$EVENT_WORKTREE" "$EVENT_AT" "$EVENT_BEFORE" "$EVENT_AFTER"
|
|
320
319
|
} > "$EVENT_TMP" || return 0
|
|
321
320
|
mv "$EVENT_TMP" "$EVENT_PATH" || return 0
|
|
322
|
-
if [ "\${
|
|
321
|
+
if [ "\${KNODIN_HOOK_FOREGROUND:-0}" = "1" ]; then
|
|
323
322
|
"$BACKGROUND"
|
|
324
323
|
else
|
|
325
324
|
nohup "$BACKGROUND" >/dev/null 2>&1 &
|
|
@@ -346,13 +345,12 @@ async function installManagedHook(hooksDir, hookName, preservedOriginal = undefi
|
|
|
346
345
|
let originalPath = preservedOriginal ?? null;
|
|
347
346
|
if (preservedOriginal === undefined && fs.existsSync(hookPath)) {
|
|
348
347
|
const content = await fs.promises.readFile(hookPath, "utf-8");
|
|
349
|
-
if (content.startsWith(`#!/bin/sh\n# ${MANAGED_MARKER}: ${hookName}\n`)
|
|
350
|
-
content.startsWith(`#!/bin/sh\n# ${LEGACY_MANAGED_MARKER}: ${hookName}\n`)) {
|
|
348
|
+
if (content.startsWith(`#!/bin/sh\n# ${MANAGED_MARKER}: ${hookName}\n`)) {
|
|
351
349
|
const match = content.match(/^# knodin original: (.+)$/m);
|
|
352
350
|
originalPath = match?.[1] ?? null;
|
|
353
351
|
}
|
|
354
352
|
else {
|
|
355
|
-
const backupPath = `${hookPath}.
|
|
353
|
+
const backupPath = `${hookPath}.knodin-original`;
|
|
356
354
|
const mode = (await fs.promises.stat(hookPath)).mode;
|
|
357
355
|
await fs.promises.copyFile(hookPath, backupPath);
|
|
358
356
|
await fs.promises.rm(hookPath, { force: true });
|
|
@@ -365,9 +363,7 @@ async function installManagedHook(hooksDir, hookName, preservedOriginal = undefi
|
|
|
365
363
|
if (!fs.existsSync(candidate))
|
|
366
364
|
break;
|
|
367
365
|
const candidateContent = await fs.promises.readFile(candidate, "utf-8");
|
|
368
|
-
if (candidateContent === canonical ||
|
|
369
|
-
candidateContent.includes(MANAGED_MARKER) ||
|
|
370
|
-
candidateContent.includes(LEGACY_MANAGED_MARKER))
|
|
366
|
+
if (candidateContent === canonical || candidateContent.includes(MANAGED_MARKER))
|
|
371
367
|
await fs.promises.rm(candidate, { force: true });
|
|
372
368
|
}
|
|
373
369
|
// Match Git's semantics: preserve inactive hook files as backups, but
|
|
@@ -388,7 +384,6 @@ async function installManagedHook(hooksDir, hookName, preservedOriginal = undefi
|
|
|
388
384
|
await fs.promises.chmod(hookPath, 0o755);
|
|
389
385
|
}
|
|
390
386
|
const ORIGINAL_HOOKS_CONFIG = "knodin.originalHooksPath";
|
|
391
|
-
const LEGACY_ORIGINAL_HOOKS_CONFIG = "reckon.originalHooksPath";
|
|
392
387
|
function effectiveHooksDirectory(repo) {
|
|
393
388
|
let raw;
|
|
394
389
|
try {
|
|
@@ -405,15 +400,10 @@ function configuredHooksPath(repo) {
|
|
|
405
400
|
}
|
|
406
401
|
catch {
|
|
407
402
|
try {
|
|
408
|
-
return
|
|
403
|
+
return runGit(repo, ["config", "--local", "--get", "core.hooksPath"]).trim() || null;
|
|
409
404
|
}
|
|
410
405
|
catch {
|
|
411
|
-
|
|
412
|
-
return runGit(repo, ["config", "--local", "--get", "core.hooksPath"]).trim() || null;
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
return null;
|
|
416
|
-
}
|
|
406
|
+
return null;
|
|
417
407
|
}
|
|
418
408
|
}
|
|
419
409
|
}
|
|
@@ -494,7 +484,7 @@ export const SETTINGS_TEMPLATE = `{
|
|
|
494
484
|
}
|
|
495
485
|
}
|
|
496
486
|
`;
|
|
497
|
-
const
|
|
487
|
+
const KNODIN_AGENT_INSTRUCTIONS = `<!-- knodin:start -->
|
|
498
488
|
## knodin — Local Code Intelligence
|
|
499
489
|
|
|
500
490
|
This repository is indexed locally by knodin. Start with \`knodin status\`; if it reports \`repair-needed\`, run \`knodin repair\` before relying on graph evidence.
|
|
@@ -540,24 +530,19 @@ async function installManagedAgentInstructions(repo, fileName = "AGENTS.md") {
|
|
|
540
530
|
}
|
|
541
531
|
const canonicalStart = "<!-- knodin:start -->";
|
|
542
532
|
const canonicalEnd = "<!-- knodin:end -->";
|
|
543
|
-
const
|
|
544
|
-
const
|
|
545
|
-
const usesCanonical = existing.includes(canonicalStart);
|
|
546
|
-
const startMarker = usesCanonical ? canonicalStart : legacyStart;
|
|
547
|
-
const endMarker = usesCanonical ? canonicalEnd : legacyEnd;
|
|
548
|
-
const start = existing.indexOf(startMarker);
|
|
549
|
-
const end = existing.indexOf(endMarker);
|
|
533
|
+
const start = existing.indexOf(canonicalStart);
|
|
534
|
+
const end = existing.indexOf(canonicalEnd);
|
|
550
535
|
const next = start >= 0 && end >= start
|
|
551
|
-
? `${existing.slice(0, start)}${
|
|
536
|
+
? `${existing.slice(0, start)}${KNODIN_AGENT_INSTRUCTIONS}${existing.slice(end + canonicalEnd.length).replace(/^\r?\n/, "")}`
|
|
552
537
|
: existing.length > 0
|
|
553
|
-
? `${existing.
|
|
554
|
-
:
|
|
538
|
+
? `${existing.trimEnd()}\n\n${KNODIN_AGENT_INSTRUCTIONS}`
|
|
539
|
+
: KNODIN_AGENT_INSTRUCTIONS;
|
|
555
540
|
if (next !== existing)
|
|
556
541
|
await fs.promises.writeFile(instructionsPath, next, "utf-8");
|
|
557
542
|
}
|
|
558
543
|
async function hasManagedAgentInstructions(repo, fileName = "AGENTS.md") {
|
|
559
544
|
try {
|
|
560
|
-
return (await fs.promises.readFile(path.join(repo, fileName), "utf-8")).includes(
|
|
545
|
+
return (await fs.promises.readFile(path.join(repo, fileName), "utf-8")).includes(KNODIN_AGENT_INSTRUCTIONS);
|
|
561
546
|
}
|
|
562
547
|
catch {
|
|
563
548
|
return false;
|
|
@@ -589,9 +574,7 @@ async function excludeUntrackedPaths(repo, candidates) {
|
|
|
589
574
|
}
|
|
590
575
|
const startMarker = "# knodin:start";
|
|
591
576
|
const endMarker = "# knodin:end";
|
|
592
|
-
const outside = existing
|
|
593
|
-
.replace(/(?:^|\n)# knodin:start\n[\s\S]*?\n# knodin:end\n?/, "\n")
|
|
594
|
-
.replace(/(?:^|\n)# reckon-graph:start\n[\s\S]*?\n# reckon-graph:end\n?/, "\n");
|
|
577
|
+
const outside = existing.replace(/(?:^|\n)# knodin:start\n[\s\S]*?\n# knodin:end\n?/, "\n");
|
|
595
578
|
const outsideLines = new Set(outside.split(/\r?\n/));
|
|
596
579
|
const owned = safe.filter((candidate) => !outsideLines.has(candidate));
|
|
597
580
|
const prefix = outside && !outside.endsWith("\n") ? `${outside}\n` : outside;
|
|
@@ -666,7 +649,7 @@ async function removeManagedPersonalArtifacts(repo, allowTrackedTransition = fal
|
|
|
666
649
|
try {
|
|
667
650
|
const current = await fs.promises.readFile(gitIgnorePath, "utf-8");
|
|
668
651
|
const next = current
|
|
669
|
-
.replace(/(?:^|\n)# knodin:start\n\.
|
|
652
|
+
.replace(/(?:^|\n)# knodin:start\n\.knodin\/\n# knodin:end\n?/g, "\n")
|
|
670
653
|
.trim();
|
|
671
654
|
if (isTracked(repo, ".gitignore")) {
|
|
672
655
|
const head = runGit(repo, ["show", "HEAD:.gitignore"]);
|
|
@@ -702,7 +685,7 @@ async function removeManagedPersonalArtifacts(repo, allowTrackedTransition = fal
|
|
|
702
685
|
}
|
|
703
686
|
export function readRepositoryIntegrationConfig(repo) {
|
|
704
687
|
try {
|
|
705
|
-
const value = JSON.parse(fs.readFileSync(path.join(repo, ".
|
|
688
|
+
const value = JSON.parse(fs.readFileSync(path.join(repo, ".knodin", "integration.json"), "utf-8"));
|
|
706
689
|
if ((value.scope === "personal" || value.scope === "team" || value.scope === "cli-only") &&
|
|
707
690
|
Array.isArray(value.agents))
|
|
708
691
|
return { scope: value.scope, agents: value.agents };
|
|
@@ -745,10 +728,10 @@ export function inspectRepositoryIntegrationStatus(repo) {
|
|
|
745
728
|
};
|
|
746
729
|
}
|
|
747
730
|
async function writeRepositoryIntegrationConfig(repo, config) {
|
|
748
|
-
await fs.promises.writeFile(path.join(repo, ".
|
|
731
|
+
await fs.promises.writeFile(path.join(repo, ".knodin", "integration.json"), `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
749
732
|
}
|
|
750
733
|
async function ensureTeamGitIgnore(repo) {
|
|
751
|
-
if (isTracked(repo, ".
|
|
734
|
+
if (isTracked(repo, ".knodin"))
|
|
752
735
|
return;
|
|
753
736
|
await assertSafeRepositoryFile(repo, ".gitignore");
|
|
754
737
|
const ignorePath = path.join(repo, ".gitignore");
|
|
@@ -760,10 +743,10 @@ async function ensureTeamGitIgnore(repo) {
|
|
|
760
743
|
if (error.code !== "ENOENT")
|
|
761
744
|
throw error;
|
|
762
745
|
}
|
|
763
|
-
if (existing.split(/\r?\n/).includes(".
|
|
746
|
+
if (existing.split(/\r?\n/).includes(".knodin/"))
|
|
764
747
|
return;
|
|
765
748
|
const prefix = existing && !existing.endsWith("\n") ? `${existing}\n` : existing;
|
|
766
|
-
await fs.promises.writeFile(ignorePath, `${prefix}# knodin:start\n.
|
|
749
|
+
await fs.promises.writeFile(ignorePath, `${prefix}# knodin:start\n.knodin/\n# knodin:end\n`, "utf-8");
|
|
767
750
|
}
|
|
768
751
|
export function detectTrackedTeamIntegration(repo) {
|
|
769
752
|
const checks = [
|
|
@@ -802,7 +785,7 @@ export async function initializeRepository(repo, options) {
|
|
|
802
785
|
detectTrackedTeamIntegration(resolvedRepo)) {
|
|
803
786
|
throw new Error("knodin init: tracked team integration is active; use `knodin configure --scope personal` or `--scope cli-only` for an explicit commit-ready transition");
|
|
804
787
|
}
|
|
805
|
-
const knodinHooksDir = path.join(resolvedRepo, ".
|
|
788
|
+
const knodinHooksDir = path.join(resolvedRepo, ".knodin", "hooks");
|
|
806
789
|
const hookInstallation = await prepareHookInstallation(resolvedRepo);
|
|
807
790
|
const hooksDir = hookInstallation.hooksDir;
|
|
808
791
|
await Promise.all([
|
|
@@ -908,15 +891,15 @@ export async function initializeRepository(repo, options) {
|
|
|
908
891
|
const excluded = scope === "team"
|
|
909
892
|
? []
|
|
910
893
|
: await excludeUntrackedPaths(resolvedRepo, [
|
|
911
|
-
...(isTracked(resolvedRepo, ".
|
|
894
|
+
...(isTracked(resolvedRepo, ".knodin") ? [] : [".knodin/"]),
|
|
912
895
|
...agentIntegration.projectFiles,
|
|
913
896
|
]);
|
|
914
897
|
registerInitializedWorktree(resolvedRepo);
|
|
915
898
|
return {
|
|
916
899
|
scope,
|
|
917
|
-
knodin: ".
|
|
918
|
-
database: ".
|
|
919
|
-
backgroundIndexer: ".
|
|
900
|
+
knodin: ".knodin",
|
|
901
|
+
database: ".knodin/db.sqlite",
|
|
902
|
+
backgroundIndexer: ".knodin/hooks/background-index.sh",
|
|
920
903
|
hooksDirectory: path.relative(resolvedRepo, hooksDir) || ".",
|
|
921
904
|
gitHooks: [...HOOK_NAMES],
|
|
922
905
|
claudeSettings: null,
|
|
@@ -979,7 +962,7 @@ export async function isRepositoryInitializationCurrent(repo, command, scope = "
|
|
|
979
962
|
((await hasManagedAgentInstructions(resolvedRepo, "AGENTS.md")) &&
|
|
980
963
|
(await hasManagedAgentInstructions(resolvedRepo, "GEMINI.md")));
|
|
981
964
|
const integrationCurrent = readRepositoryIntegrationConfig(resolvedRepo)?.scope === scope;
|
|
982
|
-
return ((await matchesManagedFile(path.join(resolvedRepo, ".
|
|
965
|
+
return ((await matchesManagedFile(path.join(resolvedRepo, ".knodin", "hooks", "background-index.sh"), backgroundScript(command), true)) &&
|
|
983
966
|
sharedFilesCurrent &&
|
|
984
967
|
integrationCurrent);
|
|
985
968
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import childProcess from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
const
|
|
4
|
+
const MANAGED_MARKER = "KNODIN MANAGED HOOK";
|
|
5
5
|
const HOOK_NAMES = ["post-commit", "post-checkout", "post-merge", "post-rewrite"];
|
|
6
6
|
const HOOK_FAILURE_FILE = "last-refresh-failure";
|
|
7
7
|
function readEvent(target) {
|
|
@@ -35,11 +35,11 @@ function readEvent(target) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
function readLastEvent(repo) {
|
|
38
|
-
return readEvent(path.join(repo, ".
|
|
38
|
+
return readEvent(path.join(repo, ".knodin", "hooks", "last-refresh-event"));
|
|
39
39
|
}
|
|
40
40
|
function readPendingEvent(repo) {
|
|
41
41
|
try {
|
|
42
|
-
const directory = path.join(repo, ".
|
|
42
|
+
const directory = path.join(repo, ".knodin", "hooks", "events");
|
|
43
43
|
const next = fs
|
|
44
44
|
.readdirSync(directory)
|
|
45
45
|
.filter((entry) => entry.endsWith(".event"))
|
|
@@ -51,7 +51,7 @@ function readPendingEvent(repo) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
function processorState(repo) {
|
|
54
|
-
const pidPath = path.join(repo, ".
|
|
54
|
+
const pidPath = path.join(repo, ".knodin", "hooks", "refresh.lock", "pid");
|
|
55
55
|
if (!fs.existsSync(path.dirname(pidPath)))
|
|
56
56
|
return { state: "idle", pid: null };
|
|
57
57
|
let pid = null;
|
|
@@ -78,7 +78,7 @@ function processorState(repo) {
|
|
|
78
78
|
function queuedEventCount(repo) {
|
|
79
79
|
try {
|
|
80
80
|
return fs
|
|
81
|
-
.readdirSync(path.join(repo, ".
|
|
81
|
+
.readdirSync(path.join(repo, ".knodin", "hooks", "events"))
|
|
82
82
|
.filter((entry) => entry.endsWith(".event")).length;
|
|
83
83
|
}
|
|
84
84
|
catch {
|
|
@@ -97,8 +97,7 @@ function inspectManagedHooks(hooksDirectory) {
|
|
|
97
97
|
const hookPath = path.join(hooksDirectory, hookName);
|
|
98
98
|
const content = fs.readFileSync(hookPath, "utf-8");
|
|
99
99
|
const mode = fs.statSync(hookPath).mode;
|
|
100
|
-
const active = (mode & 0o111) !== 0 &&
|
|
101
|
-
MANAGED_MARKERS.some((marker) => content.startsWith(`#!/bin/sh\n# ${marker}: ${hookName}\n`));
|
|
100
|
+
const active = (mode & 0o111) !== 0 && content.startsWith(`#!/bin/sh\n# ${MANAGED_MARKER}: ${hookName}\n`);
|
|
102
101
|
(active ? activeHooks : missingHooks).push(hookName);
|
|
103
102
|
}
|
|
104
103
|
catch {
|
|
@@ -108,7 +107,7 @@ function inspectManagedHooks(hooksDirectory) {
|
|
|
108
107
|
return { activeHooks, missingHooks };
|
|
109
108
|
}
|
|
110
109
|
function readRefreshFailure(repo) {
|
|
111
|
-
const failurePath = path.join(repo, ".
|
|
110
|
+
const failurePath = path.join(repo, ".knodin", "hooks", HOOK_FAILURE_FILE);
|
|
112
111
|
if (!fs.existsSync(failurePath))
|
|
113
112
|
return null;
|
|
114
113
|
try {
|
|
@@ -122,7 +121,7 @@ function readRefreshFailure(repo) {
|
|
|
122
121
|
/** Read-only proof that committed Git activity can still reach knodin's indexer. */
|
|
123
122
|
export function inspectLifecycleHealth(repoPath) {
|
|
124
123
|
const repo = path.resolve(repoPath);
|
|
125
|
-
if (!fs.existsSync(path.join(repo, ".
|
|
124
|
+
if (!fs.existsSync(path.join(repo, ".knodin", "db.sqlite"))) {
|
|
126
125
|
return {
|
|
127
126
|
status: "unconfigured",
|
|
128
127
|
refreshCapable: false,
|
|
@@ -157,14 +156,14 @@ export function inspectLifecycleHealth(repoPath) {
|
|
|
157
156
|
};
|
|
158
157
|
}
|
|
159
158
|
const { activeHooks, missingHooks } = inspectManagedHooks(hooksDirectory);
|
|
160
|
-
const background = path.join(repo, ".
|
|
159
|
+
const background = path.join(repo, ".knodin", "hooks", "background-index.sh");
|
|
161
160
|
const backgroundReady = fs.existsSync(background) && (fs.statSync(background).mode & 0o111) !== 0;
|
|
162
161
|
const issues = missingHooks.map((hook) => `${hook} no longer routes through knodin's active Git hook path`);
|
|
163
162
|
if (!backgroundReady)
|
|
164
163
|
issues.push("background indexer is missing or not executable");
|
|
165
164
|
const lastError = readRefreshFailure(repo);
|
|
166
165
|
if (lastError)
|
|
167
|
-
issues.push(`${lastError} See .
|
|
166
|
+
issues.push(`${lastError} See .knodin/indexer.log for details`);
|
|
168
167
|
const refreshCapable = missingHooks.length === 0 && backgroundReady && issues.length === 0;
|
|
169
168
|
return {
|
|
170
169
|
status: refreshCapable ? "healthy" : "degraded",
|
package/dist/src/lsp-readonly.js
CHANGED
|
@@ -59,7 +59,7 @@ function filesForService(repoPath, ts) {
|
|
|
59
59
|
const files = [];
|
|
60
60
|
const visit = (directory) => {
|
|
61
61
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
62
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".
|
|
62
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".knodin")
|
|
63
63
|
continue;
|
|
64
64
|
const candidate = path.join(directory, entry.name);
|
|
65
65
|
if (entry.isDirectory())
|
|
@@ -335,7 +335,7 @@ function percentReduction(input, output) {
|
|
|
335
335
|
}
|
|
336
336
|
function artifactDirectory(repo) {
|
|
337
337
|
const resolvedRepo = fs.realpathSync(repo);
|
|
338
|
-
const knodinDirectory = path.join(resolvedRepo, ".
|
|
338
|
+
const knodinDirectory = path.join(resolvedRepo, ".knodin");
|
|
339
339
|
const outputDirectory = path.join(knodinDirectory, "output");
|
|
340
340
|
for (const candidate of [knodinDirectory, outputDirectory]) {
|
|
341
341
|
if (fs.existsSync(candidate) && fs.lstatSync(candidate).isSymbolicLink())
|
|
@@ -5,7 +5,7 @@ import { encode } from "gpt-tokenizer/encoding/o200k_base";
|
|
|
5
5
|
export const TELEMETRY_TOKENIZER = "gpt-tokenizer@3.4.0:o200k_base";
|
|
6
6
|
export const countOutputTokens = (value) => encode(value).length;
|
|
7
7
|
export const DEFAULT_TELEMETRY_RETENTION_DAYS = 30;
|
|
8
|
-
const DEFAULT_INPUT = ".
|
|
8
|
+
const DEFAULT_INPUT = ".knodin-telemetry.jsonl";
|
|
9
9
|
function lineCount(value) {
|
|
10
10
|
return value.length === 0 ? 0 : value.split(/\r?\n/).length;
|
|
11
11
|
}
|
|
@@ -104,7 +104,7 @@ export function measureOutput(options) {
|
|
|
104
104
|
const availability = options.output.availability;
|
|
105
105
|
const freshness = options.output.freshness;
|
|
106
106
|
const fidelity = options.output.fidelity;
|
|
107
|
-
const database = path.join(fs.realpathSync(options.repo), ".
|
|
107
|
+
const database = path.join(fs.realpathSync(options.repo), ".knodin", "db.sqlite");
|
|
108
108
|
const latencyMs = Math.round((performance.now() - options.startedAt) * 100) / 100;
|
|
109
109
|
const outcome = (status !== undefined &&
|
|
110
110
|
["error", "failed", "unavailable", "blocked", "timeout"].includes(status)) ||
|
|
@@ -180,7 +180,7 @@ function telemetrySummary(records) {
|
|
|
180
180
|
indexingMs: records.reduce((sum, record) => sum + (record.indexingMs ?? 0), 0),
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
-
export function writeTelemetryReport(repoPath, records, outputPath = ".
|
|
183
|
+
export function writeTelemetryReport(repoPath, records, outputPath = ".knodin/telemetry-report.html") {
|
|
184
184
|
const { repo, target: output } = privatePath(repoPath, outputPath, "report");
|
|
185
185
|
const groups = new Map();
|
|
186
186
|
for (const record of records) {
|
|
@@ -326,7 +326,7 @@ export function telemetryStatus(repoPath, inputPath = DEFAULT_INPUT, retentionDa
|
|
|
326
326
|
tokenizer: TELEMETRY_TOKENIZER,
|
|
327
327
|
};
|
|
328
328
|
}
|
|
329
|
-
export function exportTelemetry(repoPath, records, outputPath = ".
|
|
329
|
+
export function exportTelemetry(repoPath, records, outputPath = ".knodin/telemetry-export.json") {
|
|
330
330
|
const { repo, target } = privatePath(repoPath, outputPath, "export");
|
|
331
331
|
const bundle = {
|
|
332
332
|
schemaVersion: 1,
|
package/dist/src/pr-triage.js
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
* than `ensureGhAvailable()` (throwing, still used by the CLI's hard-fail).
|
|
13
13
|
*/
|
|
14
14
|
import child_process from "node:child_process";
|
|
15
|
-
/** Resolve the `gh` binary to invoke. Tests override via `
|
|
15
|
+
/** Resolve the `gh` binary to invoke. Tests override via `KNODIN_GH_BIN` to
|
|
16
16
|
* point at a stub script fixture — no network in tests. */
|
|
17
17
|
function ghBin() {
|
|
18
|
-
return process.env.
|
|
18
|
+
return process.env.KNODIN_GH_BIN || "gh";
|
|
19
19
|
}
|
|
20
20
|
const STALE_DAYS = 30;
|
|
21
21
|
const PR_LIST_FIELDS = "number,title,updatedAt,reviewDecision,statusCheckRollup,files,headRefName";
|
|
@@ -3,7 +3,7 @@ import { clearLine as clearTerminalLine, moveCursor as moveTerminalCursor, curso
|
|
|
3
3
|
import ora from "ora";
|
|
4
4
|
import { normalizeTerminalDimension } from "./init-progress.js";
|
|
5
5
|
import { formatRepairProgress } from "./repair-progress.js";
|
|
6
|
-
const terminalEnabled = process.stderr.isTTY === true || process.env.
|
|
6
|
+
const terminalEnabled = process.stderr.isTTY === true || process.env.KNODIN_FORCE_PROGRESS_TTY === "1";
|
|
7
7
|
const progressStream = {
|
|
8
8
|
isTTY: terminalEnabled,
|
|
9
9
|
columns: normalizeTerminalDimension(process.stderr.columns, 80),
|
|
@@ -68,7 +68,7 @@ export async function runRepositoryInitializationProcess(options) {
|
|
|
68
68
|
detached: process.platform !== "win32",
|
|
69
69
|
env: {
|
|
70
70
|
...process.env,
|
|
71
|
-
|
|
71
|
+
KNODIN_INTERNAL_INIT_MEMORY_LIMIT_BYTES: String(memoryLimitBytes),
|
|
72
72
|
},
|
|
73
73
|
stdio: ["ignore", "ignore", "pipe", "ipc"],
|
|
74
74
|
});
|
|
@@ -10,7 +10,7 @@ const MAX_DEPTH = 32;
|
|
|
10
10
|
const MAX_GIT_ERROR_BYTES = 16 * 1024;
|
|
11
11
|
const MAX_TRACKED_PATH_BYTES = 1024 * 1024;
|
|
12
12
|
const MAX_DOCUMENT_CANDIDATES = 20;
|
|
13
|
-
const SKIPPED_DIRECTORIES = new Set([".git", ".
|
|
13
|
+
const SKIPPED_DIRECTORIES = new Set([".git", ".knodin", "node_modules"]);
|
|
14
14
|
function gitTopLevel(directory) {
|
|
15
15
|
return child_process
|
|
16
16
|
.execFileSync(gitExecutable(), ["rev-parse", "--show-toplevel"], {
|
|
@@ -650,7 +650,7 @@ function inventoryFreshness(lastIndexedHead, head) {
|
|
|
650
650
|
export async function inventoryRepository(record, options) {
|
|
651
651
|
const id = repositoryIdentity(record.path);
|
|
652
652
|
const mainWorktreeId = repositoryIdentity(record.mainWorktree);
|
|
653
|
-
const databaseExists = fs.existsSync(path.join(record.path, ".
|
|
653
|
+
const databaseExists = fs.existsSync(path.join(record.path, ".knodin", "db.sqlite"));
|
|
654
654
|
const head = gitValue(record.path, ["rev-parse", "HEAD"]);
|
|
655
655
|
let tracked;
|
|
656
656
|
try {
|
|
@@ -946,7 +946,7 @@ async function initializeOneRepository(repository, options) {
|
|
|
946
946
|
if (!options.dryRun && options.isolatedInitialize) {
|
|
947
947
|
return await options.isolatedInitialize(repository);
|
|
948
948
|
}
|
|
949
|
-
const databaseExists = fs.existsSync(path.join(repository, ".
|
|
949
|
+
const databaseExists = fs.existsSync(path.join(repository, ".knodin", "db.sqlite"));
|
|
950
950
|
const integration = readRepositoryIntegrationConfig(repository);
|
|
951
951
|
const scope = integration?.scope ?? "personal";
|
|
952
952
|
const agents = [...new Set([...(options.agents ?? []), ...(integration?.agents ?? [])])];
|
package/dist/src/server.js
CHANGED
|
@@ -23,7 +23,7 @@ export function createServer() {
|
|
|
23
23
|
}));
|
|
24
24
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
25
25
|
const { name, arguments: args } = request.params;
|
|
26
|
-
if (name !== "knodin"
|
|
26
|
+
if (name !== "knodin") {
|
|
27
27
|
throw new Error(`unknown tool: ${name}`);
|
|
28
28
|
}
|
|
29
29
|
const result = await handleKnodinTool(args);
|
|
@@ -78,7 +78,7 @@ function configuredRepository(value, index) {
|
|
|
78
78
|
id: stringValue(record.id, `repositories[${index}].id`),
|
|
79
79
|
role,
|
|
80
80
|
indexMode: explicitIndex ?? (derived ? "provenance-only" : "full"),
|
|
81
|
-
policySource: explicitIndex ? "
|
|
81
|
+
policySource: explicitIndex ? "knodin.yaml" : "role-default",
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
function configuredSystem(value, systemIndex) {
|
|
@@ -97,7 +97,7 @@ function configuredSystem(value, systemIndex) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
function applyLegacyFederation(repo, repositories, relationships) {
|
|
100
|
-
const legacyPath = path.join(repo, ".
|
|
100
|
+
const legacyPath = path.join(repo, ".knodin", "federation.json");
|
|
101
101
|
let legacy;
|
|
102
102
|
try {
|
|
103
103
|
legacy = asRecord(JSON.parse(fs.readFileSync(legacyPath, "utf-8")));
|
|
@@ -146,7 +146,7 @@ function applyLegacyFederation(repo, repositories, relationships) {
|
|
|
146
146
|
to: id,
|
|
147
147
|
targetIdentity: id,
|
|
148
148
|
evidence: {
|
|
149
|
-
location: ".
|
|
149
|
+
location: ".knodin/federation.json",
|
|
150
150
|
adapter: "legacy-federation",
|
|
151
151
|
kind: "compatibility",
|
|
152
152
|
confidence: 1,
|
|
@@ -165,7 +165,7 @@ function applyPersonalMappings(repositories, xdgConfigHome) {
|
|
|
165
165
|
if (typeof checkout !== "string")
|
|
166
166
|
continue;
|
|
167
167
|
if (!path.isAbsolute(checkout)) {
|
|
168
|
-
throw new Error(`personal repository path for ${repository.id} must be absolute; team config keeps paths out of
|
|
168
|
+
throw new Error(`personal repository path for ${repository.id} must be absolute; team config keeps paths out of knodin.yaml`);
|
|
169
169
|
}
|
|
170
170
|
repository.path = path.normalize(checkout);
|
|
171
171
|
repository.pathSource = "personal-config";
|
|
@@ -303,7 +303,7 @@ function declaredRelationship(value, index) {
|
|
|
303
303
|
to,
|
|
304
304
|
targetIdentity: to,
|
|
305
305
|
evidence: {
|
|
306
|
-
location: typeof source.location === "string" && source.location ? source.location : "
|
|
306
|
+
location: typeof source.location === "string" && source.location ? source.location : "knodin.yaml",
|
|
307
307
|
adapter: "knodin-yaml",
|
|
308
308
|
kind: "declared",
|
|
309
309
|
confidence,
|
|
@@ -314,9 +314,9 @@ function declaredRelationship(value, index) {
|
|
|
314
314
|
}
|
|
315
315
|
export function loadSystemConfiguration(repoPath, options = {}) {
|
|
316
316
|
const repo = path.resolve(repoPath);
|
|
317
|
-
const team = asRecord(optionalYaml(path.join(repo, "
|
|
317
|
+
const team = asRecord(optionalYaml(path.join(repo, "knodin.yaml")));
|
|
318
318
|
if (team.schemaVersion !== undefined && team.schemaVersion !== 1) {
|
|
319
|
-
throw new Error("
|
|
319
|
+
throw new Error("knodin.yaml: schemaVersion must be 1");
|
|
320
320
|
}
|
|
321
321
|
const repositories = (Array.isArray(team.repositories) ? team.repositories : []).map((value, index) => configuredRepository(value, index));
|
|
322
322
|
const systems = (Array.isArray(team.systems) ? team.systems : []).map((value, index) => configuredSystem(value, index));
|
|
@@ -163,7 +163,7 @@ function buildDocumentedKnodinTools() {
|
|
|
163
163
|
"system",
|
|
164
164
|
"telemetry",
|
|
165
165
|
],
|
|
166
|
-
description: "Which knodin capability to run. context: call this FIRST when starting any investigation and unsure which operation to reach for — one ultra-compact orientation (repo stats + top subsystems/hubs/flows + a risk score if there's a diff + a heuristic next-operation suggestion); the suggestion is only a hint and never blocks calling any operation directly. explain: use when orienting on a symbol/file or before editing it — returns edit-ready source + call paths + blast radius. review: use before writing a PR description or approving a diff — risk-scored context (changed symbols, affected flows, test gaps). map: use before a cross-cutting refactor or to understand subsystem boundaries — communities + hub/bridge nodes + confidence-tagged edges. search: use when you don't know the exact symbol name — hybrid semantic + keyword lookup over code symbols. query: use for a structured question about a known symbol — callers_of, tests_for, shortest_path, dead_code, rename_preview, flows, and more (see `pattern`). pack: create deterministic Markdown/JSON/XML source context under hard budgets, or bounded-read/exact-regex-grep a saved artifact. compress: reduce already-produced diagnostic text under exact line/content-byte budgets, preserve exit metadata and detected signals, retain private local drill-down data by default, and never label insufficient-fidelity output complete. prs: use to triage open GitHub PRs (via your local authenticated `gh`) — per-PR status, CI, and blast radius sorted ready-small-impact first; pass `prNumber` for one PR's impacted files + community names. wiki: write a static markdown documentation site for the repository's logical subsystems to `.
|
|
166
|
+
description: "Which knodin capability to run. context: call this FIRST when starting any investigation and unsure which operation to reach for — one ultra-compact orientation (repo stats + top subsystems/hubs/flows + a risk score if there's a diff + a heuristic next-operation suggestion); the suggestion is only a hint and never blocks calling any operation directly. explain: use when orienting on a symbol/file or before editing it — returns edit-ready source + call paths + blast radius. review: use before writing a PR description or approving a diff — risk-scored context (changed symbols, affected flows, test gaps). map: use before a cross-cutting refactor or to understand subsystem boundaries — communities + hub/bridge nodes + confidence-tagged edges. search: use when you don't know the exact symbol name — hybrid semantic + keyword lookup over code symbols. query: use for a structured question about a known symbol — callers_of, tests_for, shortest_path, dead_code, rename_preview, flows, and more (see `pattern`). pack: create deterministic Markdown/JSON/XML source context under hard budgets, or bounded-read/exact-regex-grep a saved artifact. compress: reduce already-produced diagnostic text under exact line/content-byte budgets, preserve exit metadata and detected signals, retain private local drill-down data by default, and never label insufficient-fidelity output complete. prs: use to triage open GitHub PRs (via your local authenticated `gh`) — per-PR status, CI, and blast radius sorted ready-small-impact first; pass `prNumber` for one PR's impacted files + community names. wiki: write a static markdown documentation site for the repository's logical subsystems to `.knodin/wiki/`. Generates an index plus one page per mapped community. Reuses the `map` output. Idempotent: unchanged pages are untouched on disk unless `force` is true. docs: call this to retrieve curated, focused markdown usage guidance directly over MCP.",
|
|
167
167
|
},
|
|
168
168
|
symbol: {
|
|
169
169
|
type: "string",
|
|
@@ -610,7 +610,7 @@ function buildDocumentedKnodinTools() {
|
|
|
610
610
|
},
|
|
611
611
|
persistTelemetry: {
|
|
612
612
|
type: "boolean",
|
|
613
|
-
description: "Opt in to local metadata-only JSONL persistence at .
|
|
613
|
+
description: "Opt in to local metadata-only JSONL persistence at .knodin-telemetry.jsonl; never records source.",
|
|
614
614
|
},
|
|
615
615
|
},
|
|
616
616
|
required: ["operation"],
|
|
@@ -100,8 +100,8 @@ export function claimScheduledUpdateCheck(options) {
|
|
|
100
100
|
if (!config ||
|
|
101
101
|
!config.enabled ||
|
|
102
102
|
config.offline ||
|
|
103
|
-
env.
|
|
104
|
-
env.
|
|
103
|
+
env.KNODIN_UPDATE_CHECK === "0" ||
|
|
104
|
+
env.KNODIN_OFFLINE === "1")
|
|
105
105
|
return false;
|
|
106
106
|
const now = options.now?.() ?? new Date();
|
|
107
107
|
const checkedAt = readTrustedUpdateState(options.stateHome)?.checkedAt;
|
|
@@ -474,9 +474,9 @@ async function checkedUpdate(options) {
|
|
|
474
474
|
if (!config)
|
|
475
475
|
return baseResult(options, "trust-unconfigured");
|
|
476
476
|
const env = options.env ?? process.env;
|
|
477
|
-
if (!config.enabled || env.
|
|
477
|
+
if (!config.enabled || env.KNODIN_UPDATE_CHECK === "0")
|
|
478
478
|
return baseResult(options, "disabled");
|
|
479
|
-
if (config.offline || env.
|
|
479
|
+
if (config.offline || env.KNODIN_OFFLINE === "1")
|
|
480
480
|
return baseResult(options, "offline");
|
|
481
481
|
const now = options.now?.() ?? new Date();
|
|
482
482
|
const previous = readTrustedUpdateState(options.stateHome);
|