akm-cli 0.9.0-beta.5 → 0.9.0-beta.51
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/CHANGELOG.md +711 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +6 -2
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +38 -10
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +27 -11
- package/dist/commands/agent/agent-dispatch.js +2 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +17 -4
- package/dist/commands/config-cli.js +18 -2
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +33 -26
- package/dist/commands/env/secret-cli.js +36 -22
- package/dist/commands/feedback-cli.js +15 -6
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/checks.js +49 -1
- package/dist/commands/health/html-report.js +422 -80
- package/dist/commands/health.js +386 -9
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +81 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +635 -660
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill.js +159 -69
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +124 -2
- package/dist/commands/improve/extract-prompt.js +39 -2
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +389 -40
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/homeostatic.js +467 -0
- package/dist/commands/improve/improve-auto-accept.js +138 -7
- package/dist/commands/improve/improve-cli.js +36 -61
- package/dist/commands/improve/improve-profiles.js +14 -0
- package/dist/commands/improve/improve-result-file.js +14 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2498
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1083 -0
- package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
- package/dist/commands/improve/outcome-loop.js +256 -0
- package/dist/commands/improve/preparation.js +1966 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +418 -0
- package/dist/commands/improve/recombine.js +850 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +183 -40
- package/dist/commands/improve/salience.js +438 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/proposal/drain-policies.js +5 -0
- package/dist/commands/proposal/drain.js +43 -50
- package/dist/commands/proposal/proposal-cli.js +21 -31
- package/dist/commands/proposal/proposal.js +5 -0
- package/dist/commands/proposal/propose.js +7 -2
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +189 -63
- package/dist/commands/read/curate.js +414 -94
- package/dist/commands/read/knowledge.js +6 -3
- package/dist/commands/read/search-cli.js +9 -4
- package/dist/commands/read/search.js +10 -6
- package/dist/commands/read/show.js +86 -7
- package/dist/commands/sources/init.js +49 -17
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +43 -45
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/stash-cli.js +28 -40
- package/dist/commands/sources/stash-skeleton.js +23 -8
- package/dist/commands/tasks/tasks-cli.js +19 -27
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +21 -35
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +18 -2
- package/dist/core/asset/frontmatter.js +166 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +0 -5
- package/dist/core/config/config-migration.js +12 -11
- package/dist/core/config/config-schema.js +340 -56
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +28 -7
- package/dist/core/events.js +3 -7
- package/dist/core/improve-types.js +11 -8
- package/dist/core/logs-db.js +10 -66
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +3 -0
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +714 -0
- package/dist/core/state-db.js +525 -474
- package/dist/indexer/db/db.js +439 -247
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/ensure-index.js +152 -17
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +218 -4
- package/dist/indexer/index-writer-lock.js +99 -0
- package/dist/indexer/indexer.js +123 -221
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/staleness-detect.js +2 -5
- package/dist/indexer/search/db-search.js +19 -6
- package/dist/indexer/search/ranking-contributors.js +22 -0
- package/dist/indexer/search/ranking.js +4 -0
- package/dist/indexer/search/search-source.js +17 -18
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/integrations/agent/config.js +6 -53
- package/dist/integrations/agent/index.js +2 -18
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/client.js +45 -15
- package/dist/llm/embedder.js +42 -3
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +66 -2
- package/dist/llm/feature-gate.js +8 -4
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +74 -2
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/passthrough.js +0 -1
- package/dist/output/text/helpers.js +16 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +22 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2617 -1961
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
- package/dist/setup/setup.js +29 -8
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/tar-utils.js +16 -8
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/registry-cache.js +92 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +5 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +37 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +1 -117
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/data-and-telemetry.md +3 -2
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +13 -11
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
|
@@ -63,6 +63,11 @@ export const searchCommand = defineJsonCommand({
|
|
|
63
63
|
description: "Disable the automatic project-context ranking boost (also disabled by AKM_DISABLE_PROJECT_CONTEXT=1).",
|
|
64
64
|
default: false,
|
|
65
65
|
},
|
|
66
|
+
"include-sessions": {
|
|
67
|
+
type: "boolean",
|
|
68
|
+
description: "Include session assets (excluded from default search results via config.search.defaultExcludeTypes).",
|
|
69
|
+
default: false,
|
|
70
|
+
},
|
|
66
71
|
},
|
|
67
72
|
async run({ args }) {
|
|
68
73
|
const query = (args.query ?? "").trim();
|
|
@@ -79,10 +84,7 @@ export const searchCommand = defineJsonCommand({
|
|
|
79
84
|
const includeProposed = args["include-proposed"] === true;
|
|
80
85
|
const belief = parseBeliefFilterMode(typeof args.belief === "string" ? args.belief : undefined);
|
|
81
86
|
const noProjectContext = getHyphenatedBoolean(args, "no-project-context");
|
|
82
|
-
|
|
83
|
-
// threading the flag through the entire call stack.
|
|
84
|
-
if (noProjectContext)
|
|
85
|
-
process.env.AKM_DISABLE_PROJECT_CONTEXT = "1";
|
|
87
|
+
const includeSessions = getHyphenatedBoolean(args, "include-sessions");
|
|
86
88
|
const result = await akmSearch({
|
|
87
89
|
query,
|
|
88
90
|
type,
|
|
@@ -91,6 +93,9 @@ export const searchCommand = defineJsonCommand({
|
|
|
91
93
|
filters,
|
|
92
94
|
includeProposed,
|
|
93
95
|
belief,
|
|
96
|
+
includeSessions,
|
|
97
|
+
disableProjectContext: noProjectContext,
|
|
98
|
+
disableScopedUtility: noProjectContext,
|
|
94
99
|
eventSource: resolveEventSource(),
|
|
95
100
|
});
|
|
96
101
|
output("search", result);
|
|
@@ -108,6 +108,9 @@ export async function akmSearch(input) {
|
|
|
108
108
|
// Without this, the index (which spans every configured source)
|
|
109
109
|
// would leak hits from sources the caller did not request.
|
|
110
110
|
restrictToSources: namedSourceName !== undefined,
|
|
111
|
+
includeExcludedTypes: input.includeSessions === true,
|
|
112
|
+
disableProjectContext: input.disableProjectContext === true,
|
|
113
|
+
disableScopedUtility: input.disableScopedUtility === true,
|
|
111
114
|
});
|
|
112
115
|
const registryResult = source === "stash" ? undefined : await searchRegistry(query, { limit, registries: config.registries });
|
|
113
116
|
if (source === "stash") {
|
|
@@ -122,8 +125,9 @@ export async function akmSearch(input) {
|
|
|
122
125
|
warnings: localResult?.warnings?.length ? localResult.warnings : undefined,
|
|
123
126
|
timing: { totalMs: Date.now() - t0, rankMs: localResult?.rankMs, embedMs: localResult?.embedMs },
|
|
124
127
|
};
|
|
125
|
-
if (!input.skipLogging)
|
|
126
|
-
logSearchEvent(query, response, localResult?.mode ?? "keyword", input.eventSource);
|
|
128
|
+
if (!input.skipLogging) {
|
|
129
|
+
logSearchEvent(query, response, localResult?.mode ?? "keyword", input.eventSource, input.disableScopedUtility === true);
|
|
130
|
+
}
|
|
127
131
|
return response;
|
|
128
132
|
}
|
|
129
133
|
const registryHits = (registryResult?.hits ?? []).map((hit) => {
|
|
@@ -158,7 +162,7 @@ export async function akmSearch(input) {
|
|
|
158
162
|
timing: { totalMs: Date.now() - t0 },
|
|
159
163
|
};
|
|
160
164
|
if (!input.skipLogging)
|
|
161
|
-
logSearchEvent(query, response, undefined, input.eventSource);
|
|
165
|
+
logSearchEvent(query, response, undefined, input.eventSource, input.disableScopedUtility === true);
|
|
162
166
|
return response;
|
|
163
167
|
}
|
|
164
168
|
// source === "both"
|
|
@@ -176,7 +180,7 @@ export async function akmSearch(input) {
|
|
|
176
180
|
timing: { totalMs: Date.now() - t0 },
|
|
177
181
|
};
|
|
178
182
|
if (!input.skipLogging)
|
|
179
|
-
logSearchEvent(query, response, undefined, input.eventSource);
|
|
183
|
+
logSearchEvent(query, response, undefined, input.eventSource, input.disableScopedUtility === true);
|
|
180
184
|
return response;
|
|
181
185
|
}
|
|
182
186
|
/**
|
|
@@ -211,7 +215,7 @@ function resolveEntryIds(db, hits) {
|
|
|
211
215
|
* Per-entry events are recorded only for stash hits because registry hits
|
|
212
216
|
* have no local entry_id to reference.
|
|
213
217
|
*/
|
|
214
|
-
function logSearchEvent(query, response, mode = "keyword", eventSource = "user") {
|
|
218
|
+
function logSearchEvent(query, response, mode = "keyword", eventSource = "user", disableScopedUtility = false) {
|
|
215
219
|
// Emit a structured event to events.jsonl so workflow-trace consumers
|
|
216
220
|
// detect akm search invocations without relying on stdout scraping.
|
|
217
221
|
const stashHits = response.hits.filter((h) => h.type !== "registry");
|
|
@@ -241,7 +245,7 @@ function logSearchEvent(query, response, mode = "keyword", eventSource = "user")
|
|
|
241
245
|
let scopeKey;
|
|
242
246
|
try {
|
|
243
247
|
const stashPath = response.stashDir;
|
|
244
|
-
const disabled =
|
|
248
|
+
const disabled = disableScopedUtility || (stashPath && isTransientStashPath(stashPath));
|
|
245
249
|
scopeKey = disabled ? undefined : getCurrentWorkflowScopeKey();
|
|
246
250
|
}
|
|
247
251
|
catch {
|
|
@@ -18,22 +18,27 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
|
+
import { findCittyTopLevelCommandIndex } from "../../cli/parse-args.js";
|
|
21
22
|
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
22
23
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
23
24
|
import { META_DIR, parseMetaRef, resolveMetaFilePath } from "../../core/asset/stash-meta.js";
|
|
24
25
|
import { asNonEmptyString } from "../../core/common.js";
|
|
25
|
-
import { loadConfig } from "../../core/config/config.js";
|
|
26
|
+
import { getIndexPassConfig, loadConfig } from "../../core/config/config.js";
|
|
26
27
|
import { NotFoundError, rethrowIfTestIsolationError, UsageError } from "../../core/errors.js";
|
|
27
28
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
28
|
-
import { findEntryIdByRef } from "../../indexer/db/db.js";
|
|
29
|
+
import { closeDatabase, computeBodyHash, findEntryIdByRef, openExistingDatabase } from "../../indexer/db/db.js";
|
|
30
|
+
import { hasGraphData } from "../../indexer/db/graph-db.js";
|
|
29
31
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
30
32
|
import { listRelatedPathsForFile } from "../../indexer/graph/graph-boost.js";
|
|
33
|
+
import { extractGraphForSingleFile } from "../../indexer/graph/graph-extraction.js";
|
|
31
34
|
import { lookup } from "../../indexer/indexer.js";
|
|
32
35
|
import { buildEditHint, findSourceForPath, isEditable, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
33
36
|
import { insertUsageEvent } from "../../indexer/usage/usage-events.js";
|
|
34
37
|
import { buildFileContext, buildRenderContext, getRenderer, runMatchers } from "../../indexer/walk/file-context.js";
|
|
35
38
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
39
|
+
import { resolveIndexPassLLM } from "../../llm/index-passes.js";
|
|
36
40
|
import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
|
|
41
|
+
import { resolveStorageLocations } from "../../storage/locations.js";
|
|
37
42
|
import { withIndexDb } from "../../storage/repositories/index-db.js";
|
|
38
43
|
// Eagerly import source providers to trigger self-registration.
|
|
39
44
|
import "../../sources/providers/index.js";
|
|
@@ -383,6 +388,15 @@ export async function showLocal(input) {
|
|
|
383
388
|
if (activeRun) {
|
|
384
389
|
fullResponse.activeRun = activeRun;
|
|
385
390
|
}
|
|
391
|
+
// #624-P3: opt-in inline graph extraction. Default OFF — when the flag is
|
|
392
|
+
// unset this whole block is skipped (no hasGraphData check, no LLM call), so
|
|
393
|
+
// behavior is byte-identical to today. When ON, it extracts graph data for an
|
|
394
|
+
// ungraphed asset, but ONLY when a model is configured (model-available
|
|
395
|
+
// guard) and ALWAYS bounded by a 30s timeout so `show` can never hang. Any
|
|
396
|
+
// timeout/model-unavailable/error path returns the response unchanged.
|
|
397
|
+
if (getIndexPassConfig(config.index, "graph")?.lazyGraphExtraction === true) {
|
|
398
|
+
await maybeExtractGraphInline(config, sourceStashDir, assetPath);
|
|
399
|
+
}
|
|
386
400
|
if (input.detail === "brief") {
|
|
387
401
|
return buildBriefResponse(fullResponse, assetPath);
|
|
388
402
|
}
|
|
@@ -391,6 +405,58 @@ export async function showLocal(input) {
|
|
|
391
405
|
}
|
|
392
406
|
return fullResponse;
|
|
393
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* #624-P3 — opt-in inline graph extraction for `akm show`. Best-effort and
|
|
410
|
+
* timeout-bounded: never throws, never hangs, never mutates the response.
|
|
411
|
+
*
|
|
412
|
+
* Preconditions (caller already checked the flag): a model must be configured
|
|
413
|
+
* (model-available guard via {@link resolveIndexPassLLM}) and the asset must be
|
|
414
|
+
* ungraphed ({@link hasGraphData}). Extraction races a 30s timeout so `show`
|
|
415
|
+
* cannot block on a slow provider; any timeout/error/missing-model path is
|
|
416
|
+
* swallowed and `show` returns its already-assembled response unchanged.
|
|
417
|
+
*/
|
|
418
|
+
async function maybeExtractGraphInline(config, sourceStashDir, assetPath) {
|
|
419
|
+
try {
|
|
420
|
+
// Model-available guard — no provider configured ⇒ silent skip, no LLM call.
|
|
421
|
+
if (!resolveIndexPassLLM("graph", config))
|
|
422
|
+
return;
|
|
423
|
+
let alreadyGraphed = false;
|
|
424
|
+
let bodyHash;
|
|
425
|
+
try {
|
|
426
|
+
const raw = fs.readFileSync(assetPath, "utf8");
|
|
427
|
+
bodyHash = computeBodyHash(parseFrontmatter(raw).content.trim());
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return; // file gone/unreadable ⇒ nothing to extract
|
|
431
|
+
}
|
|
432
|
+
withIndexDb((db) => {
|
|
433
|
+
alreadyGraphed = hasGraphData(db, sourceStashDir, assetPath);
|
|
434
|
+
});
|
|
435
|
+
if (alreadyGraphed)
|
|
436
|
+
return;
|
|
437
|
+
// Open the db for the async extraction ourselves: `withIndexDb` is
|
|
438
|
+
// synchronous and would close the connection the instant the async fn
|
|
439
|
+
// returns its Promise (before extraction completes). Close it explicitly
|
|
440
|
+
// after the race settles instead.
|
|
441
|
+
const db = openExistingDatabase(resolveStorageLocations().indexDb);
|
|
442
|
+
let timer;
|
|
443
|
+
const timeout = new Promise((resolve) => {
|
|
444
|
+
timer = setTimeout(resolve, 30_000);
|
|
445
|
+
});
|
|
446
|
+
try {
|
|
447
|
+
await Promise.race([extractGraphForSingleFile(db, sourceStashDir, assetPath, bodyHash, { config }), timeout]);
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
if (timer)
|
|
451
|
+
clearTimeout(timer);
|
|
452
|
+
closeDatabase(db);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
rethrowIfTestIsolationError(err);
|
|
457
|
+
// Any other failure: silently return the unchanged show response.
|
|
458
|
+
}
|
|
459
|
+
}
|
|
394
460
|
/**
|
|
395
461
|
* Minimal `show`: ref → indexer lookup → file contents. Used by callers that
|
|
396
462
|
* just need the raw file (e.g. clone, write-source) and don't want the full
|
|
@@ -458,6 +524,14 @@ function buildSummaryResponse(full, assetPath) {
|
|
|
458
524
|
}
|
|
459
525
|
// ── argv normalisation ───────────────────────────────────────────────────────
|
|
460
526
|
const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines"]);
|
|
527
|
+
const SHOW_ARGV_TOP_LEVEL_ARGS = {
|
|
528
|
+
format: { type: "string" },
|
|
529
|
+
output: { type: "string" },
|
|
530
|
+
detail: { type: "string" },
|
|
531
|
+
shape: { type: "string" },
|
|
532
|
+
quiet: { type: "boolean", alias: "q" },
|
|
533
|
+
verbose: { type: "boolean" },
|
|
534
|
+
};
|
|
461
535
|
/**
|
|
462
536
|
* Normalize argv so positional view-mode arguments after the asset ref
|
|
463
537
|
* are rewritten into internal flags that citty can parse.
|
|
@@ -471,15 +545,20 @@ const SHOW_VIEW_MODES = new Set(["toc", "frontmatter", "full", "section", "lines
|
|
|
471
545
|
* Returns a new array; the input is never modified.
|
|
472
546
|
*/
|
|
473
547
|
export function normalizeShowArgv(argv) {
|
|
474
|
-
|
|
475
|
-
|
|
548
|
+
const rawArgs = argv.slice(2);
|
|
549
|
+
const commandIndex = findCittyTopLevelCommandIndex(rawArgs, SHOW_ARGV_TOP_LEVEL_ARGS);
|
|
550
|
+
if (commandIndex < 0 || rawArgs[commandIndex] !== "show")
|
|
476
551
|
return argv;
|
|
477
|
-
|
|
552
|
+
const commandArgs = rawArgs.slice(commandIndex + 1);
|
|
553
|
+
if (commandArgs.includes("--view") ||
|
|
554
|
+
commandArgs.includes("--heading") ||
|
|
555
|
+
commandArgs.includes("--start") ||
|
|
556
|
+
commandArgs.includes("--end")) {
|
|
478
557
|
throw new UsageError('Legacy show flags are no longer supported. Use positional syntax like `akm show knowledge:guide toc` or `akm show knowledge:guide section "Auth"`.');
|
|
479
558
|
}
|
|
480
559
|
// Separate global flags from positional/show-specific args
|
|
481
|
-
const prefix = argv.slice(0,
|
|
482
|
-
const rest =
|
|
560
|
+
const prefix = [...argv.slice(0, 2), ...rawArgs.slice(0, commandIndex + 1)];
|
|
561
|
+
const rest = commandArgs;
|
|
483
562
|
const globalFlags = [];
|
|
484
563
|
const showArgs = [];
|
|
485
564
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -37,7 +37,7 @@ import { copyStashSkeleton, scaffoldStashMeta } from "./stash-skeleton.js";
|
|
|
37
37
|
function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
38
38
|
if (!dirExplicitlyProvided)
|
|
39
39
|
return; // Only guard explicit --dir, not default HOME resolution.
|
|
40
|
-
const isUnderTest =
|
|
40
|
+
const isUnderTest = isUnderTestRunner();
|
|
41
41
|
if (!isUnderTest)
|
|
42
42
|
return;
|
|
43
43
|
if (process.env.AKM_FORCE_INIT_TMP_STASH === "1")
|
|
@@ -52,7 +52,12 @@ function assertInitSandbox(stashDir, dirExplicitlyProvided) {
|
|
|
52
52
|
return;
|
|
53
53
|
throw new ConfigError(`refusing to persist --dir stashDir to a temporary path while under test runner; set AKM_FORCE_INIT_TMP_STASH=1 if you really mean it (stashDir=${stashDir})`, "INIT_TMP_STASH_REFUSED");
|
|
54
54
|
}
|
|
55
|
+
function isUnderTestRunner() {
|
|
56
|
+
return process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
57
|
+
}
|
|
55
58
|
export async function akmInit(options) {
|
|
59
|
+
const dirExplicitlyProvided = options?.dir != null;
|
|
60
|
+
const setDefault = options?.setDefault === true;
|
|
56
61
|
const stashDir = options?.dir ? path.resolve(options.dir) : getDefaultStashDir();
|
|
57
62
|
// Safety check (#473): refuse stashDir at /, $HOME, /etc, ~/.config, etc.
|
|
58
63
|
// Runs BEFORE any disk write — a fat-fingered `akm init --dir /` or
|
|
@@ -61,7 +66,7 @@ export async function akmInit(options) {
|
|
|
61
66
|
assertSafeStashDir(stashDir);
|
|
62
67
|
// Defense-in-depth: refuse to persist an explicit --dir /tmp/... stashDir
|
|
63
68
|
// to config under a test runner. Default HOME-resolved paths are exempt.
|
|
64
|
-
assertInitSandbox(stashDir,
|
|
69
|
+
assertInitSandbox(stashDir, dirExplicitlyProvided);
|
|
65
70
|
let created = false;
|
|
66
71
|
if (!fs.existsSync(stashDir)) {
|
|
67
72
|
fs.mkdirSync(stashDir, { recursive: true });
|
|
@@ -75,27 +80,54 @@ export async function akmInit(options) {
|
|
|
75
80
|
}
|
|
76
81
|
// Ensure the default stash is a local git repo (no remote required)
|
|
77
82
|
ensureGitRepo(stashDir);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
+
// Run seeding UNCONDITIONALLY (not just when the stash was newly created) so
|
|
84
|
+
// re-running `akm init` on an existing stash backfills any missing skeleton
|
|
85
|
+
// files — the README, the per-type SOFT convention templates under
|
|
86
|
+
// facts/conventions/assets/, and the `.meta/index.md` orientation doc. Both
|
|
87
|
+
// helpers are absent-only: they never overwrite a file a user has edited.
|
|
88
|
+
copyStashSkeleton(stashDir);
|
|
89
|
+
scaffoldStashMeta(stashDir);
|
|
90
|
+
// Persist stashDir in config.json — but ONLY when the user is actually
|
|
91
|
+
// setting up / opting into a default. A bare `akm init --dir <secondary>`
|
|
92
|
+
// must NOT silently repoint the user's real default stash (the footgun
|
|
93
|
+
// documented in memory:akm-init-persists-stashdir-warning).
|
|
94
|
+
//
|
|
95
|
+
// Decision matrix — persist when ANY of:
|
|
96
|
+
// (a) no --dir provided → default HOME-resolved setup flow
|
|
97
|
+
// (b) --dir AND no existing stashDir in config → first-time bootstrap
|
|
98
|
+
// (c) --dir AND --set-default → explicit opt-in
|
|
99
|
+
// Otherwise (--dir + existing default + no --set-default) leave the default
|
|
100
|
+
// pointer alone; the target dir is still scaffolded above.
|
|
83
101
|
const configPath = getConfigPath();
|
|
84
102
|
const existing = loadUserConfig();
|
|
85
|
-
|
|
86
|
-
|
|
103
|
+
const existingStashDir = existing.stashDir;
|
|
104
|
+
const shouldPersist = !dirExplicitlyProvided || !existingStashDir || setDefault;
|
|
105
|
+
let defaultStashUpdated = false;
|
|
106
|
+
let previousStashDir;
|
|
107
|
+
if (shouldPersist) {
|
|
108
|
+
if (!existingStashDir || existingStashDir !== stashDir) {
|
|
109
|
+
saveConfig({ ...existing, stashDir });
|
|
110
|
+
defaultStashUpdated = true;
|
|
111
|
+
}
|
|
112
|
+
// else: already pointed here — no-op, no spurious rewrite.
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Default left untouched; surface it so the CLI can inform the user.
|
|
116
|
+
previousStashDir = existingStashDir;
|
|
87
117
|
}
|
|
88
118
|
// Ensure ripgrep is available (install to cache/bin if needed)
|
|
89
119
|
let ripgrep;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
120
|
+
if (!isUnderTestRunner()) {
|
|
121
|
+
try {
|
|
122
|
+
const binDir = getBinDir();
|
|
123
|
+
const rgResult = ensureRg(binDir);
|
|
124
|
+
ripgrep = rgResult;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Non-fatal: ripgrep is optional, search works without it
|
|
128
|
+
}
|
|
97
129
|
}
|
|
98
|
-
return { stashDir, created, configPath, ripgrep };
|
|
130
|
+
return { stashDir, created, configPath, defaultStashUpdated, previousStashDir, ripgrep };
|
|
99
131
|
}
|
|
100
132
|
/** Initialise `dir` as a git repository if it is not already one. */
|
|
101
133
|
function ensureGitRepo(dir) {
|
|
@@ -17,7 +17,7 @@ import { removeLockEntry, upsertLockEntry } from "../../integrations/lockfile.js
|
|
|
17
17
|
import { parseRegistryRef } from "../../registry/resolve.js";
|
|
18
18
|
import { parseGitRepoUrl, syncMirroredRepo } from "../../sources/providers/git.js";
|
|
19
19
|
import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
|
|
20
|
-
import { ensureWebsiteMirror } from "../../sources/website-ingest.js";
|
|
20
|
+
import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests } from "../../sources/website-ingest.js";
|
|
21
21
|
import { listWikis, resolveWikisRoot } from "../../wiki/wiki.js";
|
|
22
22
|
import { removeInstalledRegistryEntry, upsertInstalledRegistryEntry } from "./source-add.js";
|
|
23
23
|
import { removeStash } from "./source-manage.js";
|
|
@@ -194,7 +194,11 @@ async function updateGitSource(stashDir, target, all, gitSource) {
|
|
|
194
194
|
/** Re-crawl a website source and return an UpdateResponse. */
|
|
195
195
|
async function updateWebsiteSource(stashDir, target, all, websiteSource) {
|
|
196
196
|
// TODO: full incremental re-crawl with delta tracking (#19)
|
|
197
|
-
await ensureWebsiteMirror(websiteSource, {
|
|
197
|
+
await ensureWebsiteMirror(websiteSource, {
|
|
198
|
+
requireStashDir: true,
|
|
199
|
+
force: true,
|
|
200
|
+
...(shouldAllowPrivateWebsiteUrlForTests(websiteSource.url ?? "") ? { allowPrivateHosts: true } : {}),
|
|
201
|
+
});
|
|
198
202
|
return buildUpdateResponse(stashDir, target, all, []);
|
|
199
203
|
}
|
|
200
204
|
/** Sync a single installed registry entry and return the processed record. */
|
|
@@ -205,7 +209,11 @@ async function updateRegistryEntry(entry, force) {
|
|
|
205
209
|
const synced = await syncFromRef(entry.ref, { force });
|
|
206
210
|
const installedEntry = {
|
|
207
211
|
id: synced.id,
|
|
208
|
-
source
|
|
212
|
+
// Preserve the original source classification. syncFromRef() re-derives the
|
|
213
|
+
// source type from the ref scheme (e.g. "github:" → source: "github"), but
|
|
214
|
+
// an update should not reclassify an existing entry. A writable entry stored
|
|
215
|
+
// as source: "git" would fail config validation if rewritten to "github".
|
|
216
|
+
source: entry.source,
|
|
209
217
|
ref: synced.ref,
|
|
210
218
|
artifactUrl: synced.artifactUrl,
|
|
211
219
|
resolvedVersion: synced.resolvedVersion,
|
|
@@ -17,8 +17,10 @@ import path from "node:path";
|
|
|
17
17
|
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
18
18
|
import { assembleAsset } from "../../core/asset/asset-serialize.js";
|
|
19
19
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
20
|
+
import { authoringRulesForType } from "../../core/authoring-rules.js";
|
|
20
21
|
import { appendEvent, readEvents } from "../../core/events.js";
|
|
21
|
-
import {
|
|
22
|
+
import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
|
|
23
|
+
import { info } from "../../core/warn.js";
|
|
22
24
|
import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
|
|
23
25
|
import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
|
|
24
26
|
import { createProposal, isProposalSkipped } from "../proposal/validators/proposals.js";
|
|
@@ -44,6 +46,9 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
44
46
|
const repairs = [];
|
|
45
47
|
const repairedRefs = new Set();
|
|
46
48
|
const { startMs, budgetMs, llmConfig, stashDir, findFilePath = defaultFindFilePath, isLessonCandidateFn = defaultIsLessonCandidate, chatFn = chatCompletion, } = options;
|
|
49
|
+
if (!stashDir) {
|
|
50
|
+
throw new Error("runSchemaRepairPass requires stashDir so repairs route through the proposal queue");
|
|
51
|
+
}
|
|
47
52
|
for (const failure of failures) {
|
|
48
53
|
if (Date.now() - startMs >= budgetMs)
|
|
49
54
|
break;
|
|
@@ -94,6 +99,16 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
94
99
|
const fieldList = missingFields.join(" and ");
|
|
95
100
|
info(`[improve] schema-repair ${failure.ref} (${fieldList})`);
|
|
96
101
|
const bodyPreview = (fm.content ?? raw).slice(0, 2000);
|
|
102
|
+
// Standards "rulebook" for this target — wiki schema (wiki page) or stash
|
|
103
|
+
// convention/meta facts (non-wiki asset). `resolveStandardsContext`
|
|
104
|
+
// dispatches on the ref.
|
|
105
|
+
const standardsContext = resolveStandardsContext(failure.ref, stashDir);
|
|
106
|
+
const standardsSection = standardsContext.trim()
|
|
107
|
+
? `\n\nStandards to follow (the rulebook for this target):\n${standardsContext.trim()}`
|
|
108
|
+
: "";
|
|
109
|
+
const assetType = parseAssetRef(failure.ref).type;
|
|
110
|
+
const authoringRules = authoringRulesForType(assetType);
|
|
111
|
+
const authoringRulesSection = authoringRules ? `\n\n${authoringRules}` : "";
|
|
97
112
|
const llmResponse = await chatFn(llmConfig, [
|
|
98
113
|
{
|
|
99
114
|
role: "system",
|
|
@@ -101,7 +116,7 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
101
116
|
},
|
|
102
117
|
{
|
|
103
118
|
role: "user",
|
|
104
|
-
content: `Generate the missing frontmatter fields (${fieldList}) for this ${
|
|
119
|
+
content: `Generate the missing frontmatter fields (${fieldList}) for this ${assetType} asset. Return ONLY valid JSON like {"description": "...", "when_to_use": "..."}${standardsSection}${authoringRulesSection}\n\n${bodyPreview}`,
|
|
105
120
|
},
|
|
106
121
|
]);
|
|
107
122
|
const parsed = parseEmbeddedJsonResponse(llmResponse.trim());
|
|
@@ -127,50 +142,33 @@ export async function runSchemaRepairPass(failures, options) {
|
|
|
127
142
|
// them human-reviewable before they affect search ranking and curate hints.
|
|
128
143
|
// mem0 open gaps (arXiv:2504.19413) — any LLM write to a memory field
|
|
129
144
|
// should be human-reviewable.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
info(`[improve] schema-repair queued: ${failure.ref} (proposal id: ${proposalResult.id})`);
|
|
145
|
-
appendEvent({
|
|
146
|
-
eventType: "schema_repair_invoked",
|
|
147
|
-
ref: failure.ref,
|
|
148
|
-
metadata: { outcome: "queued", reason: failure.reason, proposalId: proposalResult.id },
|
|
149
|
-
});
|
|
150
|
-
repairs.push({
|
|
151
|
-
ref: failure.ref,
|
|
152
|
-
reason: failure.reason,
|
|
153
|
-
outcome: "queued",
|
|
154
|
-
proposalId: proposalResult.id,
|
|
155
|
-
});
|
|
156
|
-
// Mark as repaired so the caller removes it from the validation-failure set.
|
|
157
|
-
repairedRefs.add(failure.ref);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
// Fallback: no stash dir available — write directly (legacy path).
|
|
161
|
-
// This should not occur in production; stashDir is always provided by
|
|
162
|
-
// `runSchemaRepairPass` callers in improve.ts.
|
|
163
|
-
warn(`[improve] schema-repair: no stashDir available for ${failure.ref}, falling back to direct write`);
|
|
164
|
-
fs.writeFileSync(filePath, newContent, "utf8");
|
|
165
|
-
info(`[improve] schema-repair written: ${failure.ref}`);
|
|
166
|
-
appendEvent({
|
|
167
|
-
eventType: "schema_repair_invoked",
|
|
168
|
-
ref: failure.ref,
|
|
169
|
-
metadata: { outcome: "written", reason: failure.reason },
|
|
170
|
-
});
|
|
171
|
-
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "written" });
|
|
172
|
-
repairedRefs.add(failure.ref);
|
|
145
|
+
const proposalResult = createProposal(stashDir, {
|
|
146
|
+
ref: failure.ref,
|
|
147
|
+
source: "schema-repair",
|
|
148
|
+
payload: {
|
|
149
|
+
content: newContent,
|
|
150
|
+
...(Object.keys(newFm).length > 0 ? { frontmatter: newFm } : {}),
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
if (isProposalSkipped(proposalResult)) {
|
|
154
|
+
info(`[improve] schema-repair proposal skipped for ${failure.ref}: ${proposalResult.message}`);
|
|
155
|
+
repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
|
|
156
|
+
continue;
|
|
173
157
|
}
|
|
158
|
+
info(`[improve] schema-repair queued: ${failure.ref} (proposal id: ${proposalResult.id})`);
|
|
159
|
+
appendEvent({
|
|
160
|
+
eventType: "schema_repair_invoked",
|
|
161
|
+
ref: failure.ref,
|
|
162
|
+
metadata: { outcome: "queued", reason: failure.reason, proposalId: proposalResult.id },
|
|
163
|
+
});
|
|
164
|
+
repairs.push({
|
|
165
|
+
ref: failure.ref,
|
|
166
|
+
reason: failure.reason,
|
|
167
|
+
outcome: "queued",
|
|
168
|
+
proposalId: proposalResult.id,
|
|
169
|
+
});
|
|
170
|
+
// Mark as repaired so the caller removes it from the validation-failure set.
|
|
171
|
+
repairedRefs.add(failure.ref);
|
|
174
172
|
}
|
|
175
173
|
catch (e) {
|
|
176
174
|
appendEvent({
|
|
@@ -306,8 +306,8 @@ export async function performUpgrade(check, opts) {
|
|
|
306
306
|
* The new binary's `akm index` does the work for us:
|
|
307
307
|
* 1. loadConfig() runs at startup — auto-migrates legacy `stashes` →
|
|
308
308
|
* `sources` if the on-disk config still uses the old key.
|
|
309
|
-
* 2. ensureSchema()
|
|
310
|
-
*
|
|
309
|
+
* 2. ensureSchema() converges index.db forward via its idempotent baseline
|
|
310
|
+
* schema + additive migrations (no destructive rebuild).
|
|
311
311
|
* 3. The full reindex repopulates entries + workflow_documents + FTS.
|
|
312
312
|
*/
|
|
313
313
|
function runPostUpgradeTasks(akmBin, opts) {
|
|
@@ -11,7 +11,7 @@ import { upsertLockEntry } from "../../integrations/lockfile.js";
|
|
|
11
11
|
import { parseRegistryRef } from "../../registry/resolve.js";
|
|
12
12
|
import { detectStashRoot } from "../../sources/providers/provider-utils.js";
|
|
13
13
|
import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
|
|
14
|
-
import { ensureWebsiteMirror, validateWebsiteInputUrl } from "../../sources/website-ingest.js";
|
|
14
|
+
import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests, validateWebsiteInputUrl, } from "../../sources/website-ingest.js";
|
|
15
15
|
import { ensureWikiNameAvailable, validateWikiName } from "../../wiki/wiki.js";
|
|
16
16
|
const VALID_OVERRIDE_TYPES = new Set(["wiki"]);
|
|
17
17
|
export async function akmAdd(input) {
|
|
@@ -126,7 +126,8 @@ async function addLocalSource(ref, sourcePath, stashDir, wikiName, explicitName)
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
129
|
-
const
|
|
129
|
+
const allowPrivateHosts = shouldAllowPrivateWebsiteUrlForTests(ref);
|
|
130
|
+
const normalizedUrl = validateWebsiteInputUrl(ref, { allowPrivateHosts });
|
|
130
131
|
const config = loadUserConfig();
|
|
131
132
|
const sources = [...getSources(config)];
|
|
132
133
|
let entry = sources.find((stash) => stash.type === "website" && stash.url === normalizedUrl);
|
|
@@ -154,7 +155,10 @@ async function addWebsiteSource(ref, stashDir, name, options, wikiName) {
|
|
|
154
155
|
if (changed)
|
|
155
156
|
saveConfig({ ...config, sources });
|
|
156
157
|
}
|
|
157
|
-
const cachePaths = await ensureWebsiteMirror(entry, {
|
|
158
|
+
const cachePaths = await ensureWebsiteMirror(entry, {
|
|
159
|
+
requireStashDir: true,
|
|
160
|
+
...(allowPrivateHosts ? { allowPrivateHosts: true } : {}),
|
|
161
|
+
});
|
|
158
162
|
const index = await akmIndex({ stashDir });
|
|
159
163
|
const updatedConfig = loadConfig();
|
|
160
164
|
return {
|