akm-cli 0.9.0-beta.6 → 0.9.0-rc.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/CHANGELOG.md +663 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- 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/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- 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 +11 -3
- 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 +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -14
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-cli.js +220 -227
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +153 -147
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +76 -72
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1083
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -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 +1313 -1278
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +243 -599
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +55 -4
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +160 -7
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +32 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +510 -2537
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +115 -0
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +212 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -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/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +7 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -882
- package/dist/commands/read/curate.js +419 -103
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +29 -41
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +15 -1
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +21 -4
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +389 -58
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +21 -18
- 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 +770 -0
- package/dist/core/state-db.js +142 -1091
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +356 -780
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +103 -24
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +106 -0
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +291 -310
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +9 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +39 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +7 -1
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +59 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +69 -67
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -1
- package/dist/integrations/harnesses/claude/index.js +2 -0
- package/dist/integrations/harnesses/claude/session-log.js +11 -1
- package/dist/integrations/harnesses/index.js +2 -3
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +2 -0
- 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 +98 -17
- package/dist/integrations/harnesses/types.js +1 -0
- package/dist/integrations/session-logs/index.js +16 -0
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +57 -15
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- 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 +85 -14
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/text/helpers.js +29 -1
- package/dist/output/text/workflow.js +1 -0
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/scripts/migrate-storage.js +2718 -2354
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +891 -597
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- 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/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- 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 +15 -13
- package/dist/text-import-hook.mjs +0 -0
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/cli.js +1 -0
- package/dist/workflows/db.js +3 -4
- package/dist/workflows/runtime/runs.js +43 -118
- package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +5 -4
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { UsageError } from "../../core/errors.js";
|
|
9
|
+
import { getRegistryCacheDir } from "../../core/paths.js";
|
|
10
|
+
import { parseRegistryRef, resolveRegistryArtifact, validateGitRef, validateGitUrl } from "../../registry/resolve.js";
|
|
11
|
+
import { applyAkmIncludeConfig, buildInstallCacheDir, copyDirectoryContents, detectStashRoot, isDirectory, } from "./provider-utils.js";
|
|
12
|
+
/**
|
|
13
|
+
* Shared subprocess wrapper for `git` invocations. Disables git's interactive
|
|
14
|
+
* terminal prompt so a missing credential never hangs the process.
|
|
15
|
+
*/
|
|
16
|
+
export function runGit(args, options) {
|
|
17
|
+
return spawnSync("git", args, {
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
...options,
|
|
20
|
+
env: { ...process.env, ...options?.env, GIT_TERMINAL_PROMPT: "0" },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Sync mode for a one-shot install ref (`akm add github:owner/repo` or
|
|
25
|
+
* `akm add git:url`). Runs the clone → strip → include-filter pipeline that
|
|
26
|
+
* historically lived in `installRegistryRef()`.
|
|
27
|
+
*/
|
|
28
|
+
export async function syncRegistryGitRef(ref, options) {
|
|
29
|
+
const parsed = parseRegistryRef(ref);
|
|
30
|
+
if (parsed.source === "github") {
|
|
31
|
+
const githubRef = {
|
|
32
|
+
source: "git",
|
|
33
|
+
ref: parsed.ref,
|
|
34
|
+
id: parsed.id,
|
|
35
|
+
url: `https://github.com/${parsed.owner}/${parsed.repo}.git`,
|
|
36
|
+
requestedRef: parsed.requestedRef,
|
|
37
|
+
};
|
|
38
|
+
const result = await doSyncGit(githubRef, options);
|
|
39
|
+
return { ...result, source: "github" };
|
|
40
|
+
}
|
|
41
|
+
if (parsed.source !== "git") {
|
|
42
|
+
throw new UsageError(`syncRegistryGitRef requires a git: or github: ref, got "${ref}"`);
|
|
43
|
+
}
|
|
44
|
+
return doSyncGit(parsed, options);
|
|
45
|
+
}
|
|
46
|
+
async function doSyncGit(parsed, options) {
|
|
47
|
+
const resolved = await resolveRegistryArtifact(parsed);
|
|
48
|
+
const syncedAt = (options?.now ?? new Date()).toISOString();
|
|
49
|
+
const cacheRootDir = options?.cacheRootDir ?? getRegistryCacheDir();
|
|
50
|
+
const cacheDir = buildInstallCacheDir(cacheRootDir, parsed.source, parsed.id, resolved.resolvedRevision);
|
|
51
|
+
const cloneDir = path.join(cacheDir, "clone");
|
|
52
|
+
const extractedDir = path.join(cacheDir, "extracted");
|
|
53
|
+
// Cache hit
|
|
54
|
+
if (!options?.force && isDirectory(extractedDir)) {
|
|
55
|
+
try {
|
|
56
|
+
const provisionalKitRoot = detectStashRoot(extractedDir);
|
|
57
|
+
const installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
58
|
+
const stashRoot = detectStashRoot(installRoot);
|
|
59
|
+
if (stashRoot) {
|
|
60
|
+
return {
|
|
61
|
+
id: resolved.id,
|
|
62
|
+
source: resolved.source,
|
|
63
|
+
ref: resolved.ref,
|
|
64
|
+
artifactUrl: resolved.artifactUrl,
|
|
65
|
+
resolvedVersion: resolved.resolvedVersion,
|
|
66
|
+
resolvedRevision: resolved.resolvedRevision,
|
|
67
|
+
contentDir: stashRoot,
|
|
68
|
+
cacheDir,
|
|
69
|
+
extractedDir,
|
|
70
|
+
writable: options?.writable,
|
|
71
|
+
syncedAt,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Cache invalid, re-clone
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
80
|
+
// Validate URL and ref before passing to git to prevent command injection
|
|
81
|
+
validateGitUrl(parsed.url);
|
|
82
|
+
if (parsed.requestedRef)
|
|
83
|
+
validateGitRef(parsed.requestedRef);
|
|
84
|
+
let provisionalKitRoot;
|
|
85
|
+
let installRoot;
|
|
86
|
+
let stashRoot;
|
|
87
|
+
try {
|
|
88
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
89
|
+
if (parsed.requestedRef) {
|
|
90
|
+
cloneArgs.push("--branch", parsed.requestedRef);
|
|
91
|
+
}
|
|
92
|
+
cloneArgs.push(parsed.url, cloneDir);
|
|
93
|
+
const cloneResult = runGit(cloneArgs, { timeout: 120_000 });
|
|
94
|
+
if (cloneResult.status !== 0) {
|
|
95
|
+
throw new Error(classifyCloneFailure(parsed.url, cloneResult.stderr, cloneResult.error));
|
|
96
|
+
}
|
|
97
|
+
// Copy contents to extracted dir without .git
|
|
98
|
+
fs.mkdirSync(extractedDir, { recursive: true });
|
|
99
|
+
copyDirectoryContents(cloneDir, extractedDir);
|
|
100
|
+
// Clean up the clone dir
|
|
101
|
+
fs.rmSync(cloneDir, { recursive: true, force: true });
|
|
102
|
+
provisionalKitRoot = detectStashRoot(extractedDir);
|
|
103
|
+
installRoot = applyAkmIncludeConfig(provisionalKitRoot, cacheDir, extractedDir) ?? provisionalKitRoot;
|
|
104
|
+
stashRoot = detectStashRoot(installRoot);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
// Clean up the cache directory so stale or partially-cloned artifacts
|
|
108
|
+
// don't cause false cache hits on the next install attempt.
|
|
109
|
+
try {
|
|
110
|
+
fs.rmSync(cacheDir, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* best-effort */
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
id: resolved.id,
|
|
119
|
+
source: resolved.source,
|
|
120
|
+
ref: resolved.ref,
|
|
121
|
+
artifactUrl: resolved.artifactUrl,
|
|
122
|
+
resolvedVersion: resolved.resolvedVersion,
|
|
123
|
+
resolvedRevision: resolved.resolvedRevision,
|
|
124
|
+
contentDir: stashRoot,
|
|
125
|
+
cacheDir,
|
|
126
|
+
extractedDir,
|
|
127
|
+
writable: options?.writable,
|
|
128
|
+
syncedAt,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function cloneRepo(cloneUrl, ref, destDir, writable = false) {
|
|
132
|
+
// Stage the clone into a sibling temp dir so that a failed clone never
|
|
133
|
+
// destroys a previously-valid destDir (e.g. when the remote is temporarily
|
|
134
|
+
// unreachable and we have a valid cached copy).
|
|
135
|
+
const tmpDir = `${destDir}.tmp-${randomBytes(4).toString("hex")}`;
|
|
136
|
+
const args = ["clone", "--depth", "1"];
|
|
137
|
+
if (ref)
|
|
138
|
+
args.push("--branch", ref);
|
|
139
|
+
args.push(cloneUrl, tmpDir);
|
|
140
|
+
const result = runGit(args, { timeout: 120_000 });
|
|
141
|
+
if (result.status !== 0) {
|
|
142
|
+
// Clean up the (possibly partial) temp dir but leave destDir untouched.
|
|
143
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
144
|
+
throw new Error(classifyCloneFailure(cloneUrl, result.stderr, result.error));
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
if (!writable) {
|
|
148
|
+
// Remove .git directory — we only need the working tree for read-only stashes
|
|
149
|
+
const gitDir = path.join(tmpDir, ".git");
|
|
150
|
+
if (fs.existsSync(gitDir))
|
|
151
|
+
fs.rmSync(gitDir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
// Swap: remove the old destDir (if any) then atomically rename tmpDir into place.
|
|
154
|
+
if (fs.existsSync(destDir))
|
|
155
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
156
|
+
fs.renameSync(tmpDir, destDir);
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
// Post-clone steps failed — clean up the temp dir to avoid orphaned dirs.
|
|
160
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ── Clone-failure classification (#487) ─────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* Translate git's stderr into an actionable message. Without this, a user
|
|
167
|
+
* who passes a nonexistent or private repo to `akm add` sees:
|
|
168
|
+
*
|
|
169
|
+
* "could not read Username for 'https://github.com': No such device or
|
|
170
|
+
* address"
|
|
171
|
+
*
|
|
172
|
+
* That is git falling through to its auth-prompt path — the actual cause
|
|
173
|
+
* is "repo doesn't exist (or is private)". We classify the common patterns
|
|
174
|
+
* and emit a message that names the cause and the fix.
|
|
175
|
+
*/
|
|
176
|
+
export function classifyCloneFailure(url, stderr, spawnError) {
|
|
177
|
+
const safeUrl = redactUrlUserinfo(url);
|
|
178
|
+
const raw = (stderr ?? "").trim();
|
|
179
|
+
const spawnMsg = spawnError?.message ?? "";
|
|
180
|
+
// `git` binary not on PATH.
|
|
181
|
+
if (spawnError?.code === "ENOENT") {
|
|
182
|
+
return `Failed to clone ${safeUrl}: 'git' is not installed or not on PATH. Install git, then re-run.`;
|
|
183
|
+
}
|
|
184
|
+
// Auth-prompt fall-through (the headline #487 case).
|
|
185
|
+
if (/could not read Username|terminal prompts disabled|Authentication failed|fatal: Authentication/i.test(raw)) {
|
|
186
|
+
return (`Failed to clone ${safeUrl}: repository not found or private. ` +
|
|
187
|
+
`If the repository is public, double-check the URL and try again. ` +
|
|
188
|
+
`If it is private, set GH_TOKEN (or configure a git credential helper) before re-running.`);
|
|
189
|
+
}
|
|
190
|
+
// 404-style messages from git http.
|
|
191
|
+
if (/repository '.*' not found|HTTP 404|fatal: remote error|not found:|Not Found/i.test(raw)) {
|
|
192
|
+
return (`Failed to clone ${safeUrl}: repository not found. ` +
|
|
193
|
+
`Check the URL — for GitHub, the form is 'owner/repo' or 'github:owner/repo'.`);
|
|
194
|
+
}
|
|
195
|
+
// SSH connection issues.
|
|
196
|
+
if (/Permission denied \(publickey\)|kex_exchange_identification|Connection refused|Connection timed out/i.test(raw)) {
|
|
197
|
+
return (`Failed to clone ${safeUrl}: network or SSH failure. ` +
|
|
198
|
+
`Check connectivity, your SSH agent, and the remote host's availability.`);
|
|
199
|
+
}
|
|
200
|
+
// Branch / ref-specific failures.
|
|
201
|
+
if (/Remote branch .* not found in upstream origin|couldn't find remote ref/i.test(raw)) {
|
|
202
|
+
return (`Failed to clone ${safeUrl}: the requested branch/tag does not exist on the remote. ` +
|
|
203
|
+
`Verify the ref name and re-run.`);
|
|
204
|
+
}
|
|
205
|
+
const detail = raw || spawnMsg || "unknown error";
|
|
206
|
+
return `Failed to clone ${safeUrl}: ${redactUrlUserinfo(detail)}`;
|
|
207
|
+
}
|
|
208
|
+
function redactUrlUserinfo(text) {
|
|
209
|
+
return text.replace(/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^\s/@]+)@/g, "$1[REDACTED]@");
|
|
210
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
|
|
8
|
+
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
9
|
+
import { getRegistryIndexCacheDir } from "../../core/paths.js";
|
|
10
|
+
import { validateGitUrl } from "../../registry/resolve.js";
|
|
11
|
+
import { registerSourceProvider } from "../provider-factory.js";
|
|
12
|
+
import { cloneRepo, runGit, syncRegistryGitRef } from "./git-install.js";
|
|
13
|
+
import { isExpired, sanitizeString } from "./provider-utils.js";
|
|
14
|
+
/** Cache TTL before refreshing the mirrored repo (12 hours). */
|
|
15
|
+
const CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
16
|
+
/** Maximum stale age allowed when refresh fails (7 days). */
|
|
17
|
+
const CACHE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
18
|
+
/**
|
|
19
|
+
* Git source provider — clones (and re-pulls) a remote repo into a local
|
|
20
|
+
* cache directory. Implements the v1 {@link SourceProvider} interface (spec
|
|
21
|
+
* §2.1, §2.5): `{ name, kind, init, path, sync }`.
|
|
22
|
+
*
|
|
23
|
+
* Reading is the indexer's job — this class doesn't implement `search` or
|
|
24
|
+
* `show`. The install-time helpers `syncRegistryGitRef` / `syncMirroredRepo`
|
|
25
|
+
* live below as standalone functions used by `akm add` / `akm update`.
|
|
26
|
+
*/
|
|
27
|
+
export class GitSourceProvider {
|
|
28
|
+
kind = "git";
|
|
29
|
+
name;
|
|
30
|
+
#config;
|
|
31
|
+
#path = null;
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.#config = config;
|
|
34
|
+
this.name = config.name ?? "git";
|
|
35
|
+
}
|
|
36
|
+
path() {
|
|
37
|
+
if (this.#path == null) {
|
|
38
|
+
// Lazy resolution: providers are sometimes constructed without an
|
|
39
|
+
// explicit init() call (e.g. by legacy callers that just want the
|
|
40
|
+
// path). Resolve on demand and cache.
|
|
41
|
+
this.#path = resolveGitContentDir(this.#config);
|
|
42
|
+
}
|
|
43
|
+
return this.#path;
|
|
44
|
+
}
|
|
45
|
+
async sync(options) {
|
|
46
|
+
// Two execution modes:
|
|
47
|
+
// 1. Long-lived configured source (config.url) — mirror into the
|
|
48
|
+
// registry-index cache and serve as a read-only working tree.
|
|
49
|
+
// 2. One-shot install ref (options.ref like "git:..." / "github:...") —
|
|
50
|
+
// delegate to the install-time pipeline.
|
|
51
|
+
if (typeof this.#config.options?.ref === "string" && this.#config.options.ref) {
|
|
52
|
+
await syncRegistryGitRef(String(this.#config.options.ref), { force: options?.force });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await syncMirroredRepo(this.#config, { force: options?.force });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Resolve the on-disk content directory for a configured git source. */
|
|
59
|
+
function resolveGitContentDir(config) {
|
|
60
|
+
if (config.path)
|
|
61
|
+
return config.path;
|
|
62
|
+
if (config.url) {
|
|
63
|
+
const repo = parseGitRepoUrl(config.url);
|
|
64
|
+
return getCachePaths(repo.canonicalUrl).repoDir;
|
|
65
|
+
}
|
|
66
|
+
throw new ConfigError("git source entry must have either `path` or `url`");
|
|
67
|
+
}
|
|
68
|
+
// ── Self-register ───────────────────────────────────────────────────────────
|
|
69
|
+
registerSourceProvider("git", (config) => new GitSourceProvider(config));
|
|
70
|
+
// ── Cache management ────────────────────────────────────────────────────────
|
|
71
|
+
export function getCachePaths(repoUrl) {
|
|
72
|
+
const key = createHash("sha256").update(repoUrl).digest("hex").slice(0, 16);
|
|
73
|
+
const cacheRoot = getRegistryIndexCacheDir();
|
|
74
|
+
const rootDir = path.join(cacheRoot, `git-${key}`);
|
|
75
|
+
return {
|
|
76
|
+
rootDir,
|
|
77
|
+
repoDir: path.join(rootDir, "repo"),
|
|
78
|
+
indexPath: path.join(rootDir, "index.json"),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export async function ensureGitMirror(repo, cachePaths, options) {
|
|
82
|
+
const requireRepoDir = options?.requireRepoDir === true;
|
|
83
|
+
const writable = options?.writable === true;
|
|
84
|
+
const force = options?.force === true;
|
|
85
|
+
// Check if cache is fresh
|
|
86
|
+
let mtime = 0;
|
|
87
|
+
try {
|
|
88
|
+
mtime = fs.statSync(cachePaths.indexPath).mtimeMs;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* no cached index */
|
|
92
|
+
}
|
|
93
|
+
if (!force && mtime && !isExpired(mtime, CACHE_TTL_MS) && (!requireRepoDir || hasExtractedRepo(cachePaths.repoDir))) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
fs.mkdirSync(cachePaths.rootDir, { recursive: true });
|
|
98
|
+
if (writable && fs.existsSync(path.join(cachePaths.repoDir, ".git"))) {
|
|
99
|
+
// Writable repo already cloned — pull instead of re-clone to preserve local changes
|
|
100
|
+
pullRepo(cachePaths.repoDir);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
cloneRepo(repo.cloneUrl, repo.ref, cachePaths.repoDir, writable);
|
|
104
|
+
}
|
|
105
|
+
// Touch index file to track freshness
|
|
106
|
+
fs.writeFileSync(cachePaths.indexPath, "[]", { encoding: "utf8", mode: 0o600 });
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
if (mtime && !isExpired(mtime, CACHE_STALE_MS) && (!requireRepoDir || hasExtractedRepo(cachePaths.repoDir))) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Sync mode for a long-lived configured git stash. Mirrors the repo into the
|
|
117
|
+
* shared registry-index cache (12h TTL) and exposes the working tree as the
|
|
118
|
+
* stash content directory.
|
|
119
|
+
*/
|
|
120
|
+
export async function syncMirroredRepo(config, options) {
|
|
121
|
+
if (!config.url) {
|
|
122
|
+
throw new ConfigError("git stash entry requires a URL when no install ref is supplied");
|
|
123
|
+
}
|
|
124
|
+
const repo = parseGitRepoUrl(config.url);
|
|
125
|
+
const cachePaths = getCachePaths(repo.canonicalUrl);
|
|
126
|
+
await ensureGitMirror(repo, cachePaths, {
|
|
127
|
+
requireRepoDir: true,
|
|
128
|
+
writable: options?.writable ?? config.writable === true,
|
|
129
|
+
force: options?.force,
|
|
130
|
+
});
|
|
131
|
+
const syncedAt = (options?.now ?? new Date()).toISOString();
|
|
132
|
+
const contentDir = cachePaths.repoDir;
|
|
133
|
+
return {
|
|
134
|
+
id: repo.canonicalUrl,
|
|
135
|
+
source: "git",
|
|
136
|
+
ref: repo.canonicalUrl,
|
|
137
|
+
artifactUrl: repo.canonicalUrl,
|
|
138
|
+
contentDir,
|
|
139
|
+
cacheDir: cachePaths.rootDir,
|
|
140
|
+
extractedDir: contentDir,
|
|
141
|
+
writable: options?.writable ?? config.writable === true,
|
|
142
|
+
syncedAt,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function pullRepo(repoDir) {
|
|
146
|
+
const result = runGit(["-C", repoDir, "pull", "--ff-only"], {
|
|
147
|
+
timeout: 120_000,
|
|
148
|
+
});
|
|
149
|
+
if (result.status !== 0) {
|
|
150
|
+
const err = result.stderr?.trim() || result.error?.message || "unknown error";
|
|
151
|
+
throw new Error(`Failed to pull ${repoDir}: ${err}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function hasExtractedRepo(repoDir) {
|
|
155
|
+
try {
|
|
156
|
+
if (!fs.statSync(repoDir).isDirectory())
|
|
157
|
+
return false;
|
|
158
|
+
if (fs.statSync(path.join(repoDir, "content")).isDirectory())
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
/* fall through to root-layout detection */
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
if (!fs.statSync(repoDir).isDirectory())
|
|
166
|
+
return false;
|
|
167
|
+
return Object.values(TYPE_DIRS).some((dirName) => fs.existsSync(path.join(repoDir, dirName)));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export function parseGitRepoUrl(rawUrl) {
|
|
174
|
+
if (!rawUrl) {
|
|
175
|
+
throw new ConfigError("Git provider requires a repository URL");
|
|
176
|
+
}
|
|
177
|
+
// SSH shorthand: git@host:path — valid as-is, delegated to system git credentials
|
|
178
|
+
if (/^git@[^:]+:.+$/.test(rawUrl)) {
|
|
179
|
+
return { cloneUrl: rawUrl, ref: null, canonicalUrl: rawUrl };
|
|
180
|
+
}
|
|
181
|
+
// Validate URL scheme is safe before parsing
|
|
182
|
+
try {
|
|
183
|
+
validateGitUrl(rawUrl);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (err instanceof UsageError)
|
|
187
|
+
throw new ConfigError(err.message);
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = new URL(rawUrl);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
throw new ConfigError(`Git provider URL is not valid: "${rawUrl}"`);
|
|
196
|
+
}
|
|
197
|
+
// GitHub web URLs: extract a clean clone URL and optional branch from /tree/<ref>
|
|
198
|
+
if (parsed.hostname === "github.com" && parsed.protocol === "https:") {
|
|
199
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
200
|
+
if (segments.length < 2) {
|
|
201
|
+
throw new ConfigError(`Git provider URL must point to a repository, got "${rawUrl}"`);
|
|
202
|
+
}
|
|
203
|
+
const owner = sanitizeString(segments[0]);
|
|
204
|
+
const repo = sanitizeString(segments[1].replace(/\.git$/i, ""));
|
|
205
|
+
if (!owner || !repo || !/^[A-Za-z0-9_.-]+$/.test(owner) || !/^[A-Za-z0-9_.-]+$/.test(repo)) {
|
|
206
|
+
throw new ConfigError(`Unsupported repository URL: "${rawUrl}"`);
|
|
207
|
+
}
|
|
208
|
+
let ref = null;
|
|
209
|
+
if (segments[2] === "tree" && segments.length >= 4) {
|
|
210
|
+
const rawRef = sanitizeString(segments.slice(3).join("/"), 255);
|
|
211
|
+
if (rawRef && !rawRef.includes("..") && /^[A-Za-z0-9._/-]+$/.test(rawRef)) {
|
|
212
|
+
ref = rawRef;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const cloneUrl = `https://github.com/${owner}/${repo}`;
|
|
216
|
+
const canonicalUrl = ref ? `${cloneUrl}/tree/${ref}` : cloneUrl;
|
|
217
|
+
return { cloneUrl, ref, canonicalUrl };
|
|
218
|
+
}
|
|
219
|
+
// Any other valid git URL: use as-is for cloning, but strip embedded credentials
|
|
220
|
+
// from canonicalUrl so secrets don't leak into cache keys or warning messages.
|
|
221
|
+
let canonicalUrl = rawUrl;
|
|
222
|
+
try {
|
|
223
|
+
const u = new URL(rawUrl);
|
|
224
|
+
u.username = "";
|
|
225
|
+
u.password = "";
|
|
226
|
+
u.search = "";
|
|
227
|
+
u.hash = "";
|
|
228
|
+
canonicalUrl = u.toString();
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// URL failed to parse — fall back to raw (validateGitUrl already accepted it)
|
|
232
|
+
}
|
|
233
|
+
return { cloneUrl: rawUrl, ref: null, canonicalUrl };
|
|
234
|
+
}
|