akm-cli 0.9.16-alpha.1 → 0.9.16
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 +56 -132
- package/dist/assets/hints/cli-hints-full.md +13 -6
- package/dist/assets/tasks/core/index-refresh.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
- package/dist/cli/retired-commands.js +0 -4
- package/dist/cli/unknown-flags.js +3 -36
- package/dist/commands/env/env-binding.js +4 -4
- package/dist/commands/env/env-cli.js +3 -3
- package/dist/commands/improve/collapse-detector.js +2 -2
- package/dist/commands/improve/consolidate.js +4 -6
- package/dist/commands/improve/improve-cli.js +20 -15
- package/dist/commands/improve/reflect.js +23 -2
- package/dist/commands/lint/base-linter.js +9 -0
- package/dist/commands/lint/env-key-rules.js +2 -2
- package/dist/commands/proposal/propose.js +15 -1
- package/dist/commands/proposal/repository.js +3 -12
- package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
- package/dist/commands/proposal/validators/proposal-validators.js +5 -4
- package/dist/commands/read/curate.js +44 -34
- package/dist/commands/read/search.js +35 -54
- package/dist/commands/read/show.js +21 -2
- package/dist/commands/registry-cli.js +5 -5
- package/dist/commands/sources/add-cli.js +59 -16
- package/dist/commands/sources/bundle-cli.js +35 -11
- package/dist/commands/sources/bundle-config-ops.js +30 -0
- package/dist/commands/sources/dangerous-env-audit.js +4 -4
- package/dist/commands/sources/info.js +8 -8
- package/dist/commands/sources/installed-stashes.js +55 -61
- package/dist/commands/sources/source-add.js +39 -38
- package/dist/commands/sources/source-manage.js +34 -12
- package/dist/commands/sources/stash-cli.js +111 -119
- package/dist/commands/sources/stash-skeleton.js +6 -3
- package/dist/commands/tasks/explain.js +4 -1
- package/dist/commands/tasks/tasks-cli.js +31 -9
- package/dist/commands/tasks/tasks.js +239 -194
- package/dist/commands/tasks/validate.js +20 -32
- package/dist/core/activation-policy.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +8 -35
- package/dist/core/adapter/adapters/akm-metadata.js +1 -11
- package/dist/core/adapter/execution-source.js +10 -29
- package/dist/core/asset/asset-placement.js +0 -35
- package/dist/core/config/config-schema.js +64 -8
- package/dist/core/config/config-sources.js +96 -2
- package/dist/core/config/config.js +190 -24
- package/dist/core/config/legacy-source-shape-shim.js +9 -0
- package/dist/core/config/schema/embedding.js +30 -7
- package/dist/core/config/schema/execution.js +23 -0
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/scheduler.js +20 -0
- package/dist/core/config/schema/search.js +10 -12
- package/dist/core/config/schema/sources-bundles.js +32 -1
- package/dist/core/content-safety.js +52 -0
- package/dist/core/errors.js +2 -5
- package/dist/core/maintenance-barrier.js +11 -13
- package/dist/core/paths.js +11 -0
- package/dist/core/run-lock.js +2 -5
- package/dist/core/state/migrations.js +1 -26
- package/dist/core/state-db.js +27 -63
- package/dist/core/type-presentation.js +1 -1
- package/dist/core/write-source.js +13 -8
- package/dist/indexer/bundle-identity-guard.js +45 -8
- package/dist/indexer/ensure-index.js +0 -5
- package/dist/indexer/index-db-contention.js +56 -0
- package/dist/indexer/index-rebuild-lock.js +73 -0
- package/dist/indexer/index-written-assets.js +171 -133
- package/dist/indexer/indexer.js +1621 -458
- package/dist/indexer/lookup/adapter-concept-owner.js +5 -19
- package/dist/indexer/materialize-embeddings.js +785 -0
- package/dist/indexer/passes/dir-staleness.js +161 -0
- package/dist/indexer/passes/metadata.js +1 -18
- package/dist/indexer/scan/drain-dir.js +70 -27
- package/dist/indexer/search/db-search.js +89 -373
- package/dist/indexer/search/ranking-contributors.js +16 -21
- package/dist/indexer/search/ranking.js +57 -135
- package/dist/indexer/search/search-source.js +29 -11
- package/dist/integrations/agent/execution-lowering.js +3 -2
- package/dist/integrations/agent/execution-preparation.js +32 -1
- package/dist/integrations/agent/prompts.js +1 -1
- package/dist/integrations/agent/request-lowering.js +3 -2
- package/dist/llm/client.js +3 -11
- package/dist/llm/embedder.js +3 -10
- package/dist/llm/embedders/remote.js +104 -133
- package/dist/llm/feature-gate.js +2 -4
- package/dist/llm/rerank-client.js +3 -3
- package/dist/output/html-render.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -1
- package/dist/output/stdout.js +24 -0
- package/dist/output/text/command-format.js +13 -19
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/index.js +2 -5
- package/dist/output/text.js +4 -3
- package/dist/registry/resolve.js +37 -10
- package/dist/scripts/akm-migrate-node.js +15197 -11351
- package/dist/scripts/akm-migrate.js +15514 -11668
- package/dist/setup/semantic-assets.js +2 -2
- package/dist/setup/setup.js +3 -3
- package/dist/setup/steps/connection.js +2 -3
- package/dist/setup/steps/tasks.js +29 -36
- package/dist/sources/providers/git-install.js +17 -11
- package/dist/sources/providers/git-provider.js +12 -5
- package/dist/sources/providers/git-stash.js +38 -16
- package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
- package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
- package/dist/storage/repositories/index-connection.js +3 -1
- package/dist/storage/repositories/index-entries-repository.js +68 -77
- package/dist/storage/repositories/index-entry-schema.js +25 -16
- package/dist/storage/repositories/index-fts-repository.js +263 -29
- package/dist/storage/repositories/index-meta-repository.js +29 -0
- package/dist/storage/repositories/index-schema.js +122 -115
- package/dist/storage/repositories/index-utility-repository.js +1 -1
- package/dist/storage/repositories/index-vec-repository.js +435 -22
- package/dist/tasks/activation-config.js +90 -0
- package/dist/tasks/backends/cron.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -0
- package/dist/tasks/backends/schtasks.js +2 -0
- package/dist/tasks/embedded.js +4 -5
- package/dist/tasks/scheduler-binding.js +2 -2
- package/dist/tasks/scheduler-sync-preview.js +8 -1
- package/dist/tasks/scheduler-sync.js +19 -10
- package/dist/tasks/source/parse-task-source.js +10 -113
- package/dist/tasks/source/project-v4.js +2 -2
- package/dist/tasks/source/task-source-v4.js +4 -12
- package/dist/tasks/source/task-to-v3.js +4 -12
- package/dist/tasks/source/task-to-v4.js +40 -7
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.15.md +36 -34
- package/docs/migration/release-notes/0.9.16.md +60 -98
- package/docs/migration/release-notes/README.md +0 -5
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
- package/docs/reference/cli.md +124 -122
- package/docs/reference/configuration.md +137 -133
- package/docs/reference/data-and-telemetry.md +1 -2
- package/docs/reference/tasks.md +34 -29
- package/package.json +1 -1
- package/schemas/akm-config.json +170 -6
- package/schemas/akm-task.json +1 -2
- package/dist/commands/sources/index-status.js +0 -99
- package/dist/core/hash.js +0 -18
- package/dist/indexer/drain.js +0 -306
- package/dist/indexer/embedding-identity.js +0 -20
- package/dist/indexer/enrich.js +0 -260
- package/dist/indexer/reconcile.js +0 -890
- package/dist/indexer/scan/parse-file.js +0 -66
- package/dist/indexer/units/unit.js +0 -159
- package/dist/llm/embedders/provider-limits.js +0 -288
- package/dist/storage/repositories/files-repository.js +0 -181
- package/dist/storage/repositories/units-repository.js +0 -510
|
@@ -6,7 +6,8 @@ import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
|
6
6
|
import { isRemoteUrl } from "../../core/common.js";
|
|
7
7
|
import { bundleEntryToSourceEntry, bundlesToSourceEntries, getSources, mutateConfig } from "../../core/config/config.js";
|
|
8
8
|
import { ConfigError, UsageError } from "../../core/errors.js";
|
|
9
|
-
import {
|
|
9
|
+
import { revokeSchedulerActivationsForBundle } from "../../tasks/activation-config.js";
|
|
10
|
+
import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey, placeBundle, } from "./bundle-config-ops.js";
|
|
10
11
|
// ── Operations ──────────────────────────────────────────────────────────────
|
|
11
12
|
/**
|
|
12
13
|
* Add a stash source (filesystem path or remote provider URL) to config.
|
|
@@ -16,13 +17,16 @@ import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey } from "./bundle-confi
|
|
|
16
17
|
* (e.g. "website", "git").
|
|
17
18
|
*/
|
|
18
19
|
export function addStash(opts) {
|
|
19
|
-
const { target, name, providerType, options: providerOptions, writable } = opts;
|
|
20
|
+
const { target, name, providerType, options: providerOptions, writable, credential, before, after } = opts;
|
|
20
21
|
if (providerType === "openviking") {
|
|
21
22
|
throw new ConfigError("openviking is not supported in akm v1.", "INVALID_CONFIG_FILE");
|
|
22
23
|
}
|
|
23
24
|
if (writable === true && providerType && providerType !== "filesystem" && providerType !== "git") {
|
|
24
25
|
throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
|
|
25
26
|
}
|
|
27
|
+
if (credential && providerType !== "git") {
|
|
28
|
+
throw new ConfigError("credential is only supported on git sources", "INVALID_CONFIG_FILE");
|
|
29
|
+
}
|
|
26
30
|
let result;
|
|
27
31
|
const targetIsUrl = isRemoteUrl(target);
|
|
28
32
|
if (targetIsUrl && !providerType) {
|
|
@@ -53,7 +57,15 @@ export function addStash(opts) {
|
|
|
53
57
|
return config;
|
|
54
58
|
}
|
|
55
59
|
key = nextBundleKey(bundles, name, target);
|
|
56
|
-
|
|
60
|
+
const entry = urlBundleDescriptor(providerType, target, providerOptions, writable === true, credential);
|
|
61
|
+
const nextBundles = placeBundle(bundles, key, entry, { before, after });
|
|
62
|
+
const next = { ...config, bundles: nextBundles };
|
|
63
|
+
result = {
|
|
64
|
+
sources: bundlesToSourceEntries(next) ?? [],
|
|
65
|
+
added: true,
|
|
66
|
+
entry: bundleEntryToSourceEntry(key, entry),
|
|
67
|
+
};
|
|
68
|
+
return next;
|
|
57
69
|
}
|
|
58
70
|
else {
|
|
59
71
|
const resolvedPath = path.resolve(target);
|
|
@@ -62,18 +74,22 @@ export function addStash(opts) {
|
|
|
62
74
|
return config;
|
|
63
75
|
}
|
|
64
76
|
key = nextBundleKey(bundles, name, resolvedPath);
|
|
65
|
-
|
|
77
|
+
const entry = {
|
|
66
78
|
path: resolvedPath,
|
|
67
79
|
...(writable === true ? { writable: true } : {}),
|
|
68
80
|
components: {
|
|
69
81
|
main: { root: ".", adapter: detectAdapterId(resolvedPath), writable: writable ?? true },
|
|
70
82
|
},
|
|
71
83
|
};
|
|
84
|
+
const nextBundles = placeBundle(bundles, key, entry, { before, after });
|
|
85
|
+
const next = { ...config, bundles: nextBundles };
|
|
86
|
+
result = {
|
|
87
|
+
sources: bundlesToSourceEntries(next) ?? [],
|
|
88
|
+
added: true,
|
|
89
|
+
entry: bundleEntryToSourceEntry(key, entry),
|
|
90
|
+
};
|
|
91
|
+
return next;
|
|
72
92
|
}
|
|
73
|
-
const next = { ...config, bundles };
|
|
74
|
-
const entry = bundleEntryToSourceEntry(key, bundles[key]);
|
|
75
|
-
result = { sources: bundlesToSourceEntries(next) ?? [], added: true, entry };
|
|
76
|
-
return next;
|
|
77
93
|
});
|
|
78
94
|
return result;
|
|
79
95
|
}
|
|
@@ -83,7 +99,7 @@ export function addStash(opts) {
|
|
|
83
99
|
* package spec (e.g. "lodash", "@scope/pkg@^2") — never a URL (R-013,
|
|
84
100
|
* rejected earlier in {@link addStash}).
|
|
85
101
|
*/
|
|
86
|
-
function urlBundleDescriptor(providerType, locator, options, writable) {
|
|
102
|
+
function urlBundleDescriptor(providerType, locator, options, writable, credential) {
|
|
87
103
|
if (providerType === "website") {
|
|
88
104
|
// Website provider options ride on the (passthrough) website descriptor and
|
|
89
105
|
// round-trip back to `entry.options` via bundleEntryToSourceEntry.
|
|
@@ -94,8 +110,9 @@ function urlBundleDescriptor(providerType, locator, options, writable) {
|
|
|
94
110
|
}
|
|
95
111
|
if (providerType === "npm")
|
|
96
112
|
return { npm: locator };
|
|
97
|
-
if (providerType === "git")
|
|
98
|
-
return { git: locator, ...(writable ? { writable: true } : {}) };
|
|
113
|
+
if (providerType === "git") {
|
|
114
|
+
return { git: locator, ...(writable ? { writable: true } : {}), ...(credential ? { credential } : {}) };
|
|
115
|
+
}
|
|
99
116
|
throw new ConfigError(`unsupported source type "${providerType}"; expected filesystem, git, website, or npm`, "INVALID_CONFIG_FILE");
|
|
100
117
|
}
|
|
101
118
|
/**
|
|
@@ -120,7 +137,12 @@ export function removeStash(target) {
|
|
|
120
137
|
}
|
|
121
138
|
const removed = bundleEntryToSourceEntry(key, bundles[key]);
|
|
122
139
|
delete bundles[key];
|
|
123
|
-
const next = {
|
|
140
|
+
const next = revokeSchedulerActivationsForBundle({
|
|
141
|
+
...config,
|
|
142
|
+
bundles: Object.keys(bundles).length > 0 ? bundles : undefined,
|
|
143
|
+
...(config.defaultBundle === key ? { defaultBundle: undefined } : {}),
|
|
144
|
+
...(config.defaultWriteTarget === key ? { defaultWriteTarget: undefined } : {}),
|
|
145
|
+
}, key);
|
|
124
146
|
result = { sources: bundlesToSourceEntries(next) ?? [], removed: true, entry: removed };
|
|
125
147
|
return next;
|
|
126
148
|
});
|
|
@@ -28,9 +28,10 @@
|
|
|
28
28
|
* SIGINT/SIGTERM handlers in a try/finally — left byte-for-byte untouched.
|
|
29
29
|
*/
|
|
30
30
|
import path from "node:path";
|
|
31
|
+
import { defineCommand } from "citty";
|
|
31
32
|
import * as p from "../../cli/clack.js";
|
|
32
33
|
import { getParsedInvocation } from "../../cli/invocation.js";
|
|
33
|
-
import {
|
|
34
|
+
import { defineJsonCommand, GLOBAL_OUTPUT_ARGS, output, parseAllFlagValues, runWithJsonErrors } from "../../cli/shared.js";
|
|
34
35
|
import { assertFlatAssetName } from "../../core/asset/asset-create.js";
|
|
35
36
|
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
36
37
|
import { isHttpUrl, resolveStashDir } from "../../core/common.js";
|
|
@@ -39,51 +40,17 @@ import { UsageError } from "../../core/errors.js";
|
|
|
39
40
|
import { appendEvent } from "../../core/events.js";
|
|
40
41
|
import { resolveBundleWriteTarget } from "../../core/mutation-target.js";
|
|
41
42
|
import { getCacheDir } from "../../core/paths.js";
|
|
42
|
-
import { clearLogFile, info, isVerbose, setLogFile
|
|
43
|
+
import { clearLogFile, info, isVerbose, setLogFile } from "../../core/warn.js";
|
|
43
44
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
44
|
-
import {
|
|
45
|
+
import { releaseIndexRebuildLock, tryAcquireIndexRebuildLock } from "../../indexer/index-rebuild-lock.js";
|
|
45
46
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
46
|
-
import { RECONCILE_ROOT_PROGRESS_PREFIX } from "../../indexer/reconcile.js";
|
|
47
47
|
import { getHyphenatedBoolean, getOutputMode } from "../../output/context.js";
|
|
48
48
|
import { inferAssetName, mergeXrefsIntoContent, readKnowledgeInput, resolveSupersedesForWrite, resolveSupersedesWriteTarget, resolveXrefsForWrite, writeMarkdownAsset, } from "../read/knowledge.js";
|
|
49
|
-
import { assembleIndexStatus } from "./index-status.js";
|
|
50
49
|
import { assembleInfo } from "./info.js";
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* exact prefix each producer exports, not a re-derived regex, so the two
|
|
56
|
-
* never drift apart (index-redesign B5g): this used to be a regex tuned to
|
|
57
|
-
* the deleted per-entry pipeline's `Embedded N/M entries.` line, which never
|
|
58
|
-
* matched either replacement line, so every progress line reached stderr
|
|
59
|
-
* regardless of `--verbose`.
|
|
60
|
-
*/
|
|
61
|
-
function isDetailProgressLine(message) {
|
|
62
|
-
return message.startsWith(DRAIN_BATCH_PROGRESS_PREFIX) || message.startsWith(RECONCILE_ROOT_PROGRESS_PREFIX);
|
|
63
|
-
}
|
|
64
|
-
export const indexStatusCommand = defineJsonCommand({
|
|
65
|
-
meta: {
|
|
66
|
-
name: "status",
|
|
67
|
-
description: "Show index.db's current state: files, entries, unit coverage, and the last reconcile time.",
|
|
68
|
-
},
|
|
69
|
-
run() {
|
|
70
|
-
output("index-status", assembleIndexStatus());
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
/**
|
|
74
|
-
* `akm index` = reconcile + drain (docs/plans/index-redesign.md). Still a raw
|
|
75
|
-
* group command (not `defineJsonCommand`) because its default body owns a
|
|
76
|
-
* spinner, an AbortController, and SIGINT/SIGTERM handlers in a try/finally;
|
|
77
|
-
* `defineGroupCommand` gives it `status` as a real subcommand while keeping
|
|
78
|
-
* that default body as plain `akm index`'s behavior (S-057 canonical
|
|
79
|
-
* bare-group rule does not apply here — a bare `akm index` has always run the
|
|
80
|
-
* indexer, and that stays).
|
|
81
|
-
*/
|
|
82
|
-
export const indexCommand = defineGroupCommand({
|
|
83
|
-
meta: {
|
|
84
|
-
name: "index",
|
|
85
|
-
description: "Reconcile the search index and drain the embedding queue (--full forces a full re-derivation)",
|
|
86
|
-
},
|
|
50
|
+
/** Matches the high-frequency per-committed-batch progress line (#954), excluded from non-verbose JSON-mode stderr. */
|
|
51
|
+
const EMBEDDED_BATCH_PROGRESS_PATTERN = /^Embedded \d+\/\d+ entries\.$/;
|
|
52
|
+
export const indexCommand = defineCommand({
|
|
53
|
+
meta: { name: "index", description: "Build search index (incremental by default; --full forces full reindex)" },
|
|
87
54
|
args: {
|
|
88
55
|
// R-051: `index` is a raw `defineCommand` (not `defineJsonCommand`), so it
|
|
89
56
|
// does not get `GLOBAL_OUTPUT_ARGS` for free. `--format`/`--detail`/
|
|
@@ -91,99 +58,124 @@ export const indexCommand = defineGroupCommand({
|
|
|
91
58
|
// extra positional for a stray value to fall into), so this is purely a
|
|
92
59
|
// `--help` visibility / consistency fix, not a behavior change.
|
|
93
60
|
...GLOBAL_OUTPUT_ARGS,
|
|
94
|
-
full: {
|
|
61
|
+
full: { type: "boolean", description: "Force full reindex", default: false },
|
|
62
|
+
clean: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
description: "After indexing, remove any entries whose source file no longer exists on disk.",
|
|
65
|
+
default: false,
|
|
66
|
+
},
|
|
67
|
+
"dry-run": {
|
|
95
68
|
type: "boolean",
|
|
96
|
-
description: "
|
|
97
|
-
"existing rows keep their id/embeddings/utility scores; nothing is dropped first.",
|
|
69
|
+
description: "When combined with --clean, report stale entries without deleting them.",
|
|
98
70
|
default: false,
|
|
99
71
|
},
|
|
100
72
|
reembed: {
|
|
101
73
|
type: "boolean",
|
|
102
|
-
description: "
|
|
74
|
+
description: "Force re-embedding of every entry, bypassing the embedding-model-rename compatibility check.",
|
|
103
75
|
default: false,
|
|
104
76
|
},
|
|
105
77
|
"skip-if-locked": {
|
|
106
78
|
type: "boolean",
|
|
107
|
-
description: "
|
|
108
|
-
"every write is a short, idempotent, content-addressed transaction, so two concurrent index runs " +
|
|
109
|
-
"converge instead of contending. Kept only so existing scripts do not fail on an unknown flag.",
|
|
79
|
+
description: "If another `akm index` run already holds the rebuild lock, skip gracefully (exit 0) instead of contending with it. Use for scheduled/opportunistic index runs so they don't pile up against a longer run in progress.",
|
|
110
80
|
default: false,
|
|
111
81
|
},
|
|
112
82
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
if (getHyphenatedBoolean(args, "re-enrich") || getParsedInvocation().getFlagValue("--re-enrich") !== undefined) {
|
|
119
|
-
throw new UsageError("`akm index --re-enrich` has been removed. Re-enrichment of index-time LLM passes is not exposed in this slice.");
|
|
120
|
-
}
|
|
121
|
-
if (args["skip-if-locked"]) {
|
|
122
|
-
warn("[index] --skip-if-locked is deprecated and has no effect — index runs no longer take a rebuild lock.");
|
|
123
|
-
}
|
|
124
|
-
const outputMode = getOutputMode();
|
|
125
|
-
const controller = new AbortController();
|
|
126
|
-
const abort = () => controller.abort(new Error("index interrupted"));
|
|
127
|
-
process.once("SIGINT", abort);
|
|
128
|
-
process.once("SIGTERM", abort);
|
|
129
|
-
const indexLogFile = path.join(getCacheDir(), "logs", "index", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
130
|
-
setLogFile(indexLogFile);
|
|
131
|
-
const verbose = isVerbose();
|
|
132
|
-
const spin = !verbose && outputMode.format === "text" ? p.spinner() : null;
|
|
133
|
-
if (spin) {
|
|
134
|
-
spin.start(`Building search index${args.full ? " (full rebuild)" : ""}...`);
|
|
135
|
-
}
|
|
136
|
-
let latestMessage = "";
|
|
137
|
-
// Resolve the stash dir once at the `akm index` command boundary and
|
|
138
|
-
// thread it into the indexer (WI-9.10 CLI-wide sweep) — the indexer leaf
|
|
139
|
-
// no longer reads the ambient `resolveStashDir()`.
|
|
140
|
-
const stashDir = resolveStashDir();
|
|
141
|
-
try {
|
|
142
|
-
const result = await akmIndex({
|
|
143
|
-
stashDir,
|
|
144
|
-
full: args.full,
|
|
145
|
-
reembed: args.reembed,
|
|
146
|
-
onProgress: ({ phase, message, processed, total }) => {
|
|
147
|
-
latestMessage = message;
|
|
148
|
-
const progressPrefix = processed !== undefined && total !== undefined ? `[${processed}/${total}] ` : "";
|
|
149
|
-
if (verbose) {
|
|
150
|
-
info(`[index:${phase}] ${progressPrefix}${message}`);
|
|
151
|
-
}
|
|
152
|
-
else if (spin) {
|
|
153
|
-
spin.stop(`${progressPrefix}${message}`);
|
|
154
|
-
spin.start(`${progressPrefix}${message}`);
|
|
155
|
-
}
|
|
156
|
-
else if (!isDetailProgressLine(message)) {
|
|
157
|
-
// Non-verbose, non-text (JSON/yaml/etc) mode: silence used to be
|
|
158
|
-
// total until the run finished (#954) — a stalled
|
|
159
|
-
// run looked identical to "nothing written". Phase-start
|
|
160
|
-
// messages, the credential diagnostic, and the reconcile/drain
|
|
161
|
-
// totals now reach stderr here too; the high-frequency
|
|
162
|
-
// per-root `Reconciled "…"` and per-batch `[drain] batch N: …`
|
|
163
|
-
// lines are deliberately excluded — that would be spam, not a
|
|
164
|
-
// heartbeat. `--verbose` (the `if` branch above) still gets
|
|
165
|
-
// every one of them.
|
|
166
|
-
info(`[index:${phase}] ${progressPrefix}${message}`);
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
signal: controller.signal,
|
|
170
|
-
});
|
|
171
|
-
if (spin) {
|
|
172
|
-
spin.stop(`Indexed ${result.totalEntries} assets.`);
|
|
83
|
+
async run({ args }) {
|
|
84
|
+
await runWithJsonErrors(async () => {
|
|
85
|
+
if (getHyphenatedBoolean(args, "enrich") || getParsedInvocation().getFlagValue("--enrich") !== undefined) {
|
|
86
|
+
throw new UsageError("`akm index --enrich` has been removed. Plain `akm index` now performs metadata enrichment by default.");
|
|
173
87
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
88
|
+
if (getHyphenatedBoolean(args, "re-enrich") || getParsedInvocation().getFlagValue("--re-enrich") !== undefined) {
|
|
89
|
+
throw new UsageError("`akm index --re-enrich` has been removed. Re-enrichment of index-time LLM passes is not exposed in this slice.");
|
|
90
|
+
}
|
|
91
|
+
// #956: opt-in, non-blocking rebuild lock — never gates a human-typed
|
|
92
|
+
// `akm index` (it only warns and contends), but a scheduled/opportunistic
|
|
93
|
+
// caller can pass --skip-if-locked to step aside instead of piling up
|
|
94
|
+
// behind a run already in progress. Acquired before any other side
|
|
95
|
+
// effect (log file, spinner) so a skip does neither.
|
|
96
|
+
const lockAcquisition = tryAcquireIndexRebuildLock(args["skip-if-locked"]);
|
|
97
|
+
if (lockAcquisition.state === "skipped") {
|
|
98
|
+
output("index", {
|
|
99
|
+
ok: true,
|
|
100
|
+
skipped: {
|
|
101
|
+
reason: "lock-held",
|
|
102
|
+
pid: lockAcquisition.holder.pid,
|
|
103
|
+
// #956: the launcher pid (when known) alongside the pid that
|
|
104
|
+
// actually holds the lock — every process listing and task log
|
|
105
|
+
// shows the launcher pid, not the bun/node child's.
|
|
106
|
+
launcherPid: lockAcquisition.holder.launcherPid,
|
|
107
|
+
startedAt: lockAcquisition.holder.startedAt,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const outputMode = getOutputMode();
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const abort = () => controller.abort(new Error("index interrupted"));
|
|
115
|
+
process.once("SIGINT", abort);
|
|
116
|
+
process.once("SIGTERM", abort);
|
|
117
|
+
const indexLogFile = path.join(getCacheDir(), "logs", "index", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
|
|
118
|
+
setLogFile(indexLogFile);
|
|
119
|
+
const verbose = isVerbose();
|
|
120
|
+
const spin = !verbose && outputMode.format === "text" ? p.spinner() : null;
|
|
177
121
|
if (spin) {
|
|
178
|
-
spin.
|
|
122
|
+
spin.start(`Building search index${args.full ? " (full rebuild)" : ""}...`);
|
|
179
123
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
124
|
+
let latestMessage = "";
|
|
125
|
+
// Resolve the stash dir once at the `akm index` command boundary and
|
|
126
|
+
// thread it into the indexer (WI-9.10 CLI-wide sweep) — the indexer leaf
|
|
127
|
+
// no longer reads the ambient `resolveStashDir()`.
|
|
128
|
+
const stashDir = resolveStashDir();
|
|
129
|
+
try {
|
|
130
|
+
const result = await akmIndex({
|
|
131
|
+
stashDir,
|
|
132
|
+
full: args.full,
|
|
133
|
+
clean: args.clean,
|
|
134
|
+
dryRun: args["dry-run"],
|
|
135
|
+
reembed: args.reembed,
|
|
136
|
+
onProgress: ({ phase, message, processed, total }) => {
|
|
137
|
+
latestMessage = message;
|
|
138
|
+
const progressPrefix = processed !== undefined && total !== undefined ? `[${processed}/${total}] ` : "";
|
|
139
|
+
if (verbose) {
|
|
140
|
+
info(`[index:${phase}] ${progressPrefix}${message}`);
|
|
141
|
+
}
|
|
142
|
+
else if (spin) {
|
|
143
|
+
spin.stop(`${progressPrefix}${message}`);
|
|
144
|
+
spin.start(`${progressPrefix}${message}`);
|
|
145
|
+
}
|
|
146
|
+
else if (!EMBEDDED_BATCH_PROGRESS_PATTERN.test(message)) {
|
|
147
|
+
// Non-verbose, non-text (JSON/yaml/etc) mode: silence used to be
|
|
148
|
+
// total until the run finished (#954) — a stalled
|
|
149
|
+
// run looked identical to "nothing written". Phase-start
|
|
150
|
+
// messages and the embedding heartbeat now reach stderr here
|
|
151
|
+
// too; the high-frequency per-batch `Embedded N/M entries.`
|
|
152
|
+
// line (emitted after every committed batch)
|
|
153
|
+
// is deliberately excluded — that would be spam, not a
|
|
154
|
+
// heartbeat.
|
|
155
|
+
info(`[index:${phase}] ${progressPrefix}${message}`);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
signal: controller.signal,
|
|
159
|
+
});
|
|
160
|
+
if (spin) {
|
|
161
|
+
spin.stop(`Indexed ${result.totalEntries} assets.`);
|
|
162
|
+
}
|
|
163
|
+
output("index", result);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (spin) {
|
|
167
|
+
spin.stop(latestMessage ? `Indexing failed after: ${latestMessage}` : "Indexing failed.");
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
clearLogFile();
|
|
173
|
+
process.off("SIGINT", abort);
|
|
174
|
+
process.off("SIGTERM", abort);
|
|
175
|
+
if (lockAcquisition.state === "acquired")
|
|
176
|
+
releaseIndexRebuildLock(lockAcquisition.ownership);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
187
179
|
},
|
|
188
180
|
});
|
|
189
181
|
export const infoCommand = defineJsonCommand({
|
|
@@ -73,6 +73,8 @@ export function scaffoldStashMeta(stashDir) {
|
|
|
73
73
|
}
|
|
74
74
|
/** Marks the akm-authored block in a stash `.gitignore` (idempotency anchor). */
|
|
75
75
|
const STASH_GITIGNORE_MARKER = "# akm: keep secret material out of git by default";
|
|
76
|
+
/** Documented opt-out for private bundles that deliberately version env/ and secrets/. */
|
|
77
|
+
export const STASH_GITIGNORE_TRACK_SECRETS_MARKER = "# akm: intentionally track env and secrets";
|
|
76
78
|
const STASH_GITIGNORE_BLOCK = [
|
|
77
79
|
STASH_GITIGNORE_MARKER,
|
|
78
80
|
"# env/ and secrets/ assets hold tokens and keys. They are ignored by default",
|
|
@@ -89,14 +91,15 @@ const STASH_GITIGNORE_BLOCK = [
|
|
|
89
91
|
*
|
|
90
92
|
* Idempotent + non-clobbering: creates the file when absent, appends the akm
|
|
91
93
|
* block when the file exists but lacks it (preserving the user's own rules),
|
|
92
|
-
* and no-ops once the marker is present.
|
|
93
|
-
*
|
|
94
|
+
* and no-ops once the marker is present. A private bundle that deliberately
|
|
95
|
+
* versions all secret material can add `# akm: intentionally track env and
|
|
96
|
+
* secrets`; that explicit marker suppresses this helper entirely (#981).
|
|
94
97
|
*/
|
|
95
98
|
export function ensureStashGitignore(stashDir) {
|
|
96
99
|
try {
|
|
97
100
|
const gitignorePath = path.join(stashDir, ".gitignore");
|
|
98
101
|
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
|
|
99
|
-
if (existing.includes(STASH_GITIGNORE_MARKER))
|
|
102
|
+
if (existing.includes(STASH_GITIGNORE_MARKER) || existing.includes(STASH_GITIGNORE_TRACK_SECRETS_MARKER))
|
|
100
103
|
return;
|
|
101
104
|
const gap = existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
102
105
|
fs.writeFileSync(gitignorePath, `${existing}${gap}${STASH_GITIGNORE_BLOCK}`);
|
|
@@ -77,12 +77,14 @@
|
|
|
77
77
|
*/
|
|
78
78
|
import fs from "node:fs";
|
|
79
79
|
import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
|
|
80
|
+
import { makeBundleRef } from "../../core/asset/asset-ref.js";
|
|
80
81
|
import { loadConfig } from "../../core/config/config.js";
|
|
81
82
|
import { NotFoundError } from "../../core/errors.js";
|
|
82
83
|
import { applyInputDefaults, materializeInputFlags, } from "../../execution/input-contract.js";
|
|
83
84
|
import { createInlineResolvedCommand } from "../../execution/resolved-request.js";
|
|
84
85
|
import { resolveAdapterConceptOwner } from "../../indexer/lookup/adapter-concept-owner.js";
|
|
85
86
|
import { prepareResolvedExecution } from "../../integrations/agent/execution-preparation.js";
|
|
87
|
+
import { isSchedulerRefEnabled } from "../../tasks/activation-config.js";
|
|
86
88
|
import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
|
|
87
89
|
import { projectTaskSourceV4 } from "../../tasks/source/project-v4.js";
|
|
88
90
|
import { TASK_INPUT_DIAGNOSTICS } from "../../tasks/source/task-input-diagnostics.js";
|
|
@@ -270,10 +272,11 @@ export async function akmTaskExplain(ref, options = {}) {
|
|
|
270
272
|
const materializedInputs = materializeInputFlags(inputContract, options.inputFlags ?? [], TASK_INPUT_DIAGNOSTICS);
|
|
271
273
|
const defaultedInputs = applyInputDefaults(inputContract, materializedInputs);
|
|
272
274
|
const document = projectTaskSourceV4(parsed.v4);
|
|
275
|
+
const enabled = isSchedulerRefEnabled(loadConfig(), "task", makeBundleRef(bundle.source.name, taskConceptId));
|
|
273
276
|
const schedule = parsed.v4.schedule.map((entry) => ({
|
|
274
277
|
ordinal: entry.ordinal,
|
|
275
278
|
cron: entry.cron,
|
|
276
|
-
enabled
|
|
279
|
+
enabled,
|
|
277
280
|
source: entry.source,
|
|
278
281
|
inputs: Object.fromEntries(Object.entries(entry.inputs).map(([name, value]) => {
|
|
279
282
|
const redaction = redactIfSecretShaped(name, value);
|
|
@@ -12,13 +12,11 @@
|
|
|
12
12
|
* that would skip pending cleanup).
|
|
13
13
|
*
|
|
14
14
|
* 0.9 CLI overhaul (S6): the group was renamed from the plural `tasks` to the
|
|
15
|
-
* singular `task` — hard break, no alias. `init
|
|
16
|
-
* dropped: the default improve-schedule task set now ships as embedded
|
|
15
|
+
* singular `task` — hard break, no alias. `init` was dropped: the default improve-schedule task set now ships as embedded
|
|
17
16
|
* templates under src/assets/tasks/improve/ (see src/tasks/embedded.ts),
|
|
18
17
|
* seeded through the interactive `akm setup` task-review step instead of a
|
|
19
|
-
* separate CLI command
|
|
20
|
-
*
|
|
21
|
-
* subcommand (`add`/`run`/`history`/`sync`) shares one `--bundle <bundle>`
|
|
18
|
+
* separate CLI command. `enable`/`disable` mutate the host-local scheduler
|
|
19
|
+
* allow-list, never the task source. Every subcommand shares one `--bundle <bundle>`
|
|
22
20
|
* axis (S8.4 ratified `task add`'s `--target` → `--bundle`; a later Gate-1
|
|
23
21
|
* fix reverted it to match `import`/`proposal accept`, splitting the
|
|
24
22
|
* write-target axis three-vs-one against `remember`/`clone`/`improve` — the
|
|
@@ -35,7 +33,7 @@ import { TASK_RUN_BOOLEAN_FLAGS, TASK_RUN_VALUE_FLAGS } from "../../tasks/task-r
|
|
|
35
33
|
import { akmSearch, parseSearchSource } from "../read/search.js";
|
|
36
34
|
import { rejectRetiredSourceFlag } from "../read/search-cli.js";
|
|
37
35
|
import { akmTaskExplain } from "./explain.js";
|
|
38
|
-
import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksPrune, akmTasksRun, akmTasksSync, akmTasksSyncPlan, } from "./tasks.js";
|
|
36
|
+
import { akmTasksAdd, akmTasksDisable, akmTasksDoctor, akmTasksEnable, akmTasksHistory, akmTasksPrune, akmTasksRun, akmTasksSync, akmTasksSyncPlan, } from "./tasks.js";
|
|
39
37
|
import { akmTaskValidate } from "./validate.js";
|
|
40
38
|
/** Shared `--bundle <bundle>` arg wired onto every task subcommand. */
|
|
41
39
|
const bundleArg = {
|
|
@@ -294,6 +292,28 @@ const tasksRunCommand = defineCommand({
|
|
|
294
292
|
});
|
|
295
293
|
},
|
|
296
294
|
});
|
|
295
|
+
const tasksEnableCommand = defineJsonCommand({
|
|
296
|
+
meta: { name: "enable", description: "Enable a task in this host's scheduler configuration and sync it" },
|
|
297
|
+
args: {
|
|
298
|
+
ref: { type: "positional", description: "Task ref or id", required: true },
|
|
299
|
+
...bundleArg,
|
|
300
|
+
},
|
|
301
|
+
async run({ args }) {
|
|
302
|
+
rejectRetiredTaskTargetFlag();
|
|
303
|
+
output("task-enable", await akmTasksEnable(args.ref, { target: args.bundle }));
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const tasksDisableCommand = defineJsonCommand({
|
|
307
|
+
meta: { name: "disable", description: "Disable a task in this host's scheduler configuration and sync it" },
|
|
308
|
+
args: {
|
|
309
|
+
ref: { type: "positional", description: "Task ref or id", required: true },
|
|
310
|
+
...bundleArg,
|
|
311
|
+
},
|
|
312
|
+
async run({ args }) {
|
|
313
|
+
rejectRetiredTaskTargetFlag();
|
|
314
|
+
output("task-disable", await akmTasksDisable(args.ref, { target: args.bundle }));
|
|
315
|
+
},
|
|
316
|
+
});
|
|
297
317
|
/**
|
|
298
318
|
* #911: `task run <id>` and `task explain <ref>` take the id positionally, so
|
|
299
319
|
* `task history <id>` is the natural thing to write — and it used to be
|
|
@@ -415,8 +435,8 @@ const tasksDoctorCommand = defineJsonCommand({
|
|
|
415
435
|
},
|
|
416
436
|
});
|
|
417
437
|
/**
|
|
418
|
-
* #907: `akm task validate`'s exit-code contract —
|
|
419
|
-
* successful
|
|
438
|
+
* #907: `akm task validate`'s exit-code contract — only current-schema
|
|
439
|
+
* `valid` is successful (exit 0); `blocked`/`invalid`/`not-a-task` are
|
|
420
440
|
* diagnosed defects the caller must act on (exit 1, mirroring `task sync`'s
|
|
421
441
|
* own `failures.length > 0 -> EXIT_CODES.GENERAL`). A missing path or an
|
|
422
442
|
* unreadable file never reaches this function at all — `akmTaskValidate`
|
|
@@ -424,7 +444,7 @@ const tasksDoctorCommand = defineJsonCommand({
|
|
|
424
444
|
* already maps to exit 2.
|
|
425
445
|
*/
|
|
426
446
|
export function taskValidateExitCode(result) {
|
|
427
|
-
return result.outcome === "valid"
|
|
447
|
+
return result.outcome === "valid" ? undefined : EXIT_CODES.GENERAL;
|
|
428
448
|
}
|
|
429
449
|
const tasksValidateCommand = defineJsonCommand({
|
|
430
450
|
meta: {
|
|
@@ -532,6 +552,8 @@ export const taskCommand = defineGroupCommand({
|
|
|
532
552
|
},
|
|
533
553
|
subCommands: {
|
|
534
554
|
add: tasksAddCommand,
|
|
555
|
+
enable: tasksEnableCommand,
|
|
556
|
+
disable: tasksDisableCommand,
|
|
535
557
|
run: tasksRunCommand,
|
|
536
558
|
explain: tasksExplainCommand,
|
|
537
559
|
validate: tasksValidateCommand,
|