akm-cli 0.9.0-rc.13 → 0.9.0-rc.15
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 +162 -23
- package/dist/akm-migrate +3 -1
- package/dist/assets/hints/cli-hints-full.md +3 -4
- package/dist/assets/hints/cli-hints-short.md +5 -5
- package/dist/assets/workflows/workflow-template.md +4 -3
- package/dist/cli/invocation.js +3 -2
- package/dist/cli/retired-commands.js +5 -0
- package/dist/cli/unknown-flags.js +226 -0
- package/dist/cli.js +19 -35
- package/dist/commands/agent/agent-dispatch.js +7 -3
- package/dist/commands/agent/contribute-cli.js +15 -3
- package/dist/commands/feedback-cli.js +6 -3
- package/dist/commands/health/checks.js +6 -3
- package/dist/commands/improve/collapse-detector.js +2 -3
- package/dist/commands/improve/reflect.js +5 -3
- package/dist/commands/lint/base-linter.js +4 -16
- package/dist/commands/lint/index.js +13 -13
- package/dist/commands/log.js +6 -1
- package/dist/commands/migration-tool.js +4 -5
- package/dist/commands/observability-cli.js +1 -1
- package/dist/commands/proposal/propose.js +5 -3
- package/dist/commands/proposal/repository.js +5 -5
- package/dist/commands/read/knowledge.js +2 -0
- package/dist/commands/registry-cli.js +5 -3
- package/dist/commands/sources/add-cli.js +6 -6
- package/dist/commands/sources/init.js +1 -1
- package/dist/commands/sources/self-update.js +30 -7
- package/dist/commands/sources/source-add.js +17 -2
- package/dist/commands/tasks/tasks.js +10 -10
- package/dist/commands/workflow-cli.js +133 -259
- package/dist/core/adapter/adapters/akm-lint.js +17 -13
- package/dist/core/adapter/adapters/akm-task-adapter.js +14 -11
- package/dist/core/asset/akm-markdown.js +41 -8
- package/dist/core/asset/frontmatter.js +22 -0
- package/dist/core/asset/resolve-ref.js +23 -3
- package/dist/core/common.js +45 -2
- package/dist/core/config/config-schema.js +8 -0
- package/dist/core/config/schema/experimental.js +0 -17
- package/dist/core/config/schema/sources-bundles.js +11 -0
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/errors.js +5 -1
- package/dist/core/logs-db.js +2 -1
- package/dist/core/parse.js +4 -1
- package/dist/core/state/migrations.js +11 -14
- package/dist/core/state-db.js +4 -6
- package/dist/core/subprocess.js +6 -4
- package/dist/core/type-presentation.js +1 -1
- package/dist/indexer/indexer.js +16 -1
- package/dist/indexer/search/search-source.js +1 -1
- package/dist/indexer/walk/matchers.js +3 -1
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/spawn.js +1 -2
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/lockfile.js +11 -56
- package/dist/output/shapes/passthrough.js +0 -6
- package/dist/output/text/command-format.js +3 -3
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/show-directives.js +4 -6
- package/dist/output/text/workflow-format.js +15 -181
- package/dist/output/text/workflow.js +1 -6
- package/dist/scripts/akm-migrate-node.js +58666 -0
- package/dist/scripts/akm-migrate.js +33976 -11498
- package/dist/setup/detect.js +42 -15
- package/dist/setup/setup.js +1 -1
- package/dist/sources/providers/git-stash.js +4 -2
- package/dist/sources/providers/website.js +5 -0
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +370 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +40 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +199 -0
- package/dist/sources/snapshot-fetchers/registry.js +10 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +279 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +488 -257
- package/dist/sources/snapshot-fetchers/x.js +193 -0
- package/dist/storage/engines/sqlite-migrations.js +22 -107
- package/dist/storage/repositories/workflow-runs-repository.js +9 -23
- package/dist/tasks/runner.js +43 -19
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/validator.js +6 -2
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +44 -28
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +122 -60
- package/dist/workflows/exec/step-work.js +64 -175
- package/dist/workflows/exec/unit-dispatch.js +4 -0
- package/dist/workflows/ir/compile.js +2 -2
- package/dist/workflows/ir/freeze.js +29 -9
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +6 -2
- package/dist/workflows/renderer.js +2 -2
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/runs.js +72 -92
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/validate-summary.js +30 -14
- package/docs/migration/release-notes/0.9.0.md +31 -4
- package/docs/migration/v0.8-to-v0.9.md +70 -100
- package/docs/reference/data-and-telemetry.md +5 -5
- package/package.json +7 -3
- package/schemas/akm-config.json +24 -6
- package/schemas/akm-workflow.json +1 -1
- package/dist/workflows/cli.js +0 -33
- package/dist/workflows/exec/brief.js +0 -450
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/workflow-engine-gate.js +0 -67
|
@@ -52,8 +52,10 @@
|
|
|
52
52
|
*/
|
|
53
53
|
import path from "node:path";
|
|
54
54
|
import { isDangerousEnvKey } from "../../../commands/lint/env-key-rules.js";
|
|
55
|
+
import { taskFieldProblems } from "../../../tasks/schema.js";
|
|
55
56
|
import { compileWorkflowPlan } from "../../../workflows/ir/compile.js";
|
|
56
57
|
import { parseWorkflow } from "../../../workflows/parser.js";
|
|
58
|
+
import { conceptIdForStashFile } from "../../asset/resolve-ref.js";
|
|
57
59
|
/** Recommended `category` values for facts — `commands/lint/fact-linter.ts:9`. */
|
|
58
60
|
const KNOWN_CATEGORIES = new Set(["personal", "team", "project", "convention", "meta"]);
|
|
59
61
|
/** Placeholder markers a workflow stub carries — `commands/lint/workflow-linter.ts:10`. */
|
|
@@ -152,8 +154,12 @@ function collectSuppressedKeys(raw) {
|
|
|
152
154
|
/**
|
|
153
155
|
* env/secret dangerous-key scan (`lint/index.ts:191-218` + `env-key-rules.ts#checkEnvForDangerousKeys`),
|
|
154
156
|
* keyed on `type` and preserving the `.env`-suffix narrowness (see file header).
|
|
155
|
-
* Reads the overlay `raw`, not disk.
|
|
156
|
-
*
|
|
157
|
+
* Reads the overlay `raw`, not disk.
|
|
158
|
+
*
|
|
159
|
+
* The emitted `Ref:` comes from `conceptIdForStashFile` — the one place that
|
|
160
|
+
* spells a diagnostic ref the way `akm show` accepts it. It used to be
|
|
161
|
+
* hand-built as `env:<base>` / `secret:<base>`, a colon grammar the 0.9.0 ref
|
|
162
|
+
* parser rejects outright — a dead-end ref on a *security* finding.
|
|
157
163
|
*/
|
|
158
164
|
export function dangerousEnvKeyDiagnostics(type, relPath, raw) {
|
|
159
165
|
if (type !== "env" && type !== "secret")
|
|
@@ -161,9 +167,8 @@ export function dangerousEnvKeyDiagnostics(type, relPath, raw) {
|
|
|
161
167
|
const baseNameWithExt = path.basename(relPath);
|
|
162
168
|
if (!baseNameWithExt.endsWith(".env"))
|
|
163
169
|
return []; // NARROWNESS: collectEnvFiles only visits *.env
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
const ref = baseName === "" ? `${refPrefix}:.env` : `${refPrefix}:${baseName}`;
|
|
170
|
+
// `relPath` is already stash-root-relative, so "." IS the stash root here.
|
|
171
|
+
const ref = conceptIdForStashFile(type, ".", relPath);
|
|
167
172
|
const keys = scanKeys(raw);
|
|
168
173
|
const suppressed = collectSuppressedKeys(raw);
|
|
169
174
|
const diagnostics = [];
|
|
@@ -259,17 +264,16 @@ export function factDiagnostics(relPath, data) {
|
|
|
259
264
|
}
|
|
260
265
|
return [];
|
|
261
266
|
}
|
|
262
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* TaskLinter extra check (`task-linter.ts:25-58`). `data` is the parsed YAML.
|
|
269
|
+
* Field rules come from the shared {@link taskFieldProblems} (see its doc for
|
|
270
|
+
* the lint-vs-parser reconciliation story); this sweep additionally requires
|
|
271
|
+
* at least one target.
|
|
272
|
+
*/
|
|
263
273
|
export function taskDiagnostics(relPath, data) {
|
|
264
274
|
if (data === null || Object.keys(data).length === 0)
|
|
265
275
|
return [];
|
|
266
|
-
const missing =
|
|
267
|
-
if (!("schedule" in data) || typeof data.schedule !== "string" || data.schedule.trim() === "") {
|
|
268
|
-
missing.push("schedule");
|
|
269
|
-
}
|
|
270
|
-
if (!("enabled" in data) || typeof data.enabled !== "boolean") {
|
|
271
|
-
missing.push("enabled (must be a boolean)");
|
|
272
|
-
}
|
|
276
|
+
const missing = taskFieldProblems(data);
|
|
273
277
|
const hasTarget = "prompt" in data || "workflow" in data || "command" in data;
|
|
274
278
|
if (!hasTarget)
|
|
275
279
|
missing.push("prompt, workflow, or command");
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
*
|
|
13
13
|
* ── validate (spec §6 task validation column) ──
|
|
14
14
|
*
|
|
15
|
-
* A task must declare a `schedule`,
|
|
16
|
-
*
|
|
17
|
-
* `
|
|
18
|
-
*
|
|
19
|
-
*
|
|
15
|
+
* A task must declare `version: 2`, a `schedule`, and EXACTLY ONE target
|
|
16
|
+
* (`prompt` XOR `workflow` XOR `command`). `enabled` is OPTIONAL — the parser
|
|
17
|
+
* defaults it to `true`, so an omitted field means an ACTIVE task — but must
|
|
18
|
+
* be a boolean when present. The akm adapter's `TaskLinter` port checks
|
|
19
|
+
* "at least one" target; the native task family here is STRICTER — declaring
|
|
20
|
+
* two targets is `invalid-task-yaml`. So this adapter owns a purpose-built
|
|
21
|
+
* one-target check rather than reusing that port.
|
|
20
22
|
*
|
|
21
23
|
* Conformance oracle (authored, DO NOT modify): fixture
|
|
22
24
|
* `tests/fixtures/bundles/akm-task/` + goldens
|
|
@@ -25,6 +27,7 @@
|
|
|
25
27
|
import fs from "node:fs";
|
|
26
28
|
import path from "node:path";
|
|
27
29
|
import { parse as parseYaml } from "yaml";
|
|
30
|
+
import { taskFieldProblems } from "../../../tasks/schema.js";
|
|
28
31
|
import { hashContent } from "./shared.js";
|
|
29
32
|
/** A native task bundle is single-component; its one component is `main`. */
|
|
30
33
|
const COMPONENT_ID = "main";
|
|
@@ -68,15 +71,15 @@ function parseTaskYaml(raw) {
|
|
|
68
71
|
}
|
|
69
72
|
return {};
|
|
70
73
|
}
|
|
71
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The native `invalid-task-yaml` check: the shared field rules
|
|
76
|
+
* ({@link taskFieldProblems} — see its doc for the lint-vs-parser
|
|
77
|
+
* reconciliation story) plus this adapter's stricter EXACTLY-ONE-target rule.
|
|
78
|
+
*/
|
|
72
79
|
function taskDiagnostics(relPath, data) {
|
|
73
80
|
if (Object.keys(data).length === 0)
|
|
74
81
|
return [];
|
|
75
|
-
const problems =
|
|
76
|
-
if (typeof data.schedule !== "string" || data.schedule.trim() === "")
|
|
77
|
-
problems.push("schedule");
|
|
78
|
-
if (typeof data.enabled !== "boolean")
|
|
79
|
-
problems.push("enabled (must be a boolean)");
|
|
82
|
+
const problems = taskFieldProblems(data);
|
|
80
83
|
const targets = TARGET_KEYS.filter((k) => k in data && data[k] !== undefined && data[k] !== null);
|
|
81
84
|
if (targets.length === 0)
|
|
82
85
|
problems.push("exactly one target (prompt, workflow, or command)");
|
|
@@ -2,14 +2,36 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
import { parse as parseYaml } from "yaml";
|
|
5
|
+
import { localDateStamp } from "../common.js";
|
|
5
6
|
import { UsageError } from "../errors.js";
|
|
6
7
|
import { serializeFrontmatter } from "./asset-serialize.js";
|
|
7
|
-
import { parseFrontmatterBlock } from "./frontmatter.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
import { parseFrontmatterBlock, spliceFrontmatterLine } from "./frontmatter.js";
|
|
9
|
+
/**
|
|
10
|
+
* Ensure an AKM-authored Markdown concept is also a conformant OKF concept.
|
|
11
|
+
*
|
|
12
|
+
* Stamps BOTH `type` and `updated`, because both are required of a conformant
|
|
13
|
+
* document and this is the one chokepoint every `.md` write passes through
|
|
14
|
+
* (`core/write-source.ts`). Without the `updated` stamp, every asset akm
|
|
15
|
+
* created for you — `akm remember`, `akm import`, accepted proposals,
|
|
16
|
+
* authored workflows — was immediately flagged `missing-updated` by akm's own
|
|
17
|
+
* `akm lint`, so the tool disagreed with itself about its own output.
|
|
18
|
+
*
|
|
19
|
+
* An existing `updated` is left alone: this fills a gap, it does not
|
|
20
|
+
* re-stamp on every write (which would churn timestamps and manufacture
|
|
21
|
+
* needless diffs in git-backed bundles).
|
|
22
|
+
*
|
|
23
|
+
* Source preservation: when the type already matches and the ONLY change is
|
|
24
|
+
* adding `updated`, the line is spliced into the original block textually —
|
|
25
|
+
* round-tripping through the YAML serializer would drop user-authored
|
|
26
|
+
* comments and normalize formatting just to contribute one field. Only a
|
|
27
|
+
* document whose `type` must actually be corrected takes the re-serialize
|
|
28
|
+
* path (as it always has).
|
|
29
|
+
*/
|
|
30
|
+
export function ensureAkmMarkdownType(content, type, now = new Date()) {
|
|
10
31
|
const block = parseFrontmatterBlock(content);
|
|
11
|
-
if (!block)
|
|
12
|
-
return `---\
|
|
32
|
+
if (!block) {
|
|
33
|
+
return `---\n${serializeFrontmatter({ type, updated: localDateStamp(now) })}\n---\n${content}`;
|
|
34
|
+
}
|
|
13
35
|
let parsed;
|
|
14
36
|
try {
|
|
15
37
|
parsed = block.frontmatter.trim() ? parseYaml(block.frontmatter) : {};
|
|
@@ -23,8 +45,19 @@ export function ensureAkmMarkdownType(content, type) {
|
|
|
23
45
|
throw new UsageError("AKM Markdown frontmatter must be a YAML mapping.", "INVALID_FLAG_VALUE");
|
|
24
46
|
}
|
|
25
47
|
const data = parsed;
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
const needsUpdated = !("updated" in data);
|
|
49
|
+
if (data.type === type) {
|
|
50
|
+
if (!needsUpdated)
|
|
51
|
+
return content;
|
|
52
|
+
const spliced = spliceFrontmatterLine(content, `updated: ${localDateStamp(now)}`);
|
|
53
|
+
if (spliced !== null)
|
|
54
|
+
return spliced;
|
|
55
|
+
// Unreachable in practice (parseFrontmatterBlock succeeded above), but a
|
|
56
|
+
// re-serialized document beats a non-conformant one.
|
|
57
|
+
}
|
|
28
58
|
const { type: _priorType, ...rest } = data;
|
|
29
|
-
|
|
59
|
+
const next = { type, ...rest };
|
|
60
|
+
if (needsUpdated)
|
|
61
|
+
next.updated = localDateStamp(now);
|
|
62
|
+
return `---\n${serializeFrontmatter(next)}\n---\n${block.content}`;
|
|
30
63
|
}
|
|
@@ -176,6 +176,28 @@ function countLines(text) {
|
|
|
176
176
|
return 0;
|
|
177
177
|
return text.split(/\r?\n/).length - 1;
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Insert one `key: value` line just before the closing `---` of an existing
|
|
181
|
+
* frontmatter block, leaving every other byte — YAML comments, quoting, key
|
|
182
|
+
* order, line endings — untouched. Returns null when `raw` has no well-formed
|
|
183
|
+
* block, so callers can fall back to a parse-and-serialize path.
|
|
184
|
+
*
|
|
185
|
+
* This is the source-preserving way to ADD a field to user-authored
|
|
186
|
+
* frontmatter: round-tripping the mapping through the YAML serializer drops
|
|
187
|
+
* comments and normalizes formatting, which is unacceptable for a write that
|
|
188
|
+
* only needs to contribute one line. Shared by `ensureAkmMarkdownType`
|
|
189
|
+
* (stamping `updated:` on write) and lint's `--fix` for `missing-updated`.
|
|
190
|
+
*/
|
|
191
|
+
export function spliceFrontmatterLine(raw, line) {
|
|
192
|
+
const lines = raw.split(/\r?\n/);
|
|
193
|
+
if (lines[0]?.trim() !== "---")
|
|
194
|
+
return null;
|
|
195
|
+
const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
196
|
+
if (closeIdx === -1)
|
|
197
|
+
return null;
|
|
198
|
+
lines.splice(closeIdx, 0, line);
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
}
|
|
179
201
|
/**
|
|
180
202
|
* Parse a YAML scalar value (string, boolean, or number).
|
|
181
203
|
*
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* grammar to bridge any more.
|
|
35
35
|
*/
|
|
36
36
|
import { NotFoundError, UsageError } from "../errors.js";
|
|
37
|
-
import { placementSpecFor, stashDirFor, typeForStashDir } from "./asset-placement.js";
|
|
37
|
+
import { deriveCanonicalAssetNameFromStashRoot, placementSpecFor, stashDirFor, typeForStashDir, } from "./asset-placement.js";
|
|
38
38
|
import { isBundleSlug, parseBundleRef } from "./asset-ref.js";
|
|
39
39
|
/**
|
|
40
40
|
* Resolve a maybe-short input ref to a fully-qualified {@link ResolvedRef}
|
|
@@ -97,6 +97,19 @@ export function conceptIdFromTypeName(type, name) {
|
|
|
97
97
|
const stashDir = stashDirFor(type);
|
|
98
98
|
return stashDir !== undefined ? `${stashDir}/${name}` : name;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* User-facing conceptId for a file on disk, derived through the placement
|
|
102
|
+
* spec's canonical-name rule — the ONE way a diagnostic should spell a ref it
|
|
103
|
+
* expects the user to paste into `akm show`. (The dangerous-env-key lint used
|
|
104
|
+
* to hand-build `env:<base>` colon refs the parser rejects; both its emission
|
|
105
|
+
* sites now route through here.) For a type with no placement spec — which no
|
|
106
|
+
* built-in caller passes — falls back to the raw name so the output is still
|
|
107
|
+
* informative rather than empty.
|
|
108
|
+
*/
|
|
109
|
+
export function conceptIdForStashFile(type, stashRoot, filePath) {
|
|
110
|
+
const name = deriveCanonicalAssetNameFromStashRoot(type, stashRoot, filePath);
|
|
111
|
+
return name === undefined ? filePath : conceptIdFromTypeName(type, name);
|
|
112
|
+
}
|
|
100
113
|
/**
|
|
101
114
|
* Build the USER-FACING / envelope ref string for an indexed item, applying the
|
|
102
115
|
* Chunk-5 flip F4b output-spelling rule (orchestrator decision; ref-grammar
|
|
@@ -116,8 +129,15 @@ export function conceptIdFromTypeName(type, name) {
|
|
|
116
129
|
* derived slug bundle id, never the retired `origin//type:name` spelling.
|
|
117
130
|
*/
|
|
118
131
|
export function displayRef(item, defaultBundleId) {
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
return displayRefForConceptId(item.conceptId ?? conceptIdFromTypeName(item.type, item.name), item.bundleId, defaultBundleId);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The F4b output-spelling flip itself, for a caller that already holds the
|
|
136
|
+
* conceptId (no `type`/`name` derivation needed — e.g. lint findings built
|
|
137
|
+
* from {@link conceptIdForStashFile}). {@link displayRef} delegates here, so
|
|
138
|
+
* the short-default / qualified-secondary rule still has exactly one home.
|
|
139
|
+
*/
|
|
140
|
+
export function displayRefForConceptId(conceptId, bundleId, defaultBundleId) {
|
|
121
141
|
// Default/primary bundle → SHORT conceptId (the flip).
|
|
122
142
|
if (bundleId === undefined || bundleId === defaultBundleId)
|
|
123
143
|
return conceptId;
|
package/dist/core/common.js
CHANGED
|
@@ -409,7 +409,7 @@ export async function fetchWithRetry(url, init, options) {
|
|
|
409
409
|
if (attempt < maxRetries && shouldRetry(response.status)) {
|
|
410
410
|
const retryAfter = parseRetryAfter(response);
|
|
411
411
|
const delay = retryAfter ?? baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
|
|
412
|
-
await
|
|
412
|
+
await abortableDelay(delay, init?.signal);
|
|
413
413
|
continue;
|
|
414
414
|
}
|
|
415
415
|
return response;
|
|
@@ -417,12 +417,41 @@ export async function fetchWithRetry(url, init, options) {
|
|
|
417
417
|
catch (err) {
|
|
418
418
|
if (attempt >= maxRetries)
|
|
419
419
|
throw err;
|
|
420
|
+
// A caller-supplied abort is terminal: never keep retrying past it.
|
|
421
|
+
if (init?.signal?.aborted)
|
|
422
|
+
throw err;
|
|
420
423
|
const delay = baseDelay * 2 ** attempt * (0.5 + Math.random() * 0.5);
|
|
421
|
-
await
|
|
424
|
+
await abortableDelay(delay, init?.signal);
|
|
422
425
|
}
|
|
423
426
|
}
|
|
424
427
|
throw new Error("fetchWithRetry: unreachable");
|
|
425
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Sleep, but wake immediately if `signal` aborts.
|
|
431
|
+
*
|
|
432
|
+
* A server-supplied `Retry-After` is honored verbatim and can be arbitrarily
|
|
433
|
+
* large. Sleeping it out with a bare `setTimeout` ignored the caller's abort
|
|
434
|
+
* signal entirely, so a single `429` could park an operation far past any
|
|
435
|
+
* deadline its caller believed it had imposed — the request timeout bounds
|
|
436
|
+
* only the request, never the wait between attempts.
|
|
437
|
+
*/
|
|
438
|
+
function abortableDelay(ms, signal) {
|
|
439
|
+
if (!signal)
|
|
440
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
441
|
+
if (signal.aborted)
|
|
442
|
+
return Promise.reject(signal.reason ?? new Error("Aborted"));
|
|
443
|
+
return new Promise((resolve, reject) => {
|
|
444
|
+
const onAbort = () => {
|
|
445
|
+
clearTimeout(timer);
|
|
446
|
+
reject(signal.reason ?? new Error("Aborted"));
|
|
447
|
+
};
|
|
448
|
+
const timer = setTimeout(() => {
|
|
449
|
+
signal.removeEventListener("abort", onAbort);
|
|
450
|
+
resolve();
|
|
451
|
+
}, ms);
|
|
452
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
453
|
+
});
|
|
454
|
+
}
|
|
426
455
|
function shouldRetry(status) {
|
|
427
456
|
return status === 429 || status >= 500;
|
|
428
457
|
}
|
|
@@ -628,6 +657,20 @@ export function toErrorMessage(error) {
|
|
|
628
657
|
export function todayIso() {
|
|
629
658
|
return new Date().toISOString().slice(0, 10);
|
|
630
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* `YYYY-MM-DD` in LOCAL time — deliberately not {@link todayIso}, which is
|
|
662
|
+
* UTC and can differ near midnight. This is the spelling the `updated:`
|
|
663
|
+
* frontmatter stampers share (`core/asset/akm-markdown.ts` on write,
|
|
664
|
+
* `commands/lint/base-linter.ts` on `--fix`), so the field's format has one
|
|
665
|
+
* definition even though the two stampers pick different instants (now vs
|
|
666
|
+
* file mtime).
|
|
667
|
+
*/
|
|
668
|
+
export function localDateStamp(d) {
|
|
669
|
+
const y = d.getFullYear();
|
|
670
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
671
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
672
|
+
return `${y}-${m}-${day}`;
|
|
673
|
+
}
|
|
631
674
|
/**
|
|
632
675
|
* Return a filesystem-safe timestamp string derived from the current instant.
|
|
633
676
|
* Colons and dots are replaced with hyphens so the result is safe as a
|
|
@@ -234,6 +234,14 @@ export const AkmConfigSchema = AkmConfigBaseSchema.superRefine((config, ctx) =>
|
|
|
234
234
|
message: "llmEngine must name an LLM engine",
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
|
+
const workflowJudge = config.workflow?.judgeEngine;
|
|
238
|
+
if (workflowJudge && !config.engines?.[workflowJudge]) {
|
|
239
|
+
ctx.addIssue({
|
|
240
|
+
code: z.ZodIssueCode.custom,
|
|
241
|
+
path: ["workflow", "judgeEngine"],
|
|
242
|
+
message: "judgeEngine does not name a configured engine",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
237
245
|
const defaultStrategy = config.defaults?.improveStrategy;
|
|
238
246
|
if (defaultStrategy &&
|
|
239
247
|
!BUILTIN_IMPROVE_STRATEGY_NAMES.includes(defaultStrategy) &&
|
|
@@ -26,22 +26,5 @@ export const ExperimentalConfigSchema = z
|
|
|
26
26
|
* purpose, and it has its own `sync.push: false` / `--no-push` controls.
|
|
27
27
|
*/
|
|
28
28
|
improveAutonomy: z.boolean().optional(),
|
|
29
|
-
/**
|
|
30
|
-
* Allow the `akm workflow` native engine to run (Q-05).
|
|
31
|
-
*
|
|
32
|
-
* OFF by default. Gates `akm workflow run` (the native step-execution
|
|
33
|
-
* engine) and `akm workflow brief`/`report` (the harness-neutral driver
|
|
34
|
-
* protocol).
|
|
35
|
-
*
|
|
36
|
-
* Deliberately NOT gated: authoring/linting the unified markdown format
|
|
37
|
-
* and the manual workflow CLI contract (`start`/`next`/`complete`/
|
|
38
|
-
* `status`/`list`/`create`/`resume`/`abandon`) remain stable regardless of
|
|
39
|
-
* this key.
|
|
40
|
-
*
|
|
41
|
-
* Unlike `improveAutonomy`, a gated call here REFUSES outright rather than
|
|
42
|
-
* degrading: a workflow step either executes or it does not, so there is
|
|
43
|
-
* no safe partial-execution fallback to fall back to.
|
|
44
|
-
*/
|
|
45
|
-
workflowEngine: z.boolean().optional(),
|
|
46
29
|
})
|
|
47
30
|
.passthrough();
|
|
@@ -80,6 +80,17 @@ const BundleWebsiteDescriptorSchema = z
|
|
|
80
80
|
refresh: z.string().min(1).optional(),
|
|
81
81
|
maxPages: positiveInt.optional(),
|
|
82
82
|
maxDepth: positiveInt.optional(),
|
|
83
|
+
// Default true: crawl-scoped robots.txt compliance (Disallow/Crawl-delay
|
|
84
|
+
// for the akm/akm-cli product tokens or "*"). See
|
|
85
|
+
// src/sources/snapshot-fetchers/robots.ts. Opt out with `false` to
|
|
86
|
+
// restore pre-P1 behavior exactly (no /robots.txt request at all).
|
|
87
|
+
respectRobots: z.boolean().optional(),
|
|
88
|
+
// Hard wall-clock cap on the whole crawl, in milliseconds. Defaults to
|
|
89
|
+
// 10 minutes. Unlike a between-page check, this aborts work already in
|
|
90
|
+
// flight — including a `Retry-After` sleep, which a server can otherwise
|
|
91
|
+
// make arbitrarily long. Set to 0 to disable the cap entirely for a
|
|
92
|
+
// deliberately long-running crawl.
|
|
93
|
+
crawlTimeoutMs: z.number().int().min(0).optional(),
|
|
83
94
|
})
|
|
84
95
|
.passthrough();
|
|
85
96
|
/** One component of a bundle (spec §10.1). Single-entry, transitional. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* `config-schema.ts` monolith — no behavior change.
|
|
7
7
|
*/
|
|
8
8
|
import { z } from "zod";
|
|
9
|
-
import { positiveInt } from "./primitives.js";
|
|
9
|
+
import { engineName, positiveInt } from "./primitives.js";
|
|
10
10
|
// ── Workflow engine ─────────────────────────────────────────────────────────
|
|
11
11
|
/**
|
|
12
12
|
* Workflow-engine settings (`workflow`).
|
|
@@ -19,11 +19,11 @@ import { positiveInt } from "./primitives.js";
|
|
|
19
19
|
* `[1, WORKFLOW_MAX_CONCURRENCY_CEILING]` (64). Values above the ceiling
|
|
20
20
|
* are clamped, not rejected, so a config shared across machines with wildly
|
|
21
21
|
* different core counts never hard-fails validation.
|
|
22
|
-
* The R3 brief/report driver surface does NOT consult this — drivers own their
|
|
23
|
-
* own parallelism (the engine only caps native dispatch).
|
|
24
22
|
*/
|
|
25
23
|
export const WorkflowConfigSchema = z
|
|
26
24
|
.object({
|
|
27
25
|
maxConcurrency: positiveInt.optional(),
|
|
26
|
+
/** Named LLM or agent engine frozen into every criteria-bearing gate. */
|
|
27
|
+
judgeEngine: engineName.optional(),
|
|
28
28
|
})
|
|
29
29
|
.passthrough();
|
package/dist/core/errors.js
CHANGED
|
@@ -15,7 +15,6 @@ const CONFIG_HINTS = {
|
|
|
15
15
|
SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_BUNDLE_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
|
|
16
16
|
UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The bundle directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
|
|
17
17
|
UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
|
|
18
|
-
WORKFLOW_ENGINE_NOT_ENABLED: "Run `akm config set experimental.workflowEngine true` to enable it.",
|
|
19
18
|
};
|
|
20
19
|
/** Default hint for each UsageError code. */
|
|
21
20
|
const USAGE_HINTS = {
|
|
@@ -29,12 +28,17 @@ const USAGE_HINTS = {
|
|
|
29
28
|
TARGET_NOT_UPDATABLE: "Run `akm bundle list` to view your sources, then retry with one of those values.",
|
|
30
29
|
MISSING_REQUIRED_ARGUMENT: "Refs use the form [bundle//]conceptId, e.g. `akm show knowledge/guide.md` or `akm show skills/deploy`.",
|
|
31
30
|
UNKNOWN_COMMAND: "Run `akm --help` to see available commands.",
|
|
31
|
+
UNKNOWN_FLAG: "Run the command with `--help` to see its accepted flags.",
|
|
32
32
|
};
|
|
33
33
|
/** Default hint for each NotFoundError code. */
|
|
34
34
|
const NOT_FOUND_HINTS = {
|
|
35
35
|
ASSET_NOT_FOUND: "Run `akm search <query>` or `akm index` to refresh the index.",
|
|
36
36
|
SOURCE_NOT_FOUND: "Run `akm bundle list` to view your sources, then retry with one of those values.",
|
|
37
37
|
WORKFLOW_NOT_FOUND: "Run `akm workflow list --active` to see runs.",
|
|
38
|
+
// A proposal is addressed by id or ref, never by path — reusing
|
|
39
|
+
// FILE_NOT_FOUND here handed users "check the path exists and is readable"
|
|
40
|
+
// for a mistyped id, which points at the wrong thing entirely.
|
|
41
|
+
PROPOSAL_NOT_FOUND: "Run `akm proposal list` to see pending proposals and their ids.",
|
|
38
42
|
FILE_NOT_FOUND: "Check the path exists and is readable.",
|
|
39
43
|
};
|
|
40
44
|
/**
|
package/dist/core/logs-db.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* @module logs-db
|
|
39
39
|
*/
|
|
40
40
|
import path from "node:path";
|
|
41
|
-
import { runMigrations as runSqliteMigrations } from "../storage/engines/sqlite-migrations.js";
|
|
41
|
+
import { assertMigrationRegistry, runMigrations as runSqliteMigrations, } from "../storage/engines/sqlite-migrations.js";
|
|
42
42
|
import { openManagedDatabase } from "../storage/managed-db.js";
|
|
43
43
|
import { getDataDir } from "./paths.js";
|
|
44
44
|
// ── Path helper ──────────────────────────────────────────────────────────────
|
|
@@ -130,6 +130,7 @@ const MIGRATIONS = [
|
|
|
130
130
|
`,
|
|
131
131
|
},
|
|
132
132
|
];
|
|
133
|
+
assertMigrationRegistry(MIGRATIONS);
|
|
133
134
|
/**
|
|
134
135
|
* Apply every pending migration. Called automatically by
|
|
135
136
|
* {@link openLogsDatabase}; exported for the same test seams state-db exposes.
|
package/dist/core/parse.js
CHANGED
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
* models). Also strips leading/trailing whitespace.
|
|
24
24
|
*/
|
|
25
25
|
export function stripThinkBlocks(raw) {
|
|
26
|
-
return raw
|
|
26
|
+
return raw
|
|
27
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
28
|
+
.replace(/^[\s\S]*?<\/think>/i, "")
|
|
29
|
+
.trim();
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* Strips markdown code fences (``` or ~~~, with optional language tag).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import { runMigrations as runSqliteMigrations } from "../../storage/engines/sqlite-migrations.js";
|
|
4
|
+
import { assertMigrationRegistry, runMigrations as runSqliteMigrations, } from "../../storage/engines/sqlite-migrations.js";
|
|
5
5
|
export const STATE_MIGRATIONS = [
|
|
6
6
|
// ── Migration 001 — initial schema ──────────────────────────────────────────
|
|
7
7
|
{
|
|
@@ -59,7 +59,7 @@ export const STATE_MIGRATIONS = [
|
|
|
59
59
|
-- id TEXT PK — UUID (crypto.randomUUID()); stable directory name.
|
|
60
60
|
-- stash_dir TEXT — absolute stash root; multi-stash installs need
|
|
61
61
|
-- this to partition proposal lists per stash.
|
|
62
|
-
-- ref TEXT — target asset ref (e.g. "
|
|
62
|
+
-- ref TEXT — target asset ref (e.g. "lessons/alpha");
|
|
63
63
|
-- indexed for ref-scoped queue views.
|
|
64
64
|
-- status TEXT — "pending" | "accepted" | "rejected"; indexed
|
|
65
65
|
-- so pending-queue queries are fast.
|
|
@@ -76,7 +76,7 @@ export const STATE_MIGRATIONS = [
|
|
|
76
76
|
-- metadata_json TEXT — JSON object for future proposal fields.
|
|
77
77
|
-- Current fields stored here: sourceRun,
|
|
78
78
|
-- review, confidence, gateDecision (#577),
|
|
79
|
-
-- backupContent
|
|
79
|
+
-- backupContent.
|
|
80
80
|
--
|
|
81
81
|
-- ADD COLUMN extension points (future migrations):
|
|
82
82
|
-- ALTER TABLE proposals ADD COLUMN source_run TEXT DEFAULT NULL;
|
|
@@ -353,9 +353,9 @@ export const STATE_MIGRATIONS = [
|
|
|
353
353
|
// (`scripts/akm-migrate/migrate/legacy/proposal-fs-import.ts`, wired through
|
|
354
354
|
// `akm-migrate apply`),
|
|
355
355
|
// whose idempotency is INSERT OR IGNORE on the proposal UUID plus migrate-apply's
|
|
356
|
-
//
|
|
357
|
-
// stays because
|
|
358
|
-
//
|
|
356
|
+
// incomplete sentinel — it no longer reads or writes this ledger. The CREATE TABLE
|
|
357
|
+
// stays because migration IDs are append-only: removing a released migration
|
|
358
|
+
// would make the schema_migrations ledger of an
|
|
359
359
|
// already-migrated rc database stop being an exact ordered prefix, and the
|
|
360
360
|
// runner would refuse to open it. The empty table is harmless.
|
|
361
361
|
//
|
|
@@ -833,12 +833,11 @@ export const STATE_MIGRATIONS = [
|
|
|
833
833
|
//
|
|
834
834
|
// The state.db half of the three-DB merge (plan §3.2/§8, normative §11.4,
|
|
835
835
|
// chunk-8 cutover design §1). This migration is PURE,
|
|
836
|
-
//
|
|
836
|
+
// IDEMPOTENT DDL ONLY — `CREATE TABLE IF NOT EXISTS` (+ indexes),
|
|
837
837
|
// never a DROP or a data move. The actual data movement (the workflow.db merge,
|
|
838
838
|
// the usage_events rescue from index.db, the full old-ref→item_ref re-key, and
|
|
839
|
-
// the workflow.db delete / index.db quarantine) is
|
|
840
|
-
//
|
|
841
|
-
// `cutover-applied` phase), driven by
|
|
839
|
+
// the workflow.db delete / index.db quarantine) is idempotent migrate-apply
|
|
840
|
+
// code driven by
|
|
842
841
|
// `scripts/akm-migrate/migrate/legacy/three-db-cutover.ts`. See the no-DROP
|
|
843
842
|
// contract carve-out note in `src/core/state-db.ts`.
|
|
844
843
|
//
|
|
@@ -985,6 +984,7 @@ export const STATE_MIGRATIONS = [
|
|
|
985
984
|
`,
|
|
986
985
|
},
|
|
987
986
|
];
|
|
987
|
+
assertMigrationRegistry(STATE_MIGRATIONS);
|
|
988
988
|
/**
|
|
989
989
|
* Apply every pending migration in a single transaction per migration.
|
|
990
990
|
*
|
|
@@ -994,8 +994,5 @@ export const STATE_MIGRATIONS = [
|
|
|
994
994
|
* Called automatically by `openStateDatabase()`.
|
|
995
995
|
*/
|
|
996
996
|
export function runMigrations(db, options) {
|
|
997
|
-
runSqliteMigrations(db, STATE_MIGRATIONS, {
|
|
998
|
-
applyPending: options?.applyPending,
|
|
999
|
-
generationMarker: options?.generationMarker,
|
|
1000
|
-
});
|
|
997
|
+
runSqliteMigrations(db, STATE_MIGRATIONS, { applyPending: options?.applyPending });
|
|
1001
998
|
}
|
package/dist/core/state-db.js
CHANGED
|
@@ -45,12 +45,10 @@
|
|
|
45
45
|
* merge-target tables at their final shape. The one-time, filesystem-derived,
|
|
46
46
|
* fail-closed DATA movement (the workflow.db merge, the usage_events rescue, the
|
|
47
47
|
* full old-ref→item_ref re-key, and the workflow.db unlink / index.db quarantine
|
|
48
|
-
* rename)
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* contract here is intact — the physical workflow.db deletion happens outside
|
|
53
|
-
* the ledger DDL, under the backup-verified-restorable fail-closed gate.
|
|
48
|
+
* rename) runs as idempotent code in the migrate-apply coordinator
|
|
49
|
+
* (`scripts/akm-migrate/migrate/legacy/three-db-cutover.ts`). So the no-DROP
|
|
50
|
+
* contract here is intact: physical workflow.db deletion happens outside the
|
|
51
|
+
* ledger DDL, after a verified backup and committed data transaction.
|
|
54
52
|
*
|
|
55
53
|
* ## Schema design: indexed columns vs. metadata_json
|
|
56
54
|
*
|
package/dist/core/subprocess.js
CHANGED
|
@@ -148,6 +148,7 @@ export async function readStream(stream, opts) {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
const EMPTY_READ = { text: "", timedOut: false };
|
|
151
|
+
const UNBOUNDED_STREAM_READ_SAFETY_MS = 60 * 60 * 1000;
|
|
151
152
|
function toError(err) {
|
|
152
153
|
return err instanceof Error ? err : new Error(String(err));
|
|
153
154
|
}
|
|
@@ -238,10 +239,11 @@ export async function runManagedSubprocess(cmd, opts) {
|
|
|
238
239
|
else
|
|
239
240
|
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
240
241
|
}
|
|
241
|
-
// Stream-drain timeout: the wall budget plus a 2 s grace, or
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
242
|
+
// Stream-drain timeout: the wall budget plus a 2 s grace, or a one-hour
|
|
243
|
+
// safety bound when execution itself is unbounded. This timer starts with
|
|
244
|
+
// capture, so the null-timeout path must not impose a short hidden deadline
|
|
245
|
+
// on an otherwise healthy long-running process.
|
|
246
|
+
const streamDrainTimeoutMs = timeoutMs !== null ? timeoutMs + 2_000 : UNBOUNDED_STREAM_READ_SAFETY_MS;
|
|
245
247
|
const stdoutPromise = capture
|
|
246
248
|
? readStream(proc.stdout ?? null, {
|
|
247
249
|
timeoutMs: streamDrainTimeoutMs,
|
|
@@ -38,7 +38,7 @@ function shellQuote(value) {
|
|
|
38
38
|
}
|
|
39
39
|
/** Reproduced from `output/renderers.ts#buildWorkflowAction` — the one function-valued `ACTION_BUILDERS.workflow` entry. */
|
|
40
40
|
function buildWorkflowAction(ref) {
|
|
41
|
-
return `
|
|
41
|
+
return `Start or resume execution with \`akm workflow run ${shellQuote(ref)}\`.`;
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Exhaustive over {@link KnownType} — a HAND-WRITTEN literal, not derived
|
package/dist/indexer/indexer.js
CHANGED
|
@@ -419,12 +419,27 @@ async function akmIndexReal(options) {
|
|
|
419
419
|
// Load config and resolve all stash sources
|
|
420
420
|
const { loadConfig, mutateConfig } = await import("../core/config/config.js");
|
|
421
421
|
let config = loadConfig();
|
|
422
|
+
// Durable state must be runtime-compatible before source hydration,
|
|
423
|
+
// adapter persistence, or index.db creation can mutate the installation.
|
|
424
|
+
onProgress({ phase: "preflight", message: "Validating durable state." });
|
|
425
|
+
withStateDb(() => undefined);
|
|
422
426
|
// Ensure git stash caches are extracted before resolving stash dirs,
|
|
423
427
|
// so their content directories exist on disk for the walker to discover.
|
|
424
428
|
const sourceCacheStart = Date.now();
|
|
425
429
|
onProgress({ phase: "preflight", message: "Hydrating source caches." });
|
|
426
430
|
const { ensureSourceCaches, resolveSourceEntries } = await import("./search/search-source.js");
|
|
427
|
-
|
|
431
|
+
// Inject the store-backed secret resolver from here — a composition root
|
|
432
|
+
// ABOVE the provider/fetcher import cycle (this module reaches
|
|
433
|
+
// search-source only via dynamic import). This is what lets a website
|
|
434
|
+
// source's X fetcher resolve `secrets/x-bearer-token` during
|
|
435
|
+
// bundle-update / hydrate, not just from the command-layer URL-ingest
|
|
436
|
+
// path. `secret-seam` is imported here, never from inside the cycle.
|
|
437
|
+
const { storeSecretResolver } = await import("../sources/snapshot-fetchers/secret-seam.js");
|
|
438
|
+
await ensureSourceCaches(config, {
|
|
439
|
+
force: full,
|
|
440
|
+
materialize: options.hydrateSources !== false,
|
|
441
|
+
secrets: storeSecretResolver,
|
|
442
|
+
});
|
|
428
443
|
const sourceCacheEnd = Date.now();
|
|
429
444
|
const allSourceEntries = resolveSourceEntries(stashDir, config);
|
|
430
445
|
const detected = detectAndPersistBundleAdapters(allSourceEntries, config, mutateConfig, {
|
|
@@ -304,7 +304,7 @@ export async function ensureSourceCaches(config, options) {
|
|
|
304
304
|
continue;
|
|
305
305
|
}
|
|
306
306
|
try {
|
|
307
|
-
await provider.sync({ force });
|
|
307
|
+
await provider.sync({ force, secrets: options?.secrets });
|
|
308
308
|
}
|
|
309
309
|
catch (err) {
|
|
310
310
|
warn(`Warning: failed to refresh ${provider.kind} source "${provider.name}": ${err instanceof Error ? err.message : String(err)}`);
|