akm-cli 0.9.17-alpha.5 → 0.9.17-alpha.7
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 +281 -0
- package/STABILITY.md +2 -2
- package/dist/akm +7 -7
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/commands/improve/consolidate.js +11 -0
- package/dist/commands/improve/execution.js +5 -5
- package/dist/commands/improve/improve-cli.js +27 -7
- package/dist/commands/improve/improve-strategies.js +3 -0
- package/dist/commands/improve/ledger.js +7 -3
- package/dist/commands/improve/loop-stages.js +5 -4
- package/dist/commands/improve/preparation.js +40 -10
- package/dist/commands/improve/reflect.js +46 -22
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +77 -0
- package/dist/commands/read/curate.js +15 -49
- package/dist/commands/read/show.js +2 -81
- package/dist/commands/tasks/tasks-cli.js +10 -12
- package/dist/commands/tasks/tasks.js +57 -56
- package/dist/commands/tasks/validate.js +27 -46
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/config/config.js +1 -1
- package/dist/core/config/schema/improve-processes.js +4 -3
- package/dist/core/config/schema/index-config.js +4 -23
- package/dist/core/improve-result.js +4 -1
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/paths.js +0 -4
- package/dist/indexer/db/graph-db.js +8 -81
- package/dist/indexer/graph/graph-extraction.js +74 -227
- package/dist/indexer/graph/graph-related.js +5 -4
- package/dist/indexer/indexer.js +1 -3
- package/dist/indexer/search/db-search.js +10 -1
- package/dist/indexer/usage/usage-events.js +34 -0
- package/dist/llm/feature-gate.js +0 -3
- package/dist/llm/graph-extract.js +81 -41
- package/dist/scripts/akm-migrate-node.js +7148 -7174
- package/dist/scripts/akm-migrate.js +8718 -8744
- package/dist/storage/repositories/index-entries-repository.js +5 -7
- package/dist/storage/repositories/index-schema.js +16 -34
- package/dist/storage/repositories/proposals-repository.js +4 -0
- package/dist/tasks/backends/cron.js +80 -43
- package/dist/tasks/backends/launchd.js +28 -15
- package/dist/tasks/backends/schtasks.js +25 -10
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +4 -2
- package/dist/tasks/scheduler-invocation.js +127 -235
- package/dist/tasks/scheduler-sync.js +13 -8
- package/dist/tasks/source/parse-task-source.js +22 -126
- package/dist/tasks/source/task-to-v3.js +1 -55
- package/dist/tasks/source/task-to-v4.js +1 -13
- package/docs/migration/v0.9.1-to-v0.9.2.md +7 -3
- package/docs/reference/cli.md +13 -4
- package/docs/reference/configuration.md +12 -0
- package/docs/reference/tasks.md +58 -40
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -6
|
@@ -435,17 +435,15 @@ const tasksDoctorCommand = defineJsonCommand({
|
|
|
435
435
|
},
|
|
436
436
|
});
|
|
437
437
|
/**
|
|
438
|
-
* #907: `akm task validate`'s exit-code contract — `valid`
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* throws a `UsageError` for those, which `defineJsonCommand`'s wrapping
|
|
445
|
-
* already maps to exit 2.
|
|
438
|
+
* #907: `akm task validate`'s exit-code contract — `valid` succeeds (exit
|
|
439
|
+
* 0); `blocked`/`invalid`/`not-a-task` are diagnosed defects the caller must
|
|
440
|
+
* act on (exit 1, mirroring `task sync`'s own `failures.length > 0 ->
|
|
441
|
+
* EXIT_CODES.GENERAL`). A missing path or an unreadable file never reaches
|
|
442
|
+
* this function at all — `akmTaskValidate` throws a `UsageError` for those,
|
|
443
|
+
* which `defineJsonCommand`'s wrapping already maps to exit 2.
|
|
446
444
|
*/
|
|
447
445
|
export function taskValidateExitCode(result) {
|
|
448
|
-
return result.outcome === "valid"
|
|
446
|
+
return result.outcome === "valid" ? undefined : EXIT_CODES.GENERAL;
|
|
449
447
|
}
|
|
450
448
|
const tasksValidateCommand = defineJsonCommand({
|
|
451
449
|
meta: {
|
|
@@ -478,9 +476,9 @@ export function taskPruneExitCode(result) {
|
|
|
478
476
|
const tasksPruneCommand = defineJsonCommand({
|
|
479
477
|
meta: {
|
|
480
478
|
name: "prune",
|
|
481
|
-
description: "Remove installed scheduler entries `sync` can never reclaim because
|
|
482
|
-
"
|
|
483
|
-
"
|
|
479
|
+
description: "Remove installed scheduler entries `sync` can never reclaim because they no longer resolve to a live " +
|
|
480
|
+
"bundle (the bundle directory a row names is gone, or an older row's --scheduler-context descriptor " +
|
|
481
|
+
"cannot be read). Defaults to a dry-run preview — zero scheduler writes. Requires --yes to remove anything; " +
|
|
484
482
|
"--id narrows removal to specific binding ids (comma-separated).",
|
|
485
483
|
},
|
|
486
484
|
args: {
|
|
@@ -41,7 +41,7 @@ import { readTaskHistory } from "../../tasks/run/task-history.js";
|
|
|
41
41
|
import { exitCodeForStatus } from "../../tasks/run/task-result.js";
|
|
42
42
|
import { parseSchedule, SCHEDULE_SUPPORTED_SUBSET_HINT } from "../../tasks/schedule.js";
|
|
43
43
|
import { compileTaskSchedulerBindings, schedulerBindingNativeId, } from "../../tasks/scheduler-binding.js";
|
|
44
|
-
import {
|
|
44
|
+
import { readLegacySchedulerContext, scheduledRowEnvironment } from "../../tasks/scheduler-invocation.js";
|
|
45
45
|
import { withSchedulerLock } from "../../tasks/scheduler-lock.js";
|
|
46
46
|
import { compileSchedulerSources, installedRowNativeId, installedRowOwner, installedRowScope, planSchedulerSync, renderSchedulerPlanPreview, scheduledInvocationBundle, } from "../../tasks/scheduler-sync.js";
|
|
47
47
|
import { parseTaskSource } from "../../tasks/source/parse-task-source.js";
|
|
@@ -301,9 +301,7 @@ export async function akmTasksHistory(input) {
|
|
|
301
301
|
*/
|
|
302
302
|
export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
|
|
303
303
|
return withSchedulerLock(async () => {
|
|
304
|
-
const { sched, plan,
|
|
305
|
-
if (publish && plan.operations.some((operation) => operation.kind !== "remove"))
|
|
306
|
-
publish();
|
|
304
|
+
const { sched, plan, warnings } = await buildSchedulerSyncPlan(deps, bundleTarget, options);
|
|
307
305
|
const failed = new Set();
|
|
308
306
|
const failures = [...plan.failures];
|
|
309
307
|
for (const operation of plan.operations) {
|
|
@@ -343,7 +341,7 @@ export async function akmTasksSync(deps = {}, bundleTarget, options = {}) {
|
|
|
343
341
|
}
|
|
344
342
|
/**
|
|
345
343
|
* `akm task sync --dry-run` (#849): the exact plan `akmTasksSync` would
|
|
346
|
-
* apply, previewed. It writes nothing: no config, no
|
|
344
|
+
* apply, previewed. It writes nothing: no config, no row.
|
|
347
345
|
*/
|
|
348
346
|
export async function akmTasksSyncPlan(deps = {}, bundleTarget, options = {}) {
|
|
349
347
|
const { sched, plan } = await buildSchedulerSyncPlan(deps, bundleTarget, { ...options, dryRun: true });
|
|
@@ -440,7 +438,7 @@ async function buildSchedulerSyncPlan(deps, bundleTarget, options) {
|
|
|
440
438
|
installed,
|
|
441
439
|
scopes,
|
|
442
440
|
...(sched.expectedSignature ? { expectedSignature: sched.expectedSignature.bind(sched) } : {}),
|
|
443
|
-
|
|
441
|
+
installOptions: runtime.options,
|
|
444
442
|
rebind: options.rebind === true,
|
|
445
443
|
extraRemovals,
|
|
446
444
|
keepRefs,
|
|
@@ -450,53 +448,45 @@ async function buildSchedulerSyncPlan(deps, bundleTarget, options) {
|
|
|
450
448
|
? plan.operations.some((operation) => operation.kind !== "remove")
|
|
451
449
|
: plan.installed.length > 0;
|
|
452
450
|
if (runtime.via === "checkout" && writesLauncher) {
|
|
453
|
-
warnings.push(`Scheduled tasks now run akm from a source checkout (${runtime.options
|
|
451
|
+
warnings.push(`Scheduled tasks now run akm from a source checkout (${runtime.options.binding?.join(" ")}); they run whatever the checkout holds when they fire. Install akm with \`npm install --global akm-cli\` or a standalone release, then run \`akm task sync --rebind\`.`);
|
|
454
452
|
}
|
|
455
|
-
return {
|
|
456
|
-
sched,
|
|
457
|
-
plan: { ...plan, failures: [...failures, ...plan.failures] },
|
|
458
|
-
...(runtime.publish ? { publish: runtime.publish } : {}),
|
|
459
|
-
warnings,
|
|
460
|
-
};
|
|
453
|
+
return { sched, plan: { ...plan, failures: [...failures, ...plan.failures] }, warnings };
|
|
461
454
|
}
|
|
462
455
|
/**
|
|
463
|
-
* The launcher and
|
|
464
|
-
* the current policy on every sync; a row that is already installed
|
|
465
|
-
* launcher unless `--rebind` (see `installOptionsFor`). An injected
|
|
466
|
-
* (tests) renders with its own
|
|
456
|
+
* The launcher and environment rows are written with. The environment
|
|
457
|
+
* follows the current policy on every sync; a row that is already installed
|
|
458
|
+
* keeps its launcher unless `--rebind` (see `installOptionsFor`). An injected
|
|
459
|
+
* backend (tests) renders with its own launcher.
|
|
467
460
|
*/
|
|
468
461
|
function prepareSchedulerRuntime(deps) {
|
|
462
|
+
const environment = scheduledRowEnvironment();
|
|
469
463
|
if (deps.schedulerRuntime) {
|
|
470
464
|
const runtime = deps.schedulerRuntime();
|
|
471
|
-
return {
|
|
472
|
-
options: { binding: runtime.binding, contextPath: runtime.contextPath },
|
|
473
|
-
...(runtime.via ? { via: runtime.via } : {}),
|
|
474
|
-
};
|
|
465
|
+
return { options: { binding: runtime.binding, environment }, ...(runtime.via ? { via: runtime.via } : {}) };
|
|
475
466
|
}
|
|
476
467
|
if (deps.backend)
|
|
477
|
-
return {};
|
|
478
|
-
const descriptor = schedulerContextDescriptor();
|
|
468
|
+
return { options: { environment } };
|
|
479
469
|
const invocation = resolveAkmInvocation();
|
|
480
|
-
return {
|
|
481
|
-
options: { binding: invocation.argv, contextPath: schedulerContextPath(descriptor) },
|
|
482
|
-
// The content-addressed descriptor is written once, before the first row that references it.
|
|
483
|
-
publish: () => {
|
|
484
|
-
writeSchedulerContextDescriptor(descriptor);
|
|
485
|
-
},
|
|
486
|
-
via: invocation.via,
|
|
487
|
-
};
|
|
470
|
+
return { options: { binding: invocation.argv, environment }, via: invocation.via };
|
|
488
471
|
}
|
|
489
|
-
/** One read of the akm-owned rows, each attributed to the bundle path
|
|
472
|
+
/** One read of the akm-owned rows, each attributed to the bundle path it names (#846). */
|
|
490
473
|
async function listInstalledRows(sched) {
|
|
491
474
|
return (await sched.list()).map((row) => {
|
|
492
|
-
const ownerBundlePath =
|
|
475
|
+
const ownerBundlePath = installedRowBundleDir(row);
|
|
493
476
|
return ownerBundlePath !== undefined ? { ...row, ownerBundlePath } : row;
|
|
494
477
|
});
|
|
495
478
|
}
|
|
496
|
-
/**
|
|
497
|
-
|
|
479
|
+
/**
|
|
480
|
+
* The `AKM_BUNDLE_DIR` an installed row names: its inline one, or the one in
|
|
481
|
+
* the descriptor a row written before 0.9.17-alpha.7 references. Undefined
|
|
482
|
+
* when it names none, or its descriptor cannot be read — which never means
|
|
483
|
+
* "mine".
|
|
484
|
+
*/
|
|
485
|
+
function installedRowBundleDir(row) {
|
|
486
|
+
if (row.contextPath === undefined)
|
|
487
|
+
return row.environment?.AKM_BUNDLE_DIR;
|
|
498
488
|
try {
|
|
499
|
-
return
|
|
489
|
+
return readLegacySchedulerContext(row.contextPath).AKM_BUNDLE_DIR;
|
|
500
490
|
}
|
|
501
491
|
catch {
|
|
502
492
|
return undefined;
|
|
@@ -515,26 +505,34 @@ function installedSchedulerBundle(config, row) {
|
|
|
515
505
|
return bundleKeyForContentRoot(config, row.ownerBundlePath);
|
|
516
506
|
}
|
|
517
507
|
/**
|
|
518
|
-
* Why `akm task prune` (#851) would remove an installed row:
|
|
519
|
-
* descriptor
|
|
520
|
-
* that is gone (`dead-bundle-path`). A row
|
|
521
|
-
* bundle is never a candidate.
|
|
508
|
+
* Why `akm task prune` (#851) would remove an installed row: a row written
|
|
509
|
+
* before 0.9.17-alpha.7 whose descriptor cannot be read (`invalid-context`),
|
|
510
|
+
* or one naming a bundle directory that is gone (`dead-bundle-path`). A row
|
|
511
|
+
* that still resolves to a live bundle is never a candidate.
|
|
522
512
|
*/
|
|
523
513
|
function classifyPruneCandidate(entry) {
|
|
524
|
-
let
|
|
525
|
-
|
|
526
|
-
|
|
514
|
+
let bundleDir;
|
|
515
|
+
if (entry.contextPath !== undefined) {
|
|
516
|
+
try {
|
|
517
|
+
bundleDir = readLegacySchedulerContext(entry.contextPath).AKM_BUNDLE_DIR;
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
return "invalid-context";
|
|
521
|
+
}
|
|
522
|
+
// Every descriptor akm wrote names the bundle; one that does not cannot be attributed.
|
|
523
|
+
if (bundleDir === undefined)
|
|
524
|
+
return "invalid-context";
|
|
527
525
|
}
|
|
528
|
-
|
|
529
|
-
|
|
526
|
+
else {
|
|
527
|
+
bundleDir = entry.environment?.AKM_BUNDLE_DIR;
|
|
530
528
|
}
|
|
531
|
-
if (
|
|
529
|
+
if (bundleDir !== undefined && !fs.existsSync(bundleDir))
|
|
532
530
|
return "dead-bundle-path";
|
|
533
531
|
return undefined;
|
|
534
532
|
}
|
|
535
533
|
/**
|
|
536
534
|
* `akm task prune` (#851): remove installed rows `sync` can never reclaim
|
|
537
|
-
* because
|
|
535
|
+
* because they no longer resolve to a live bundle. It scans
|
|
538
536
|
* every installed row, not one bundle's. Without `--yes` it only previews and
|
|
539
537
|
* writes nothing; `--id` narrows it to named candidates.
|
|
540
538
|
*/
|
|
@@ -655,7 +653,7 @@ function groupInstalledBindings(entries) {
|
|
|
655
653
|
}
|
|
656
654
|
groups.set(key, {
|
|
657
655
|
argv,
|
|
658
|
-
contextPath: entry.contextPath,
|
|
656
|
+
...(entry.contextPath !== undefined ? { contextPath: entry.contextPath } : {}),
|
|
659
657
|
taskIds: [entry.id],
|
|
660
658
|
status,
|
|
661
659
|
});
|
|
@@ -669,13 +667,15 @@ function inspectInstalledBinding(entry) {
|
|
|
669
667
|
status.push("checkout");
|
|
670
668
|
if (binding.some((part) => part === "akm" || part === "bun" || part === "node"))
|
|
671
669
|
status.push("path-selected");
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
670
|
+
if (entry.contextPath !== undefined) {
|
|
671
|
+
try {
|
|
672
|
+
readLegacySchedulerContext(entry.contextPath);
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
status.push("invalid-context");
|
|
676
|
+
}
|
|
677
677
|
}
|
|
678
|
-
const absolutePaths =
|
|
678
|
+
const absolutePaths = binding.filter((part) => path.isAbsolute(part));
|
|
679
679
|
if (absolutePaths.some((part) => !fs.existsSync(part)))
|
|
680
680
|
status.push("missing-path");
|
|
681
681
|
if (status.length === 0)
|
|
@@ -734,8 +734,9 @@ export function resolveTaskReadBundle(refBundle, flagBundle) {
|
|
|
734
734
|
* New scheduler bindings always carry a canonical `--bundle <owner>` token,
|
|
735
735
|
* including an env-selected working stash. That stash need not be persisted in
|
|
736
736
|
* config (CI, one-shot tools, and fresh installs commonly use only
|
|
737
|
-
* AKM_BUNDLE_DIR), so its scheduled child
|
|
738
|
-
*
|
|
737
|
+
* AKM_BUNDLE_DIR), so its scheduled child — every row sets AKM_BUNDLE_DIR to
|
|
738
|
+
* the working stash it was synced from — must accept precisely its derived
|
|
739
|
+
* owner name.
|
|
739
740
|
*
|
|
740
741
|
* This is intentionally narrower than an unknown-bundle fallback: a configured
|
|
741
742
|
* source always wins, and an unconfigured selector is accepted only when it is
|
|
@@ -9,23 +9,20 @@
|
|
|
9
9
|
* a bundle/adapter/concept id at all — it reads exactly the path it was
|
|
10
10
|
* given and classifies it.
|
|
11
11
|
*
|
|
12
|
-
* Reuses the exact version
|
|
13
|
-
* (`src/tasks/source/parse-task-source.ts`)
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the report, because `parseTaskSource`'s own `ParsedTaskSource.version` is
|
|
20
|
-
* always `4` post-shim — it cannot answer "was this a v2/v3/v4 file?" on
|
|
21
|
-
* its own once a v2/v3 source has been converted in memory.
|
|
12
|
+
* Reuses the exact version router `parseTaskSource`
|
|
13
|
+
* (`src/tasks/source/parse-task-source.ts`) every other task-source reader
|
|
14
|
+
* uses (`akm task sync`'s `compileTaskSources` included) — this module never
|
|
15
|
+
* forks a second parser. `readBoundedTaskSourceYaml` /
|
|
16
|
+
* `peekTaskSourceVersion` / `own` are the SAME front-end helpers the router
|
|
17
|
+
* calls first; they are used here only to recover the file's DECLARED schema
|
|
18
|
+
* version for the report and to tell `blocked` from `invalid`.
|
|
22
19
|
*
|
|
23
20
|
* Beyond parsing, this module also runs the SAME two per-source gates
|
|
24
21
|
* `akm task sync`'s `compileTaskSources` runs before it ever installs a
|
|
25
22
|
* schedule — `assertTaskScheduleInputsSatisfyContract` and
|
|
26
23
|
* `assertTaskScheduleCronValid` (both extracted from `scheduler-sync.ts` for
|
|
27
24
|
* exactly this reuse) — so a file `sync` would reject can never
|
|
28
|
-
* be reported `valid
|
|
25
|
+
* be reported `valid` here. Cron dialect is checked against
|
|
29
26
|
* `backendNameForPlatform()`, the same platform default `sync` falls back
|
|
30
27
|
* to whenever it has no injected/native-inspected backend to hand (see
|
|
31
28
|
* `akmTasksAdd`, `src/commands/tasks/tasks.ts`); a bare file was never
|
|
@@ -46,27 +43,18 @@
|
|
|
46
43
|
*
|
|
47
44
|
* Outcome classification (mirrors `parse-task-source.ts`'s own routing
|
|
48
45
|
* table in its header, extended for the two gates above):
|
|
49
|
-
* - `valid` — parses as task source v4
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* `schedule[].enabled` key also reports `converts`
|
|
56
|
-
* (`sourceVersion` is still `4`) — it read through the
|
|
57
|
-
* same in-memory shim, not the direct v4 parse path.
|
|
58
|
-
* - `blocked` — declared `version: 2` or `3`; the migrator itself
|
|
59
|
-
* could not convert it (an ambiguous/unmigratable
|
|
60
|
-
* shape) — the ONLY way `parseTaskSource` ever throws
|
|
61
|
-
* for those two version numbers, so no message-text
|
|
62
|
-
* sniffing is needed to tell this apart from `invalid`.
|
|
46
|
+
* - `valid` — parses as task source v4 and passes both sync gates.
|
|
47
|
+
* - `blocked` — declared `version: 2` or `3`, or a `version: 4`
|
|
48
|
+
* document still carrying the retired
|
|
49
|
+
* `schedule[].enabled`: what `akm migrate apply`
|
|
50
|
+
* rewrites. The runtime reads only v4 (#987), so the
|
|
51
|
+
* reason names that command.
|
|
63
52
|
* - `invalid` — the document declares SOME version (`4`, or anything
|
|
64
53
|
* other than 2/3/4) but fails to parse/validate, OR it
|
|
65
|
-
* parsed
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* shape) — reported with the parser's own reason.
|
|
54
|
+
* parsed but fails one of the two sync gates above, OR
|
|
55
|
+
* the YAML itself does not parse at all (a genuine
|
|
56
|
+
* syntax error, not merely a non-task shape) — reported
|
|
57
|
+
* with the parser's own reason.
|
|
70
58
|
* - `not-a-task` — the document parses as YAML but never declares a
|
|
71
59
|
* `version:` field at all (or isn't a YAML mapping) —
|
|
72
60
|
* the strongest signal available that the file was
|
|
@@ -142,10 +130,11 @@ export async function akmTaskValidate(filePath) {
|
|
|
142
130
|
if (!(cause instanceof UsageError))
|
|
143
131
|
throw cause;
|
|
144
132
|
const reason = cause.message;
|
|
145
|
-
// `
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
133
|
+
// What `akm migrate apply` rewrites is the only refusal for these
|
|
134
|
+
// shapes (`parse-task-source.ts`), so no message-text sniffing is needed.
|
|
135
|
+
if (declaredVersion === 2 ||
|
|
136
|
+
declaredVersion === 3 ||
|
|
137
|
+
(declaredVersion === 4 && v4ScheduleHasRetiredEnabledKey(root))) {
|
|
149
138
|
return { ok: false, path: resolvedPath, sourceVersion: declaredVersion, outcome: "blocked", reason };
|
|
150
139
|
}
|
|
151
140
|
if (peekFailed) {
|
|
@@ -163,13 +152,10 @@ export async function akmTaskValidate(filePath) {
|
|
|
163
152
|
};
|
|
164
153
|
}
|
|
165
154
|
// Success is unreachable from any path that leaves `declaredVersion`
|
|
166
|
-
// undefined — the router requires a numeric
|
|
155
|
+
// undefined — the router requires a numeric version 4 to reach here.
|
|
167
156
|
const sourceVersion = declaredVersion ?? 4;
|
|
168
|
-
// The document itself parsed
|
|
169
|
-
//
|
|
170
|
-
// accepting it. A violation here is `invalid`, never `blocked`: the
|
|
171
|
-
// migrator already succeeded, so this is the same kind of defect a
|
|
172
|
-
// native v4 document with the identical schedule would have.
|
|
157
|
+
// The document itself parsed — now the two gates `compileTaskSources`
|
|
158
|
+
// runs before accepting it. A violation here is `invalid`.
|
|
173
159
|
try {
|
|
174
160
|
assertTaskScheduleInputsSatisfyContract(parsed.v4, resolvedPath);
|
|
175
161
|
assertTaskScheduleCronValid(parsed.v4, backend);
|
|
@@ -179,17 +165,12 @@ export async function akmTaskValidate(filePath) {
|
|
|
179
165
|
throw cause;
|
|
180
166
|
return { ok: false, path: resolvedPath, sourceVersion, outcome: "invalid", reason: cause.message };
|
|
181
167
|
}
|
|
182
|
-
// A declared `version: 4` document with a retired `schedule[].enabled`
|
|
183
|
-
// key parsed through `parseTaskSource`'s in-memory shim, not the direct
|
|
184
|
-
// v4 path, even though `sourceVersion` reads `4` — report it the same
|
|
185
|
-
// way a converted v2/v3 document is reported.
|
|
186
|
-
const convertedFromRetiredScheduleEnabled = sourceVersion === 4 && !peekFailed && v4ScheduleHasRetiredEnabledKey(root);
|
|
187
168
|
const id = path.parse(resolvedPath).name;
|
|
188
169
|
return {
|
|
189
170
|
ok: true,
|
|
190
171
|
path: resolvedPath,
|
|
191
172
|
sourceVersion,
|
|
192
|
-
outcome:
|
|
173
|
+
outcome: "valid",
|
|
193
174
|
resolved: buildResolved(id, parsed.v4),
|
|
194
175
|
};
|
|
195
176
|
}
|
|
@@ -17,11 +17,9 @@
|
|
|
17
17
|
* ── validate (spec §6 task validation column) ──
|
|
18
18
|
*
|
|
19
19
|
* Validation enters the canonical task source parser (`parseTaskSource`,
|
|
20
|
-
* task source v4
|
|
21
|
-
*
|
|
22
|
-
* `
|
|
23
|
-
* cannot convert, or any other unsupported number, fails closed with
|
|
24
|
-
* `TASK_SCHEMA_VERSION_UNSUPPORTED`). That parser owns the closed key sets,
|
|
20
|
+
* which reads only task source v4 — a `version: 2` or `version: 3`
|
|
21
|
+
* document fails closed with `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming
|
|
22
|
+
* `akm migrate apply`, which converts it). That parser owns the closed key sets,
|
|
25
23
|
* the executable-selector XOR, hostile YAML policy, the `akm/command`
|
|
26
24
|
* builtin, bounds, and physical `working-directory` containment. The
|
|
27
25
|
* adapter only translates a parser failure into the format-family
|
|
@@ -33,7 +31,8 @@
|
|
|
33
31
|
*/
|
|
34
32
|
import fs from "node:fs";
|
|
35
33
|
import path from "node:path";
|
|
36
|
-
import {
|
|
34
|
+
import { readBoundedTaskSourceYaml } from "../../../tasks/source/bounded-document.js";
|
|
35
|
+
import { parseTaskSource, peekTaskSourceVersion } from "../../../tasks/source/parse-task-source.js";
|
|
37
36
|
import { TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskSourceErrorDetail, } from "../../../tasks/source-v3.js";
|
|
38
37
|
import { toPosix } from "../../common.js";
|
|
39
38
|
import { hashContent } from "./shared.js";
|
|
@@ -125,8 +124,11 @@ export const akmTaskAdapter = {
|
|
|
125
124
|
return ["."];
|
|
126
125
|
},
|
|
127
126
|
/**
|
|
128
|
-
* Install-time probe (§1.2): a root holding a top-level, valid task
|
|
129
|
-
* `.yml` file
|
|
127
|
+
* Install-time probe (§1.2): a root holding a top-level, valid task source
|
|
128
|
+
* v4 `.yml` file, or a task v2/v3 file that `akm migrate apply` has yet to
|
|
129
|
+
* convert (the runtime refuses those, so without this the migrator and
|
|
130
|
+
* `akm task sync` would not find them in a bundle whose adapter config does
|
|
131
|
+
* not record). The full parser keeps this disjoint from unrelated YAML and
|
|
130
132
|
* prevents probe semantics from drifting from validation semantics.
|
|
131
133
|
*/
|
|
132
134
|
looksLikeRoot(root) {
|
|
@@ -152,9 +154,28 @@ export const akmTaskAdapter = {
|
|
|
152
154
|
return true;
|
|
153
155
|
}
|
|
154
156
|
catch {
|
|
157
|
+
if (isLegacyTaskDocument(raw, entry.name))
|
|
158
|
+
return true;
|
|
155
159
|
// Continue probing the remaining top-level .yml files.
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
return false;
|
|
159
163
|
},
|
|
160
164
|
};
|
|
165
|
+
/** The executable keys each retired task schema version required one of. */
|
|
166
|
+
const LEGACY_TASK_TARGET_KEYS = {
|
|
167
|
+
2: ["workflow", "prompt", "command"],
|
|
168
|
+
3: ["uses", "run"],
|
|
169
|
+
};
|
|
170
|
+
/** A task v2/v3 document: its schema version, plus a target key that version used. */
|
|
171
|
+
function isLegacyTaskDocument(yaml, filePath) {
|
|
172
|
+
let root;
|
|
173
|
+
try {
|
|
174
|
+
root = readBoundedTaskSourceYaml({ yaml, filePath }, { sourceLabel: "task source" }).root;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
const keys = LEGACY_TASK_TARGET_KEYS[peekTaskSourceVersion(root) ?? 0];
|
|
180
|
+
return keys !== undefined && keys.some((key) => Object.hasOwn(root, key));
|
|
181
|
+
}
|
|
@@ -33,7 +33,7 @@ export { FEEDBACK_FAILURE_MODES } from "./config-schema.js";
|
|
|
33
33
|
* combined prompt size well under common 8K/16K context windows (each body is
|
|
34
34
|
* sliced to ~500 chars in the graph-extract prompt builder).
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
const DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE = 4;
|
|
37
37
|
/**
|
|
38
38
|
* Approximate character budget per asset body inside a batched
|
|
39
39
|
* graph-extraction prompt — used by {@link resolveBatchSize} to derive a
|
|
@@ -183,9 +183,10 @@ const MEMORY_INFERENCE_PROCESS_FIELDS = {
|
|
|
183
183
|
cls: clsField,
|
|
184
184
|
};
|
|
185
185
|
/**
|
|
186
|
-
* GraphExtraction process fields:
|
|
187
|
-
* batching.
|
|
188
|
-
*
|
|
186
|
+
* GraphExtraction process fields: one strategy's graph extraction scope and
|
|
187
|
+
* batching. `includeTypes` and `batchSize` override `index.graph`'s
|
|
188
|
+
* `graphExtractionIncludeTypes` and `graphExtractionBatchSize`; unset, the
|
|
189
|
+
* pass reads those.
|
|
189
190
|
*/
|
|
190
191
|
const GRAPH_EXTRACTION_PROCESS_FIELDS = {
|
|
191
192
|
// #624 P2: when set, rank eligible files by utility_scores DESC and process
|
|
@@ -32,22 +32,11 @@ const INDEX_PASS_RETIRED_KEYS = new Set([
|
|
|
32
32
|
"maxTokens",
|
|
33
33
|
"capabilities",
|
|
34
34
|
]);
|
|
35
|
-
const INDEX_PASS_KNOWN_KEYS = new Set([
|
|
36
|
-
"engine",
|
|
37
|
-
"model",
|
|
38
|
-
"timeoutMs",
|
|
39
|
-
"enabled",
|
|
40
|
-
"llm",
|
|
41
|
-
"graphExtractionBatchSize",
|
|
42
|
-
"graphExtractionIncludeTypes",
|
|
43
|
-
"lazyGraphExtraction",
|
|
44
|
-
]);
|
|
45
35
|
/**
|
|
46
|
-
* Per-pass `index.<pass>` entry.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* string` strings — keeps `akm` startup errors actionable.
|
|
36
|
+
* Per-pass `index.<pass>` entry. The preprocess names and drops the retired
|
|
37
|
+
* engine settings above with a targeted message. Any other unknown key is kept
|
|
38
|
+
* and named once by the config loader's schema walk, like an unknown key
|
|
39
|
+
* anywhere else in config.
|
|
51
40
|
*/
|
|
52
41
|
export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
|
|
53
42
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
@@ -62,13 +51,6 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
|
|
|
62
51
|
cleaned ??= { ...obj };
|
|
63
52
|
delete cleaned[key];
|
|
64
53
|
}
|
|
65
|
-
else if (!INDEX_PASS_KNOWN_KEYS.has(key)) {
|
|
66
|
-
warnOnce(`index-pass:unknown:${dotted}`, `Unknown key \`${dotted}\` ignored. Per-pass entries support ` +
|
|
67
|
-
"`engine`, `model`, `timeoutMs`, `enabled`, `llm`, `graphExtractionBatchSize`, " +
|
|
68
|
-
"`graphExtractionIncludeTypes`, and `lazyGraphExtraction`.");
|
|
69
|
-
cleaned ??= { ...obj };
|
|
70
|
-
delete cleaned[key];
|
|
71
|
-
}
|
|
72
54
|
}
|
|
73
55
|
return cleaned ?? raw;
|
|
74
56
|
}, z
|
|
@@ -81,7 +63,6 @@ export const IndexPassConfigSchema = z.preprocess((raw, ctx) => {
|
|
|
81
63
|
graphExtractionBatchSize: positiveInt.optional(),
|
|
82
64
|
// Accept-any until Chunk 2 (WI-9.6c) — no longer enum-restricted.
|
|
83
65
|
graphExtractionIncludeTypes: z.array(z.string().min(1)).nonempty().optional(),
|
|
84
|
-
lazyGraphExtraction: z.boolean().optional(),
|
|
85
66
|
})
|
|
86
67
|
.passthrough());
|
|
87
68
|
const MetadataEnhanceSchema = z.object({ enabled: z.boolean().optional() }).passthrough();
|
|
@@ -306,6 +306,8 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
|
|
|
306
306
|
fail("plan.limits.totalCeiling must equal plan.limits.effective + plan.limits.additiveReplayAllowance");
|
|
307
307
|
}
|
|
308
308
|
const gateNames = new Set(["profile", "cleanup", "validation", "signal", "disk", "limit"]);
|
|
309
|
+
// Plans stored before 0.9.17-alpha.7 (#986) have no retrieval gate.
|
|
310
|
+
const optionalGateNames = new Set(["retrieval"]);
|
|
309
311
|
if (!Array.isArray(value.gates))
|
|
310
312
|
fail("plan.gates must be an array");
|
|
311
313
|
const gateRemovedByName = new Map();
|
|
@@ -313,8 +315,9 @@ function validateImprovePlan(value, dryRun, plannedRefNames) {
|
|
|
313
315
|
if (!isRecord(gate))
|
|
314
316
|
fail("plan.gates entries must be objects");
|
|
315
317
|
requireExactFields(gate, new Set(["name", "removed", "reason"]));
|
|
316
|
-
if (typeof gate.name !== "string" || !gateNames.has(gate.name))
|
|
318
|
+
if (typeof gate.name !== "string" || !(gateNames.has(gate.name) || optionalGateNames.has(gate.name))) {
|
|
317
319
|
fail("plan.gates.name is invalid");
|
|
320
|
+
}
|
|
318
321
|
requireCount(gate, "removed", "plan.gates entry");
|
|
319
322
|
if (typeof gate.reason !== "string")
|
|
320
323
|
fail("plan.gates.reason must be a string");
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
/** The line of `src/assets/stash-skeleton/README.md` that reaches curate verbatim as a query. */
|
|
5
|
+
const STASH_README_LINE = "This is an **AKM stash** — a structured knowledge repository that stores reusable";
|
|
6
|
+
/**
|
|
7
|
+
* What the (trimmed) curate input is when it is not a task, else undefined.
|
|
8
|
+
* Harness and tool envelopes (`<task-notification>…`, `<system-reminder>…`,
|
|
9
|
+
* `<cross-session-message …>…`) start with a tag and close one, and the stash
|
|
10
|
+
* README line arrives verbatim; on the retrieval suite neither shape occurs in
|
|
11
|
+
* a real query. Length is not a signal: prompts over 2,000 characters found
|
|
12
|
+
* relevant assets at about the rate of shorter long prompts.
|
|
13
|
+
*/
|
|
14
|
+
export function nonTaskInput(query) {
|
|
15
|
+
if (query.startsWith("<") && query.includes("</"))
|
|
16
|
+
return "a harness or tool envelope";
|
|
17
|
+
if (query === STASH_README_LINE)
|
|
18
|
+
return "the akm stash README boilerplate";
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
package/dist/core/paths.js
CHANGED
|
@@ -249,10 +249,6 @@ export function getIndexRebuildLockPath() {
|
|
|
249
249
|
export function getStateDbPathInDataDir() {
|
|
250
250
|
return path.join(getDataDir(), "state.db");
|
|
251
251
|
}
|
|
252
|
-
/** Content-addressed scheduler runtime descriptors. */
|
|
253
|
-
export function getTaskContextDir(env = process.env) {
|
|
254
|
-
return path.join(getDataDir(env), "tasks", "context");
|
|
255
|
-
}
|
|
256
252
|
/** Path to the akm.lock file in $DATA. */
|
|
257
253
|
export function getLockfilePath() {
|
|
258
254
|
return path.join(getDataDir(), "akm.lock");
|