akm-cli 0.9.15 → 0.9.16-alpha.2

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/assets/hints/cli-hints-full.md +13 -6
  3. package/dist/assets/tasks/improve/akm-improve-catchup.yml +3 -6
  4. package/dist/cli/retired-commands.js +0 -2
  5. package/dist/commands/env/env-binding.js +4 -4
  6. package/dist/commands/env/env-cli.js +3 -3
  7. package/dist/commands/improve/improve-cli.js +19 -14
  8. package/dist/commands/improve/reflect.js +23 -2
  9. package/dist/commands/lint/base-linter.js +9 -0
  10. package/dist/commands/lint/env-key-rules.js +2 -2
  11. package/dist/commands/proposal/propose.js +15 -1
  12. package/dist/commands/proposal/validators/proposal-quality-validators.js +40 -3
  13. package/dist/commands/proposal/validators/proposal-validators.js +5 -4
  14. package/dist/commands/read/search.js +33 -4
  15. package/dist/commands/read/show.js +21 -2
  16. package/dist/commands/registry-cli.js +5 -5
  17. package/dist/commands/sources/add-cli.js +59 -16
  18. package/dist/commands/sources/bundle-cli.js +35 -11
  19. package/dist/commands/sources/bundle-config-ops.js +30 -0
  20. package/dist/commands/sources/dangerous-env-audit.js +4 -4
  21. package/dist/commands/sources/installed-stashes.js +43 -28
  22. package/dist/commands/sources/source-add.js +33 -17
  23. package/dist/commands/sources/source-manage.js +34 -12
  24. package/dist/commands/sources/stash-skeleton.js +6 -3
  25. package/dist/commands/tasks/explain.js +4 -1
  26. package/dist/commands/tasks/tasks-cli.js +31 -9
  27. package/dist/commands/tasks/tasks.js +239 -194
  28. package/dist/commands/tasks/validate.js +20 -32
  29. package/dist/core/activation-policy.js +4 -4
  30. package/dist/core/adapter/adapters/akm-adapter.js +5 -0
  31. package/dist/core/adapter/execution-source.js +10 -29
  32. package/dist/core/config/config-schema.js +64 -8
  33. package/dist/core/config/config-sources.js +96 -2
  34. package/dist/core/config/config.js +190 -24
  35. package/dist/core/config/legacy-source-shape-shim.js +9 -0
  36. package/dist/core/config/schema/execution.js +23 -0
  37. package/dist/core/config/schema/experimental.js +1 -1
  38. package/dist/core/config/schema/scheduler.js +20 -0
  39. package/dist/core/config/schema/search.js +1 -1
  40. package/dist/core/config/schema/sources-bundles.js +32 -1
  41. package/dist/core/content-safety.js +52 -0
  42. package/dist/core/maintenance-barrier.js +6 -6
  43. package/dist/core/type-presentation.js +1 -1
  44. package/dist/core/write-source.js +13 -8
  45. package/dist/indexer/bundle-identity-guard.js +45 -8
  46. package/dist/indexer/indexer.js +1 -1
  47. package/dist/indexer/materialize-embeddings.js +15 -1
  48. package/dist/indexer/search/search-source.js +29 -11
  49. package/dist/integrations/agent/execution-lowering.js +3 -2
  50. package/dist/integrations/agent/execution-preparation.js +32 -1
  51. package/dist/integrations/agent/prompts.js +1 -1
  52. package/dist/integrations/agent/request-lowering.js +3 -2
  53. package/dist/llm/client.js +2 -1
  54. package/dist/output/shapes/passthrough.js +2 -0
  55. package/dist/registry/resolve.js +37 -10
  56. package/dist/scripts/akm-migrate-node.js +13644 -9894
  57. package/dist/scripts/akm-migrate.js +12626 -8876
  58. package/dist/setup/setup.js +3 -3
  59. package/dist/setup/steps/tasks.js +29 -36
  60. package/dist/sources/providers/git-install.js +17 -11
  61. package/dist/sources/providers/git-provider.js +12 -5
  62. package/dist/sources/providers/git-stash.js +38 -16
  63. package/dist/sources/snapshot-fetchers/website-ingest.js +3 -3
  64. package/dist/storage/repositories/index-vec-repository.js +100 -0
  65. package/dist/tasks/activation-config.js +90 -0
  66. package/dist/tasks/backends/cron.js +9 -0
  67. package/dist/tasks/backends/launchd.js +1 -0
  68. package/dist/tasks/backends/schtasks.js +2 -0
  69. package/dist/tasks/embedded.js +4 -5
  70. package/dist/tasks/scheduler-binding.js +2 -2
  71. package/dist/tasks/scheduler-sync-preview.js +8 -1
  72. package/dist/tasks/scheduler-sync.js +19 -10
  73. package/dist/tasks/source/parse-task-source.js +10 -113
  74. package/dist/tasks/source/project-v4.js +2 -2
  75. package/dist/tasks/source/task-source-v4.js +4 -12
  76. package/dist/tasks/source/task-to-v3.js +4 -12
  77. package/dist/tasks/source/task-to-v4.js +40 -7
  78. package/docs/migration/README.md +1 -0
  79. package/docs/migration/release-notes/0.9.16.md +72 -0
  80. package/docs/migration/v0.9.1-to-v0.9.2.md +6 -9
  81. package/docs/reference/cli.md +37 -29
  82. package/docs/reference/configuration.md +48 -5
  83. package/docs/reference/tasks.md +34 -29
  84. package/package.json +1 -1
  85. package/schemas/akm-config.json +112 -4
  86. package/schemas/akm-task.json +1 -2
@@ -6,7 +6,7 @@ import path from "node:path";
6
6
  import { detectAdapterId } from "../../core/adapter/detect-adapter.js";
7
7
  import { isBundleSlug } from "../../core/asset/asset-ref.js";
8
8
  import { isHttpUrl, resolveStashDir } from "../../core/common.js";
9
- import { bundleEntryToSourceEntry, getSources, installedSourceDescriptor, loadConfig, mutateConfig, } from "../../core/config/config.js";
9
+ import { bundleEntryToSourceEntry, getSources, installedSourceDescriptor, loadConfig, mutateConfig, resolveSecret, } from "../../core/config/config.js";
10
10
  import { ConfigError, UsageError } from "../../core/errors.js";
11
11
  import { akmIndex } from "../../indexer/indexer.js";
12
12
  import { deriveBundleId } from "../../indexer/installations.js";
@@ -16,7 +16,8 @@ import { detectStashRoot } from "../../sources/providers/provider-utils.js";
16
16
  import { syncFromRef } from "../../sources/providers/sync-from-ref.js";
17
17
  import { storeSecretResolver } from "../../sources/snapshot-fetchers/secret-seam.js";
18
18
  import { ensureWebsiteMirror, shouldAllowPrivateWebsiteUrlForTests, validateWebsiteInputUrl, } from "../../sources/snapshot-fetchers/website-ingest.js";
19
- import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey } from "./bundle-config-ops.js";
19
+ import { revokeSchedulerActivationsForBundle } from "../../tasks/activation-config.js";
20
+ import { bundleKeyForPath, bundleKeyForUrl, nextBundleKey, placeBundle, } from "./bundle-config-ops.js";
20
21
  export async function akmAdd(input) {
21
22
  const ref = input.ref.trim();
22
23
  if (!ref)
@@ -24,23 +25,23 @@ export async function akmAdd(input) {
24
25
  "Examples: `akm bundle add @scope/stash`, `akm bundle add github:owner/repo`, `akm bundle add ./local/path`");
25
26
  const stashDir = resolveStashDir();
26
27
  if (shouldAddAsWebsiteUrl(ref)) {
27
- return addWebsiteSource(ref, stashDir, input.name, input.options);
28
+ return addWebsiteSource(ref, stashDir, input.name, input.options, input);
28
29
  }
29
30
  // Local directories become filesystem bundles; registry refs use the
30
31
  // registry-backed bundle installer below.
31
32
  try {
32
33
  const parsed = parseRegistryRef(ref);
33
34
  if (parsed.source === "local") {
34
- return addLocalSource(ref, parsed.sourcePath, stashDir, input.name, input.adapter);
35
+ return addLocalSource(ref, parsed.sourcePath, stashDir, input.name, input.adapter, input);
35
36
  }
36
37
  }
37
38
  catch {
38
39
  // Not a local ref — fall through to registry install
39
40
  }
40
- return addRegistryStash(ref, stashDir, input.writable);
41
+ return addRegistryStash(ref, stashDir, input.writable, input, input.credential);
41
42
  }
42
43
  /** Add a local directory as a filesystem bundle. */
43
- async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitAdapter) {
44
+ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitAdapter, position = {}) {
44
45
  const stashRoot = detectStashRoot(sourcePath);
45
46
  const resolvedPath = path.resolve(stashRoot);
46
47
  const adapter = explicitAdapter ?? detectAdapterId(resolvedPath);
@@ -62,11 +63,11 @@ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitA
62
63
  }
63
64
  const bundles = { ...(config.bundles ?? {}) };
64
65
  bundleKey = nextBundleKey(bundles, explicitName, resolvedPath);
65
- bundles[bundleKey] = {
66
+ const entry = {
66
67
  path: resolvedPath,
67
68
  components: { main: { root: ".", adapter } },
68
69
  };
69
- return { ...config, bundles };
70
+ return { ...config, bundles: placeBundle(bundles, bundleKey, entry, position) };
70
71
  });
71
72
  const index = await akmIndex({ stashDir });
72
73
  const updatedConfig = loadConfig();
@@ -92,7 +93,7 @@ async function addLocalSource(ref, sourcePath, stashDir, explicitName, explicitA
92
93
  },
93
94
  };
94
95
  }
95
- async function addWebsiteSource(ref, stashDir, name, options) {
96
+ async function addWebsiteSource(ref, stashDir, name, options, position = {}) {
96
97
  const allowPrivateHosts = shouldAllowPrivateWebsiteUrlForTests(ref);
97
98
  const normalizedUrl = validateWebsiteInputUrl(ref, { allowPrivateHosts });
98
99
  const numberOption = (value) => (typeof value === "number" ? value : undefined);
@@ -124,9 +125,9 @@ async function addWebsiteSource(ref, stashDir, name, options) {
124
125
  entry = bundleEntryToSourceEntry(key, bundles[key]);
125
126
  return config;
126
127
  }
127
- bundles[key] = nextBundle;
128
+ const nextBundles = placeBundle(bundles, key, nextBundle, position);
128
129
  entry = bundleEntryToSourceEntry(key, nextBundle);
129
- return { ...config, bundles };
130
+ return { ...config, bundles: nextBundles };
130
131
  });
131
132
  const cachePaths = await ensureWebsiteMirror(entry, {
132
133
  requireStashDir: true,
@@ -161,11 +162,14 @@ async function addWebsiteSource(ref, stashDir, name, options) {
161
162
  * Install a stash from a registry (npm, github, git) by dispatching to the
162
163
  * matching syncable provider and persisting the lock entry.
163
164
  */
164
- async function addRegistryStash(ref, stashDir, writable) {
165
+ async function addRegistryStash(ref, stashDir, writable, position = {}, credentialRef) {
165
166
  const parsedRef = parseRegistryRef(ref);
166
167
  if (writable === true && parsedRef.source !== "git" && parsedRef.source !== "github") {
167
168
  throw new ConfigError("writable: true is only supported on filesystem and git sources", "INVALID_CONFIG_FILE");
168
169
  }
170
+ if (credentialRef && parsedRef.source !== "git" && parsedRef.source !== "github") {
171
+ throw new ConfigError("credential is only supported on git sources", "INVALID_CONFIG_FILE");
172
+ }
169
173
  const currentConfig = loadConfig();
170
174
  const existingBundleKey = findInstalledBundleKey(currentConfig.bundles ?? {}, parsedRef.id);
171
175
  const existingBundle = existingBundleKey ? currentConfig.bundles?.[existingBundleKey] : undefined;
@@ -179,6 +183,11 @@ async function addRegistryStash(ref, stashDir, writable) {
179
183
  writable: effectiveWritable,
180
184
  ...(effectiveWritable && priorLock?.localRoot ? { writableRoot: priorLock.localRoot } : {}),
181
185
  ...(requiredRoots.length > 0 ? { writableRequiredRoots: requiredRoots } : {}),
186
+ ...(credentialRef
187
+ ? { credential: resolveSecret(credentialRef, storeSecretResolver.resolveSecret) }
188
+ : existingBundle?.credential
189
+ ? { credential: resolveSecret(existingBundle.credential, storeSecretResolver.resolveSecret) }
190
+ : {}),
182
191
  });
183
192
  const { config: updatedConfig, bundleId } = upsertInstalledRegistryEntry({
184
193
  id: synced.id,
@@ -191,7 +200,7 @@ async function addRegistryStash(ref, stashDir, writable) {
191
200
  cacheDir: synced.cacheDir,
192
201
  installedAt: synced.syncedAt,
193
202
  writable: synced.writable,
194
- });
203
+ }, position, credentialRef);
195
204
  // The prior materialized root (if this is a re-install) — read BEFORE the lock
196
205
  // upsert overwrites it, so a moved cache root can be cleaned afterwards.
197
206
  const priorLocalRoot = priorLock?.localRoot;
@@ -252,7 +261,7 @@ async function addRegistryStash(ref, stashDir, writable) {
252
261
  * via {@link upsertLockEntry} with the returned `bundleId`). Returns the config
253
262
  * plus the derived bundle id so the caller keys its lock entry identically.
254
263
  */
255
- export function upsertInstalledRegistryEntry(entry) {
264
+ export function upsertInstalledRegistryEntry(entry, position = {}, credential) {
256
265
  let bundleId = entry.id;
257
266
  const config = mutateConfig((current) => {
258
267
  const bundles = { ...(current.bundles ?? {}) };
@@ -271,13 +280,15 @@ export function upsertInstalledRegistryEntry(entry) {
271
280
  },
272
281
  };
273
282
  const descriptor = installedSourceDescriptor(entry.source, entry.ref, path.resolve(entry.stashRoot));
274
- bundles[bundleId] = {
283
+ const effectiveCredential = credential ?? bundles[bundleId]?.credential;
284
+ const nextEntry = {
275
285
  ...descriptor,
286
+ ...(effectiveCredential ? { credential: effectiveCredential } : {}),
276
287
  ...(entry.writable === true ? { writable: true } : {}),
277
288
  ...(entry.id !== bundleId ? { registryId: entry.id } : {}),
278
289
  components: components,
279
290
  };
280
- return { ...current, bundles };
291
+ return { ...current, bundles: placeBundle(bundles, bundleId, nextEntry, position) };
281
292
  }).config;
282
293
  return { config, bundleId };
283
294
  }
@@ -295,7 +306,12 @@ export async function removeInstalledRegistryEntry(id) {
295
306
  return current;
296
307
  removedKey = key;
297
308
  delete bundles[key];
298
- return { ...current, bundles: Object.keys(bundles).length > 0 ? bundles : undefined };
309
+ return revokeSchedulerActivationsForBundle({
310
+ ...current,
311
+ bundles: Object.keys(bundles).length > 0 ? bundles : undefined,
312
+ ...(current.defaultBundle === key ? { defaultBundle: undefined } : {}),
313
+ ...(current.defaultWriteTarget === key ? { defaultWriteTarget: undefined } : {}),
314
+ }, key);
299
315
  }).config;
300
316
  if (removedKey)
301
317
  await removeLockEntry(removedKey);
@@ -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 { bundleKeyForPath, bundleKeyForUrl, nextBundleKey } from "./bundle-config-ops.js";
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
- bundles[key] = urlBundleDescriptor(providerType, target, providerOptions, writable === true);
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
- bundles[key] = {
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 = { ...config, bundles: Object.keys(bundles).length > 0 ? bundles : undefined };
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
  });
@@ -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. The user opts INTO versioning by
93
- * un-ignoring a path.
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: entry.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`/`enable`/`disable` are
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, and toggling a task's enabled state is a file edit +
20
- * `task sync` (tasks-sync.test.ts already proves the flip path). Every
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 — `valid`/`converts` are
419
- * successful outcomes (exit 0); `blocked`/`invalid`/`not-a-task` are
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" || result.outcome === "converts" ? undefined : EXIT_CODES.GENERAL;
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,