akm-cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -0
- package/dist/{cli.js → src/cli.js} +712 -34
- package/dist/{commands → src/commands}/config-cli.js +47 -4
- package/dist/src/commands/distill.js +283 -0
- package/dist/src/commands/events.js +108 -0
- package/dist/src/commands/history.js +191 -0
- package/dist/{commands → src/commands}/installed-stashes.js +1 -1
- package/dist/src/commands/proposal.js +119 -0
- package/dist/src/commands/propose.js +171 -0
- package/dist/src/commands/reflect.js +193 -0
- package/dist/{commands → src/commands}/registry-search.js +71 -7
- package/dist/{commands → src/commands}/remember.js +12 -0
- package/dist/{commands → src/commands}/search.js +104 -4
- package/dist/{commands → src/commands}/self-update.js +4 -3
- package/dist/{commands → src/commands}/show.js +73 -0
- package/dist/{commands → src/commands}/source-add.js +5 -1
- package/dist/{commands → src/commands}/source-manage.js +7 -1
- package/dist/{core → src/core}/asset-ref.js +5 -5
- package/dist/{core → src/core}/asset-spec.js +12 -0
- package/dist/{core → src/core}/common.js +1 -1
- package/dist/{core → src/core}/config.js +203 -121
- package/dist/{core → src/core}/errors.js +4 -0
- package/dist/src/core/events.js +239 -0
- package/dist/src/core/lesson-lint.js +86 -0
- package/dist/src/core/proposals.js +406 -0
- package/dist/src/core/warn.js +72 -0
- package/dist/{core → src/core}/write-source.js +80 -5
- package/dist/{indexer → src/indexer}/db-search.js +114 -24
- package/dist/{indexer → src/indexer}/db.js +76 -23
- package/dist/{indexer → src/indexer}/file-context.js +0 -3
- package/dist/src/indexer/graph-boost.js +179 -0
- package/dist/src/indexer/graph-extraction.js +212 -0
- package/dist/{indexer → src/indexer}/indexer.js +88 -7
- package/dist/{indexer → src/indexer}/matchers.js +1 -1
- package/dist/src/indexer/memory-inference.js +263 -0
- package/dist/{indexer → src/indexer}/metadata.js +111 -3
- package/dist/{indexer → src/indexer}/search-source.js +4 -2
- package/dist/src/integrations/agent/config.js +292 -0
- package/dist/src/integrations/agent/detect.js +94 -0
- package/dist/src/integrations/agent/index.js +17 -0
- package/dist/src/integrations/agent/profiles.js +65 -0
- package/dist/src/integrations/agent/prompts.js +167 -0
- package/dist/src/integrations/agent/spawn.js +272 -0
- package/dist/{integrations → src/integrations}/github.js +9 -3
- package/dist/{integrations → src/integrations}/lockfile.js +0 -26
- package/dist/{llm → src/llm}/client.js +33 -2
- package/dist/{llm → src/llm}/embedders/remote.js +37 -3
- package/dist/src/llm/feature-gate.js +108 -0
- package/dist/src/llm/graph-extract.js +107 -0
- package/dist/src/llm/index-passes.js +35 -0
- package/dist/src/llm/memory-infer.js +86 -0
- package/dist/{output → src/output}/cli-hints.js +15 -2
- package/dist/{output → src/output}/renderers.js +63 -2
- package/dist/src/output/shapes.js +523 -0
- package/dist/src/output/text.js +1116 -0
- package/dist/{registry → src/registry}/build-index.js +19 -8
- package/dist/{registry → src/registry}/factory.js +0 -8
- package/dist/{registry → src/registry}/providers/static-index.js +6 -3
- package/dist/{registry → src/registry}/resolve.js +68 -2
- package/dist/{setup → src/setup}/setup.js +52 -5
- package/dist/{sources → src/sources}/providers/git.js +7 -15
- package/dist/{wiki → src/wiki}/wiki.js +54 -6
- package/dist/{workflows → src/workflows}/runs.js +37 -3
- package/dist/tests/add-website-source.test.js +119 -0
- package/dist/tests/agent/agent-config-loader.test.js +70 -0
- package/dist/tests/agent/agent-config.test.js +221 -0
- package/dist/tests/agent/agent-detect.test.js +100 -0
- package/dist/tests/agent/agent-spawn.test.js +234 -0
- package/dist/tests/agent-output.test.js +186 -0
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +103 -0
- package/dist/tests/architecture/agent-spawn-seam.test.js +193 -0
- package/dist/tests/architecture/llm-stateless-seam.test.js +112 -0
- package/dist/tests/asset-ref.test.js +192 -0
- package/dist/tests/asset-registry.test.js +103 -0
- package/dist/tests/asset-spec.test.js +241 -0
- package/dist/tests/bench/attribution.test.js +996 -0
- package/dist/tests/bench/cleanup-sigint.test.js +83 -0
- package/dist/tests/bench/cleanup.js +234 -0
- package/dist/tests/bench/cleanup.test.js +166 -0
- package/dist/tests/bench/cli.js +1018 -0
- package/dist/tests/bench/cli.test.js +445 -0
- package/dist/tests/bench/compare.test.js +556 -0
- package/dist/tests/bench/corpus.js +317 -0
- package/dist/tests/bench/corpus.test.js +258 -0
- package/dist/tests/bench/doctor.js +525 -0
- package/dist/tests/bench/driver.js +401 -0
- package/dist/tests/bench/driver.test.js +584 -0
- package/dist/tests/bench/environment.js +233 -0
- package/dist/tests/bench/environment.test.js +199 -0
- package/dist/tests/bench/evolve-metrics.js +179 -0
- package/dist/tests/bench/evolve-metrics.test.js +187 -0
- package/dist/tests/bench/evolve.js +647 -0
- package/dist/tests/bench/evolve.test.js +624 -0
- package/dist/tests/bench/failure-modes.test.js +349 -0
- package/dist/tests/bench/feedback-integrity.test.js +457 -0
- package/dist/tests/bench/leakage.test.js +228 -0
- package/dist/tests/bench/learning-curve.test.js +134 -0
- package/dist/tests/bench/metrics.js +2395 -0
- package/dist/tests/bench/metrics.test.js +1150 -0
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +43 -0
- package/dist/tests/bench/opencode-config.js +194 -0
- package/dist/tests/bench/opencode-config.test.js +370 -0
- package/dist/tests/bench/report.js +1885 -0
- package/dist/tests/bench/report.test.js +1038 -0
- package/dist/tests/bench/run-config.js +355 -0
- package/dist/tests/bench/run-config.test.js +298 -0
- package/dist/tests/bench/run-curate-test.js +32 -0
- package/dist/tests/bench/run-failing-tasks.js +56 -0
- package/dist/tests/bench/run-full-bench.js +51 -0
- package/dist/tests/bench/run-items36-targeted.js +69 -0
- package/dist/tests/bench/run-nano-quick.js +42 -0
- package/dist/tests/bench/run-waveg-targeted.js +62 -0
- package/dist/tests/bench/runner.js +699 -0
- package/dist/tests/bench/runner.test.js +958 -0
- package/dist/tests/bench/search-bridge.test.js +331 -0
- package/dist/tests/bench/tmp.js +131 -0
- package/dist/tests/bench/trajectory.js +116 -0
- package/dist/tests/bench/trajectory.test.js +127 -0
- package/dist/tests/bench/verifier.js +114 -0
- package/dist/tests/bench/verifier.test.js +118 -0
- package/dist/tests/bench/workflow-evaluator.js +557 -0
- package/dist/tests/bench/workflow-evaluator.test.js +421 -0
- package/dist/tests/bench/workflow-spec.js +345 -0
- package/dist/tests/bench/workflow-spec.test.js +363 -0
- package/dist/tests/bench/workflow-trace.js +472 -0
- package/dist/tests/bench/workflow-trace.test.js +254 -0
- package/dist/tests/benchmark-search-quality.js +536 -0
- package/dist/tests/benchmark-suite.js +1441 -0
- package/dist/tests/capture-cli.test.js +112 -0
- package/dist/tests/cli-errors.test.js +204 -0
- package/dist/tests/commands/events.test.js +370 -0
- package/dist/tests/commands/history.test.js +418 -0
- package/dist/tests/commands/import.test.js +103 -0
- package/dist/tests/commands/proposal-cli.test.js +209 -0
- package/dist/tests/commands/reflect-propose-cli.test.js +333 -0
- package/dist/tests/commands/remember.test.js +97 -0
- package/dist/tests/commands/scope-flags.test.js +300 -0
- package/dist/tests/commands/search.test.js +537 -0
- package/dist/tests/commands/show-indexer-parity.test.js +117 -0
- package/dist/tests/commands/show.test.js +294 -0
- package/dist/tests/common.test.js +266 -0
- package/dist/tests/completions.test.js +142 -0
- package/dist/tests/config-cli.test.js +193 -0
- package/dist/tests/config-llm-features.test.js +139 -0
- package/dist/tests/config.test.js +569 -0
- package/dist/tests/contracts/migration-baseline.test.js +43 -0
- package/dist/tests/contracts/reflect-propose-envelope.test.js +139 -0
- package/dist/tests/contracts/spec-helpers.js +46 -0
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +228 -0
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +56 -0
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +94 -0
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +39 -0
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +44 -0
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +47 -0
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +40 -0
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +58 -0
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +34 -0
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +75 -0
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +36 -0
- package/dist/tests/core/write-source.test.js +366 -0
- package/dist/tests/curate-command.test.js +87 -0
- package/dist/tests/db-scoring.test.js +201 -0
- package/dist/tests/db.test.js +654 -0
- package/dist/tests/distill-cli-flag.test.js +208 -0
- package/dist/tests/distill.test.js +515 -0
- package/dist/tests/docker-install.test.js +120 -0
- package/dist/tests/e2e.test.js +1419 -0
- package/dist/tests/embedder.test.js +340 -0
- package/dist/tests/embedding-model-config.test.js +379 -0
- package/dist/tests/feedback-command.test.js +172 -0
- package/dist/tests/file-context.test.js +552 -0
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +9 -0
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +7 -0
- package/dist/tests/fixtures/stashes/load.js +166 -0
- package/dist/tests/fixtures/stashes/load.test.js +97 -0
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +12 -0
- package/dist/tests/frontmatter.test.js +190 -0
- package/dist/tests/fts-field-weighting.test.js +254 -0
- package/dist/tests/fuzzy-search.test.js +230 -0
- package/dist/tests/git-provider-clone.test.js +45 -0
- package/dist/tests/github.test.js +161 -0
- package/dist/tests/graph-boost-ranking.test.js +305 -0
- package/dist/tests/graph-extraction.test.js +282 -0
- package/dist/tests/helpers/usage-events.js +8 -0
- package/dist/tests/index-pass-llm.test.js +161 -0
- package/dist/tests/indexer.test.js +570 -0
- package/dist/tests/info-command.test.js +166 -0
- package/dist/tests/init.test.js +69 -0
- package/dist/tests/install-script.test.js +246 -0
- package/dist/tests/integration/agent-real-profile.test.js +94 -0
- package/dist/tests/issue-36-repro.test.js +304 -0
- package/dist/tests/issues-191-194.test.js +160 -0
- package/dist/tests/lesson-lint.test.js +111 -0
- package/dist/tests/llm-client.test.js +115 -0
- package/dist/tests/llm-feature-gate.test.js +151 -0
- package/dist/tests/llm.test.js +139 -0
- package/dist/tests/lockfile.test.js +216 -0
- package/dist/tests/manifest.test.js +205 -0
- package/dist/tests/markdown.test.js +126 -0
- package/dist/tests/matchers-unit.test.js +189 -0
- package/dist/tests/memory-inference.test.js +299 -0
- package/dist/tests/merge-scoring.test.js +136 -0
- package/dist/tests/metadata.test.js +313 -0
- package/dist/tests/migration-help.test.js +89 -0
- package/dist/tests/origin-resolve.test.js +124 -0
- package/dist/tests/output-baseline.test.js +218 -0
- package/dist/tests/output-shapes-unit.test.js +478 -0
- package/dist/tests/parallel-search.test.js +272 -0
- package/dist/tests/parameter-metadata.test.js +365 -0
- package/dist/tests/paths.test.js +177 -0
- package/dist/tests/progressive-disclosure.test.js +280 -0
- package/dist/tests/proposals.test.js +279 -0
- package/dist/tests/proposed-quality.test.js +271 -0
- package/dist/tests/provider-registry.test.js +32 -0
- package/dist/tests/ranking-regression.test.js +548 -0
- package/dist/tests/reflect-propose.test.js +455 -0
- package/dist/tests/registry-build-index.test.js +394 -0
- package/dist/tests/registry-cli.test.js +290 -0
- package/dist/tests/registry-index-v2.test.js +430 -0
- package/dist/tests/registry-install.test.js +728 -0
- package/dist/tests/registry-providers/parity.test.js +189 -0
- package/dist/tests/registry-providers/skills-sh.test.js +309 -0
- package/dist/tests/registry-providers/static-index.test.js +238 -0
- package/dist/tests/registry-resolve.test.js +126 -0
- package/dist/tests/registry-search.test.js +923 -0
- package/dist/tests/remember-frontmatter.test.js +378 -0
- package/dist/tests/remember-unit.test.js +123 -0
- package/dist/tests/ripgrep-install.test.js +251 -0
- package/dist/tests/ripgrep-resolve.test.js +108 -0
- package/dist/tests/ripgrep.test.js +163 -0
- package/dist/tests/save-command.test.js +94 -0
- package/dist/tests/save-trust-qa-fixes.test.js +270 -0
- package/dist/tests/scoring-pipeline.test.js +648 -0
- package/dist/tests/search-include-proposed-cli.test.js +118 -0
- package/dist/tests/self-update.test.js +442 -0
- package/dist/tests/semantic-search-e2e.test.js +512 -0
- package/dist/tests/semantic-status.test.js +471 -0
- package/dist/tests/setup-run.integration.js +877 -0
- package/dist/tests/setup-wizard.test.js +198 -0
- package/dist/tests/setup.test.js +131 -0
- package/dist/tests/source-add.test.js +11 -0
- package/dist/tests/source-clone.test.js +254 -0
- package/dist/tests/source-manage.test.js +366 -0
- package/dist/tests/source-providers/filesystem.test.js +82 -0
- package/dist/tests/source-providers/git.test.js +252 -0
- package/dist/tests/source-providers/website.test.js +128 -0
- package/dist/tests/source-qa-fixes.test.js +286 -0
- package/dist/tests/source-registry.test.js +350 -0
- package/dist/tests/source-resolve.test.js +100 -0
- package/dist/tests/source-source.test.js +281 -0
- package/dist/tests/source.test.js +533 -0
- package/dist/tests/tar-utils-scan.test.js +73 -0
- package/dist/tests/toggle-components.test.js +73 -0
- package/dist/tests/usage-telemetry.test.js +265 -0
- package/dist/tests/utility-scoring.test.js +558 -0
- package/dist/tests/vault-load-error.test.js +78 -0
- package/dist/tests/vault-qa-fixes.test.js +194 -0
- package/dist/tests/vault.test.js +429 -0
- package/dist/tests/vector-search.test.js +608 -0
- package/dist/tests/walker.test.js +252 -0
- package/dist/tests/wave2-cluster-bc.test.js +228 -0
- package/dist/tests/wave2-cluster-d.test.js +180 -0
- package/dist/tests/wave2-cluster-e.test.js +179 -0
- package/dist/tests/wiki-qa-fixes.test.js +270 -0
- package/dist/tests/wiki.test.js +529 -0
- package/dist/tests/workflow-cli.test.js +271 -0
- package/dist/tests/workflow-markdown.test.js +171 -0
- package/dist/tests/workflow-path-escape.test.js +132 -0
- package/dist/tests/workflow-qa-fixes.test.js +395 -0
- package/dist/tests/workflows/indexer-rejection.test.js +213 -0
- package/docs/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +244 -0
- package/package.json +2 -2
- package/dist/core/warn.js +0 -27
- package/dist/output/shapes.js +0 -212
- package/dist/output/text.js +0 -520
- /package/dist/{commands → src/commands}/completions.js +0 -0
- /package/dist/{commands → src/commands}/curate.js +0 -0
- /package/dist/{commands → src/commands}/info.js +0 -0
- /package/dist/{commands → src/commands}/init.js +0 -0
- /package/dist/{commands → src/commands}/install-audit.js +0 -0
- /package/dist/{commands → src/commands}/migration-help.js +0 -0
- /package/dist/{commands → src/commands}/source-clone.js +0 -0
- /package/dist/{commands → src/commands}/vault.js +0 -0
- /package/dist/{core → src/core}/asset-registry.js +0 -0
- /package/dist/{core → src/core}/frontmatter.js +0 -0
- /package/dist/{core → src/core}/markdown.js +0 -0
- /package/dist/{core → src/core}/paths.js +0 -0
- /package/dist/{indexer → src/indexer}/manifest.js +0 -0
- /package/dist/{indexer → src/indexer}/search-fields.js +0 -0
- /package/dist/{indexer → src/indexer}/semantic-status.js +0 -0
- /package/dist/{indexer → src/indexer}/usage-events.js +0 -0
- /package/dist/{indexer → src/indexer}/walker.js +0 -0
- /package/dist/{llm → src/llm}/embedder.js +0 -0
- /package/dist/{llm → src/llm}/embedders/cache.js +0 -0
- /package/dist/{llm → src/llm}/embedders/local.js +0 -0
- /package/dist/{llm → src/llm}/embedders/types.js +0 -0
- /package/dist/{llm → src/llm}/metadata-enhance.js +0 -0
- /package/dist/{output → src/output}/context.js +0 -0
- /package/dist/{registry → src/registry}/create-provider-registry.js +0 -0
- /package/dist/{registry → src/registry}/origin-resolve.js +0 -0
- /package/dist/{registry → src/registry}/providers/index.js +0 -0
- /package/dist/{registry → src/registry}/providers/skills-sh.js +0 -0
- /package/dist/{registry → src/registry}/providers/types.js +0 -0
- /package/dist/{registry → src/registry}/types.js +0 -0
- /package/dist/{setup → src/setup}/detect.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-install.js +0 -0
- /package/dist/{setup → src/setup}/ripgrep-resolve.js +0 -0
- /package/dist/{setup → src/setup}/steps.js +0 -0
- /package/dist/{sources → src/sources}/include.js +0 -0
- /package/dist/{sources → src/sources}/provider-factory.js +0 -0
- /package/dist/{sources → src/sources}/provider.js +0 -0
- /package/dist/{sources → src/sources}/providers/filesystem.js +0 -0
- /package/dist/{sources → src/sources}/providers/index.js +0 -0
- /package/dist/{sources → src/sources}/providers/install-types.js +0 -0
- /package/dist/{sources → src/sources}/providers/npm.js +0 -0
- /package/dist/{sources → src/sources}/providers/provider-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{sources → src/sources}/providers/tar-utils.js +0 -0
- /package/dist/{sources → src/sources}/providers/website.js +0 -0
- /package/dist/{sources → src/sources}/resolve.js +0 -0
- /package/dist/{sources → src/sources}/types.js +0 -0
- /package/dist/{templates → src/templates}/wiki-templates.js +0 -0
- /package/dist/{version.js → src/version.js} +0 -0
- /package/dist/{workflows → src/workflows}/authoring.js +0 -0
- /package/dist/{workflows → src/workflows}/cli.js +0 -0
- /package/dist/{workflows → src/workflows}/db.js +0 -0
- /package/dist/{workflows → src/workflows}/document-cache.js +0 -0
- /package/dist/{workflows → src/workflows}/parser.js +0 -0
- /package/dist/{workflows → src/workflows}/renderer.js +0 -0
- /package/dist/{workflows → src/workflows}/schema.js +0 -0
- /package/dist/{workflows → src/workflows}/validator.js +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
const CLI = path.join(__dirname, "..", "src", "cli.ts");
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
function makeTempDir(prefix) {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
10
|
+
tempDirs.push(dir);
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
function createWorkflowEnv() {
|
|
14
|
+
const stashDir = makeTempDir("akm-wfqa-stash-");
|
|
15
|
+
const xdgCache = makeTempDir("akm-wfqa-cache-");
|
|
16
|
+
const xdgConfig = makeTempDir("akm-wfqa-config-");
|
|
17
|
+
return {
|
|
18
|
+
...process.env,
|
|
19
|
+
AKM_STASH_DIR: stashDir,
|
|
20
|
+
XDG_CACHE_HOME: xdgCache,
|
|
21
|
+
XDG_CONFIG_HOME: xdgConfig,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function runCli(args, env) {
|
|
25
|
+
return spawnSync("bun", [CLI, ...args], {
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
timeout: 30_000,
|
|
28
|
+
env,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
for (const dir of tempDirs.splice(0)) {
|
|
33
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const TWO_STEP_WORKFLOW = `---
|
|
37
|
+
description: Test workflow
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
# Workflow: Test Flow
|
|
41
|
+
|
|
42
|
+
## Step: First Step
|
|
43
|
+
Step ID: first
|
|
44
|
+
|
|
45
|
+
### Instructions
|
|
46
|
+
Do the first thing.
|
|
47
|
+
|
|
48
|
+
### Completion Criteria
|
|
49
|
+
- First thing done
|
|
50
|
+
|
|
51
|
+
## Step: Second Step
|
|
52
|
+
Step ID: second
|
|
53
|
+
|
|
54
|
+
### Instructions
|
|
55
|
+
Do the second thing.
|
|
56
|
+
`;
|
|
57
|
+
function setupWorkflow(env, name = "test-flow") {
|
|
58
|
+
const sourceDir = makeTempDir("akm-wfqa-src-");
|
|
59
|
+
const sourcePath = path.join(sourceDir, "wf.md");
|
|
60
|
+
fs.writeFileSync(sourcePath, TWO_STEP_WORKFLOW, "utf8");
|
|
61
|
+
const result = runCli(["workflow", "create", name, "--from", sourcePath], env);
|
|
62
|
+
if (result.status !== 0) {
|
|
63
|
+
throw new Error(`Failed to create workflow: ${result.stderr}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// 1. resumeWorkflowRun — blocked → active; fails on completed
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
describe("workflow resume command", () => {
|
|
70
|
+
test("resume flips a blocked run back to active", () => {
|
|
71
|
+
const env = createWorkflowEnv();
|
|
72
|
+
setupWorkflow(env);
|
|
73
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
74
|
+
expect(started.status).toBe(0);
|
|
75
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
76
|
+
// Complete first step as blocked
|
|
77
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
|
|
78
|
+
// Verify it's blocked
|
|
79
|
+
const statusBlocked = runCli(["workflow", "status", startRun.id], env);
|
|
80
|
+
expect(statusBlocked.status).toBe(0);
|
|
81
|
+
const { run: blockedRun } = JSON.parse(statusBlocked.stdout);
|
|
82
|
+
expect(blockedRun.status).toBe("blocked");
|
|
83
|
+
// Resume it
|
|
84
|
+
const resumed = runCli(["workflow", "resume", startRun.id], env);
|
|
85
|
+
expect(resumed.status).toBe(0);
|
|
86
|
+
const { run: resumedRun } = JSON.parse(resumed.stdout);
|
|
87
|
+
expect(resumedRun.status).toBe("active");
|
|
88
|
+
});
|
|
89
|
+
test("resume on a completed run returns an error", () => {
|
|
90
|
+
const env = createWorkflowEnv();
|
|
91
|
+
setupWorkflow(env);
|
|
92
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
93
|
+
expect(started.status).toBe(0);
|
|
94
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
95
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
|
|
96
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
|
|
97
|
+
const resumed = runCli(["workflow", "resume", startRun.id], env);
|
|
98
|
+
expect(resumed.status).toBe(2);
|
|
99
|
+
const err = JSON.parse(resumed.stderr);
|
|
100
|
+
expect(err.error).toContain("already completed");
|
|
101
|
+
});
|
|
102
|
+
test("resume on a failed run flips it to active", () => {
|
|
103
|
+
const env = createWorkflowEnv();
|
|
104
|
+
setupWorkflow(env);
|
|
105
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
106
|
+
expect(started.status).toBe(0);
|
|
107
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
108
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "failed"], env).status).toBe(0);
|
|
109
|
+
const resumed = runCli(["workflow", "resume", startRun.id], env);
|
|
110
|
+
expect(resumed.status).toBe(0);
|
|
111
|
+
const { run: resumedRun } = JSON.parse(resumed.stdout);
|
|
112
|
+
expect(resumedRun.status).toBe("active");
|
|
113
|
+
});
|
|
114
|
+
// Issue #156: after resuming a blocked run, the previously-blocked step must be
|
|
115
|
+
// re-actionable so it can be reclassified to completed/failed/skipped.
|
|
116
|
+
for (const newState of ["completed", "failed", "skipped"]) {
|
|
117
|
+
test(`resume re-opens a blocked step so it can be reclassified to ${newState}`, () => {
|
|
118
|
+
const env = createWorkflowEnv();
|
|
119
|
+
setupWorkflow(env);
|
|
120
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
121
|
+
expect(started.status).toBe(0);
|
|
122
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
123
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
|
|
124
|
+
expect(runCli(["workflow", "resume", startRun.id], env).status).toBe(0);
|
|
125
|
+
const reclassified = runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", newState, "--notes", "resolved"], env);
|
|
126
|
+
expect(reclassified.status).toBe(0);
|
|
127
|
+
const parsed = JSON.parse(reclassified.stdout);
|
|
128
|
+
const firstStep = parsed.workflow.steps.find((s) => s.id === "first");
|
|
129
|
+
expect(firstStep?.status).toBe(newState);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
test("resume does not disturb already-completed earlier steps", () => {
|
|
133
|
+
const env = createWorkflowEnv();
|
|
134
|
+
setupWorkflow(env);
|
|
135
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
136
|
+
expect(started.status).toBe(0);
|
|
137
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
138
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
|
|
139
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "second", "--state", "blocked"], env).status).toBe(0);
|
|
140
|
+
expect(runCli(["workflow", "resume", startRun.id], env).status).toBe(0);
|
|
141
|
+
const status = runCli(["workflow", "status", startRun.id], env);
|
|
142
|
+
expect(status.status).toBe(0);
|
|
143
|
+
const detail = JSON.parse(status.stdout);
|
|
144
|
+
expect(detail.workflow.steps.find((s) => s.id === "first")?.status).toBe("completed");
|
|
145
|
+
expect(detail.workflow.steps.find((s) => s.id === "second")?.status).toBe("pending");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// 2. listWorkflowRuns --active includes blocked runs
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
describe("workflow list --active includes blocked", () => {
|
|
152
|
+
test("blocked run appears in --active list", () => {
|
|
153
|
+
const env = createWorkflowEnv();
|
|
154
|
+
setupWorkflow(env);
|
|
155
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
156
|
+
expect(started.status).toBe(0);
|
|
157
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
158
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first", "--state", "blocked"], env).status).toBe(0);
|
|
159
|
+
const listed = runCli(["workflow", "list", "--ref", "workflow:test-flow", "--active"], env);
|
|
160
|
+
expect(listed.status).toBe(0);
|
|
161
|
+
const { runs } = JSON.parse(listed.stdout);
|
|
162
|
+
expect(runs.some((r) => r.id === startRun.id && r.status === "blocked")).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
test("completed run does NOT appear in --active list", () => {
|
|
165
|
+
const env = createWorkflowEnv();
|
|
166
|
+
setupWorkflow(env);
|
|
167
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
168
|
+
expect(started.status).toBe(0);
|
|
169
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
170
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
|
|
171
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
|
|
172
|
+
const listed = runCli(["workflow", "list", "--active"], env);
|
|
173
|
+
expect(listed.status).toBe(0);
|
|
174
|
+
const { runs } = JSON.parse(listed.stdout);
|
|
175
|
+
expect(runs.some((r) => r.id === startRun.id)).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// 3. workflow next on a completed run: done:true, step:null
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
describe("workflow next — completed run signals done", () => {
|
|
182
|
+
test("next on a completed run-id returns done:true and step:null", () => {
|
|
183
|
+
const env = createWorkflowEnv();
|
|
184
|
+
setupWorkflow(env);
|
|
185
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
186
|
+
expect(started.status).toBe(0);
|
|
187
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
188
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "first"], env).status).toBe(0);
|
|
189
|
+
expect(runCli(["workflow", "complete", startRun.id, "--step", "second"], env).status).toBe(0);
|
|
190
|
+
const next = runCli(["workflow", "next", startRun.id], env);
|
|
191
|
+
expect(next.status).toBe(0);
|
|
192
|
+
const nextJson = JSON.parse(next.stdout);
|
|
193
|
+
expect(nextJson.done).toBe(true);
|
|
194
|
+
expect(nextJson.step).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// 4. workflow next <ref> auto-start: autoStarted:true
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
describe("workflow next — auto-start flags autoStarted", () => {
|
|
201
|
+
test("next on a ref with no existing run returns autoStarted:true", () => {
|
|
202
|
+
const env = createWorkflowEnv();
|
|
203
|
+
setupWorkflow(env);
|
|
204
|
+
const next = runCli(["workflow", "next", "workflow:test-flow"], env);
|
|
205
|
+
expect(next.status).toBe(0);
|
|
206
|
+
const nextJson = JSON.parse(next.stdout);
|
|
207
|
+
expect(nextJson.autoStarted).toBe(true);
|
|
208
|
+
expect(nextJson.run.status).toBe("active");
|
|
209
|
+
expect(nextJson.step.id).toBe("first");
|
|
210
|
+
});
|
|
211
|
+
test("next on a ref with existing active run does NOT set autoStarted", () => {
|
|
212
|
+
const env = createWorkflowEnv();
|
|
213
|
+
setupWorkflow(env);
|
|
214
|
+
// First call auto-starts
|
|
215
|
+
const first = runCli(["workflow", "next", "workflow:test-flow"], env);
|
|
216
|
+
expect(first.status).toBe(0);
|
|
217
|
+
// Second call resumes existing — no autoStarted
|
|
218
|
+
const second = runCli(["workflow", "next", "workflow:test-flow"], env);
|
|
219
|
+
expect(second.status).toBe(0);
|
|
220
|
+
const secondJson = JSON.parse(second.stdout);
|
|
221
|
+
expect(secondJson.autoStarted).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// 5. workflow next --params: sets params on auto-start; fails on existing run
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
describe("workflow next --params", () => {
|
|
228
|
+
test("--params is accepted when auto-starting a new run", () => {
|
|
229
|
+
const env = createWorkflowEnv();
|
|
230
|
+
setupWorkflow(env);
|
|
231
|
+
const next = runCli(["workflow", "next", "workflow:test-flow", "--params", '{"x":1}'], env);
|
|
232
|
+
expect(next.status).toBe(0);
|
|
233
|
+
const nextJson = JSON.parse(next.stdout);
|
|
234
|
+
expect(nextJson.autoStarted).toBe(true);
|
|
235
|
+
expect(nextJson.run.params?.x).toBe(1);
|
|
236
|
+
});
|
|
237
|
+
test("--params fails when an active run already exists (ref specifier)", () => {
|
|
238
|
+
const env = createWorkflowEnv();
|
|
239
|
+
setupWorkflow(env);
|
|
240
|
+
// Start a run first
|
|
241
|
+
expect(runCli(["workflow", "start", "workflow:test-flow"], env).status).toBe(0);
|
|
242
|
+
const next = runCli(["workflow", "next", "workflow:test-flow", "--params", '{"x":1}'], env);
|
|
243
|
+
expect(next.status).toBe(2);
|
|
244
|
+
const err = JSON.parse(next.stderr);
|
|
245
|
+
expect(err.error).toContain("--params can only be set on a new run");
|
|
246
|
+
});
|
|
247
|
+
test("--params fails when given a direct run-id (existing run)", () => {
|
|
248
|
+
const env = createWorkflowEnv();
|
|
249
|
+
setupWorkflow(env);
|
|
250
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
251
|
+
expect(started.status).toBe(0);
|
|
252
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
253
|
+
const next = runCli(["workflow", "next", startRun.id, "--params", '{"x":1}'], env);
|
|
254
|
+
expect(next.status).toBe(2);
|
|
255
|
+
const err = JSON.parse(next.stderr);
|
|
256
|
+
expect(err.error).toContain("--params can only be used when starting a new run from a workflow ref");
|
|
257
|
+
expect(err.error).toContain("existing run id");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// 6. workflow status workflow:<name> resolves to most-recent run
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
describe("workflow status with workflow ref", () => {
|
|
264
|
+
test("status workflow:<name> resolves to the most-recently-updated run", () => {
|
|
265
|
+
const env = createWorkflowEnv();
|
|
266
|
+
setupWorkflow(env);
|
|
267
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
268
|
+
expect(started.status).toBe(0);
|
|
269
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
270
|
+
const status = runCli(["workflow", "status", "workflow:test-flow"], env);
|
|
271
|
+
expect(status.status).toBe(0);
|
|
272
|
+
const statusJson = JSON.parse(status.stdout);
|
|
273
|
+
expect(statusJson.run.id).toBe(startRun.id);
|
|
274
|
+
expect(statusJson.run.status).toBe("active");
|
|
275
|
+
});
|
|
276
|
+
test("status workflow:<name> returns NotFoundError when no runs exist", () => {
|
|
277
|
+
const env = createWorkflowEnv();
|
|
278
|
+
setupWorkflow(env);
|
|
279
|
+
const status = runCli(["workflow", "status", "workflow:test-flow"], env);
|
|
280
|
+
// No runs created yet — should fail with not-found (exit 1)
|
|
281
|
+
expect(status.status).toBe(1);
|
|
282
|
+
const err = JSON.parse(status.stderr);
|
|
283
|
+
expect(err.error).toContain("No workflow runs found");
|
|
284
|
+
});
|
|
285
|
+
test("next with an unknown run id returns WORKFLOW_NOT_FOUND", () => {
|
|
286
|
+
const env = createWorkflowEnv();
|
|
287
|
+
setupWorkflow(env);
|
|
288
|
+
const next = runCli(["workflow", "next", "bogus-run-id"], env);
|
|
289
|
+
expect(next.status).toBe(1);
|
|
290
|
+
const err = JSON.parse(next.stderr);
|
|
291
|
+
expect(err.code).toBe("WORKFLOW_NOT_FOUND");
|
|
292
|
+
expect(err.hint).toContain("akm workflow list --active");
|
|
293
|
+
});
|
|
294
|
+
test("status with an unknown run id returns WORKFLOW_NOT_FOUND", () => {
|
|
295
|
+
const env = createWorkflowEnv();
|
|
296
|
+
setupWorkflow(env);
|
|
297
|
+
const status = runCli(["workflow", "status", "bogus-run-id"], env);
|
|
298
|
+
expect(status.status).toBe(1);
|
|
299
|
+
const err = JSON.parse(status.stderr);
|
|
300
|
+
expect(err.code).toBe("WORKFLOW_NOT_FOUND");
|
|
301
|
+
expect(err.hint).toContain("akm workflow list --active");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
// 7. workflow create name validation
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
describe("workflow create — name validation", () => {
|
|
308
|
+
test("name with spaces is rejected", () => {
|
|
309
|
+
const env = createWorkflowEnv();
|
|
310
|
+
const result = runCli(["workflow", "create", "name with spaces"], env);
|
|
311
|
+
expect(result.status).toBe(2);
|
|
312
|
+
const err = JSON.parse(result.stderr);
|
|
313
|
+
expect(err.error).toContain("Workflow name must start with a lowercase letter");
|
|
314
|
+
});
|
|
315
|
+
test("name with uppercase is rejected", () => {
|
|
316
|
+
const env = createWorkflowEnv();
|
|
317
|
+
const result = runCli(["workflow", "create", "MyWorkflow"], env);
|
|
318
|
+
expect(result.status).toBe(2);
|
|
319
|
+
const err = JSON.parse(result.stderr);
|
|
320
|
+
expect(err.error).toContain("Workflow name must start with a lowercase letter");
|
|
321
|
+
});
|
|
322
|
+
test("valid lowercase name is accepted", () => {
|
|
323
|
+
const env = createWorkflowEnv();
|
|
324
|
+
const result = runCli(["workflow", "create", "my-workflow"], env);
|
|
325
|
+
expect(result.status).toBe(0);
|
|
326
|
+
const json = JSON.parse(result.stdout);
|
|
327
|
+
expect(json.ref).toBe("workflow:my-workflow");
|
|
328
|
+
});
|
|
329
|
+
test("hierarchical name with forward slash is accepted", () => {
|
|
330
|
+
// Slashes are allowed for hierarchical naming (e.g. release/ship)
|
|
331
|
+
const env = createWorkflowEnv();
|
|
332
|
+
const result = runCli(["workflow", "create", "release/ship"], env);
|
|
333
|
+
expect(result.status).toBe(0);
|
|
334
|
+
const json = JSON.parse(result.stdout);
|
|
335
|
+
expect(json.ref).toBe("workflow:release/ship");
|
|
336
|
+
});
|
|
337
|
+
test("name validation error message mentions slashes", () => {
|
|
338
|
+
const env = createWorkflowEnv();
|
|
339
|
+
const result = runCli(["workflow", "create", "BAD NAME"], env);
|
|
340
|
+
expect(result.status).toBe(2);
|
|
341
|
+
const err = JSON.parse(result.stderr);
|
|
342
|
+
expect(err.error).toContain("slashes");
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// 8. workflow create --force without --from/--reset is rejected
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
describe("workflow create --force guard", () => {
|
|
349
|
+
test("--force without --from or --reset is rejected", () => {
|
|
350
|
+
const env = createWorkflowEnv();
|
|
351
|
+
setupWorkflow(env);
|
|
352
|
+
const result = runCli(["workflow", "create", "test-flow", "--force"], env);
|
|
353
|
+
expect(result.status).toBe(2);
|
|
354
|
+
const err = JSON.parse(result.stderr);
|
|
355
|
+
expect(err.error).toContain("Refusing to overwrite with template");
|
|
356
|
+
});
|
|
357
|
+
test("--force --reset succeeds and overwrites with template", () => {
|
|
358
|
+
const env = createWorkflowEnv();
|
|
359
|
+
setupWorkflow(env);
|
|
360
|
+
const result = runCli(["workflow", "create", "test-flow", "--force", "--reset"], env);
|
|
361
|
+
expect(result.status).toBe(0);
|
|
362
|
+
const json = JSON.parse(result.stdout);
|
|
363
|
+
expect(json.ok).toBe(true);
|
|
364
|
+
expect(json.ref).toBe("workflow:test-flow");
|
|
365
|
+
});
|
|
366
|
+
test("--force --from <file> succeeds and overwrites with file content", () => {
|
|
367
|
+
const env = createWorkflowEnv();
|
|
368
|
+
setupWorkflow(env);
|
|
369
|
+
const sourceDir = makeTempDir("akm-wfqa-src2-");
|
|
370
|
+
const sourcePath = path.join(sourceDir, "new.md");
|
|
371
|
+
fs.writeFileSync(sourcePath, TWO_STEP_WORKFLOW, "utf8");
|
|
372
|
+
const result = runCli(["workflow", "create", "test-flow", "--force", "--from", sourcePath], env);
|
|
373
|
+
expect(result.status).toBe(0);
|
|
374
|
+
const json = JSON.parse(result.stdout);
|
|
375
|
+
expect(json.ok).toBe(true);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// 9. complete --state help text documents default (just ensure it runs)
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
describe("workflow complete --state default", () => {
|
|
382
|
+
test("complete without --state defaults to completed", () => {
|
|
383
|
+
const env = createWorkflowEnv();
|
|
384
|
+
setupWorkflow(env);
|
|
385
|
+
const started = runCli(["workflow", "start", "workflow:test-flow"], env);
|
|
386
|
+
expect(started.status).toBe(0);
|
|
387
|
+
const { run: startRun } = JSON.parse(started.stdout);
|
|
388
|
+
// No --state flag → should default to 'completed'
|
|
389
|
+
const completed = runCli(["workflow", "complete", startRun.id, "--step", "first"], env);
|
|
390
|
+
expect(completed.status).toBe(0);
|
|
391
|
+
const json = JSON.parse(completed.stdout);
|
|
392
|
+
const step = json.workflow.steps.find((s) => s.id === "first");
|
|
393
|
+
expect(step?.status).toBe("completed");
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { afterEach, beforeEach, expect, test } from "bun:test";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getDbPath } from "../../src/core/paths";
|
|
6
|
+
import { resetQuiet, resetVerbose, setVerbose } from "../../src/core/warn";
|
|
7
|
+
import { closeDatabase, openDatabase } from "../../src/indexer/db";
|
|
8
|
+
import { akmIndex } from "../../src/indexer/indexer";
|
|
9
|
+
let testConfigDir = "";
|
|
10
|
+
let testCacheDir = "";
|
|
11
|
+
const originalXdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
12
|
+
const originalXdgCacheHome = process.env.XDG_CACHE_HOME;
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
testConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-config-"));
|
|
15
|
+
testCacheDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-cache-"));
|
|
16
|
+
process.env.XDG_CONFIG_HOME = testConfigDir;
|
|
17
|
+
process.env.XDG_CACHE_HOME = testCacheDir;
|
|
18
|
+
const dbPath = getDbPath();
|
|
19
|
+
for (const f of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
20
|
+
try {
|
|
21
|
+
fs.unlinkSync(f);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* ignore */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Defensive: other test files may have left the warn module's quiet/verbose
|
|
28
|
+
// latches on. Reset both before each test so the noise-gate assertions read
|
|
29
|
+
// a clean state.
|
|
30
|
+
resetQuiet();
|
|
31
|
+
resetVerbose();
|
|
32
|
+
delete process.env.AKM_VERBOSE;
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
if (originalXdgConfigHome === undefined)
|
|
36
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
37
|
+
else
|
|
38
|
+
process.env.XDG_CONFIG_HOME = originalXdgConfigHome;
|
|
39
|
+
if (originalXdgCacheHome === undefined)
|
|
40
|
+
delete process.env.XDG_CACHE_HOME;
|
|
41
|
+
else
|
|
42
|
+
process.env.XDG_CACHE_HOME = originalXdgCacheHome;
|
|
43
|
+
if (testConfigDir) {
|
|
44
|
+
fs.rmSync(testConfigDir, { recursive: true, force: true });
|
|
45
|
+
testConfigDir = "";
|
|
46
|
+
}
|
|
47
|
+
if (testCacheDir) {
|
|
48
|
+
fs.rmSync(testCacheDir, { recursive: true, force: true });
|
|
49
|
+
testCacheDir = "";
|
|
50
|
+
}
|
|
51
|
+
resetVerbose();
|
|
52
|
+
delete process.env.AKM_VERBOSE;
|
|
53
|
+
});
|
|
54
|
+
function tmpStash() {
|
|
55
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-wf-idx-"));
|
|
56
|
+
fs.mkdirSync(path.join(dir, "workflows"), { recursive: true });
|
|
57
|
+
return dir;
|
|
58
|
+
}
|
|
59
|
+
function writeWorkflow(stashDir, name, content) {
|
|
60
|
+
const file = path.join(stashDir, "workflows", `${name}.md`);
|
|
61
|
+
fs.writeFileSync(file, content);
|
|
62
|
+
return file;
|
|
63
|
+
}
|
|
64
|
+
const VALID_WORKFLOW = `# Workflow: Ship Release
|
|
65
|
+
|
|
66
|
+
## Step: Validate
|
|
67
|
+
Step ID: validate
|
|
68
|
+
|
|
69
|
+
### Instructions
|
|
70
|
+
Confirm release notes are present.
|
|
71
|
+
`;
|
|
72
|
+
const BROKEN_WORKFLOW = `# Workflow: Bad
|
|
73
|
+
|
|
74
|
+
## Step: First
|
|
75
|
+
Step ID: first
|
|
76
|
+
### Instructions
|
|
77
|
+
do A
|
|
78
|
+
|
|
79
|
+
## Step: Second
|
|
80
|
+
Step ID: first
|
|
81
|
+
### Instructions
|
|
82
|
+
do B
|
|
83
|
+
`;
|
|
84
|
+
test("indexer admits valid workflows and writes their JSON to workflow_documents", async () => {
|
|
85
|
+
const stashDir = tmpStash();
|
|
86
|
+
writeWorkflow(stashDir, "good", VALID_WORKFLOW);
|
|
87
|
+
const result = await akmIndex({ stashDir, full: true });
|
|
88
|
+
expect(result.totalEntries).toBe(1);
|
|
89
|
+
const db = openDatabase();
|
|
90
|
+
try {
|
|
91
|
+
const row = db
|
|
92
|
+
.prepare(`SELECT wd.document_json, wd.schema_version, wd.source_path
|
|
93
|
+
FROM workflow_documents wd
|
|
94
|
+
JOIN entries e ON e.id = wd.entry_id
|
|
95
|
+
WHERE e.entry_type = 'workflow' AND e.entry_key LIKE ?`)
|
|
96
|
+
.get(`${stashDir}:workflow:%`);
|
|
97
|
+
expect(row).toBeDefined();
|
|
98
|
+
if (!row)
|
|
99
|
+
return;
|
|
100
|
+
expect(row.schema_version).toBe(1);
|
|
101
|
+
expect(row.source_path).toContain("good.md");
|
|
102
|
+
const doc = JSON.parse(row.document_json);
|
|
103
|
+
expect(doc.title).toBe("Ship Release");
|
|
104
|
+
expect(doc.steps).toHaveLength(1);
|
|
105
|
+
expect(doc.steps[0].instructions.text).toContain("Confirm release notes");
|
|
106
|
+
expect(doc.steps[0].source.start).toBeGreaterThan(0);
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
closeDatabase(db);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
test("indexer rejects broken workflows and surfaces every error in IndexResponse.warnings", async () => {
|
|
113
|
+
const stashDir = tmpStash();
|
|
114
|
+
writeWorkflow(stashDir, "good", VALID_WORKFLOW);
|
|
115
|
+
const brokenPath = writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
|
|
116
|
+
const result = await akmIndex({ stashDir, full: true });
|
|
117
|
+
expect(result.totalEntries).toBe(1); // only the good one
|
|
118
|
+
expect(result.warnings ?? []).toBeDefined();
|
|
119
|
+
const warnings = result.warnings ?? [];
|
|
120
|
+
// The broken workflow has a duplicate step ID; the warning string must
|
|
121
|
+
// mention the file and at least one of its errors.
|
|
122
|
+
const brokenWarning = warnings.find((w) => w.includes(brokenPath));
|
|
123
|
+
expect(brokenWarning).toBeDefined();
|
|
124
|
+
expect(brokenWarning).toMatch(/already used|Step ID/);
|
|
125
|
+
const db = openDatabase();
|
|
126
|
+
try {
|
|
127
|
+
const goodRow = db
|
|
128
|
+
.prepare(`SELECT 1 FROM workflow_documents wd
|
|
129
|
+
JOIN entries e ON e.id = wd.entry_id
|
|
130
|
+
WHERE e.entry_key = ?`)
|
|
131
|
+
.get(`${stashDir}:workflow:good`);
|
|
132
|
+
expect(goodRow).toBeDefined();
|
|
133
|
+
const badRow = db
|
|
134
|
+
.prepare(`SELECT 1 FROM workflow_documents wd
|
|
135
|
+
JOIN entries e ON e.id = wd.entry_id
|
|
136
|
+
WHERE e.entry_key = ?`)
|
|
137
|
+
.get(`${stashDir}:workflow:bad`);
|
|
138
|
+
expect(badRow).toBeFalsy();
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
closeDatabase(db);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// ── Workflow validation noise gate (issue #273) ─────────────────────────────
|
|
145
|
+
async function captureStderr(fn) {
|
|
146
|
+
const lines = [];
|
|
147
|
+
const originalWarn = console.warn.bind(console);
|
|
148
|
+
console.warn = (...args) => {
|
|
149
|
+
lines.push(args.map((a) => (typeof a === "string" ? a : JSON.stringify(a))).join(" "));
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const result = await fn();
|
|
153
|
+
return { result, lines };
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
console.warn = originalWarn;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
test("default verbosity emits one summary line, not per-spec workflow warnings", async () => {
|
|
160
|
+
const stashDir = tmpStash();
|
|
161
|
+
// Two broken workflows so we can prove the summary line is emitted instead
|
|
162
|
+
// of two separate per-spec warnings on stderr.
|
|
163
|
+
writeWorkflow(stashDir, "bad1", BROKEN_WORKFLOW);
|
|
164
|
+
writeWorkflow(stashDir, "bad2", BROKEN_WORKFLOW);
|
|
165
|
+
const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
|
|
166
|
+
const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
|
|
167
|
+
expect(perSpec).toHaveLength(0);
|
|
168
|
+
const summary = lines.filter((l) => l.includes("workflow specs skipped due to validation errors"));
|
|
169
|
+
expect(summary).toHaveLength(1);
|
|
170
|
+
expect(summary[0]).toMatch(/^2 workflow specs skipped/);
|
|
171
|
+
expect(summary[0]).toContain("--verbose");
|
|
172
|
+
expect(summary[0]).toContain("AKM_VERBOSE");
|
|
173
|
+
});
|
|
174
|
+
test("default verbosity uses singular 'workflow spec' when only one was skipped", async () => {
|
|
175
|
+
const stashDir = tmpStash();
|
|
176
|
+
writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
|
|
177
|
+
const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
|
|
178
|
+
const summary = lines.filter((l) => l.includes("workflow spec skipped"));
|
|
179
|
+
expect(summary).toHaveLength(1);
|
|
180
|
+
expect(summary[0]).toMatch(/^1 workflow spec skipped/);
|
|
181
|
+
});
|
|
182
|
+
test("--verbose flag restores per-spec workflow warnings and suppresses the summary", async () => {
|
|
183
|
+
const stashDir = tmpStash();
|
|
184
|
+
writeWorkflow(stashDir, "bad1", BROKEN_WORKFLOW);
|
|
185
|
+
writeWorkflow(stashDir, "bad2", BROKEN_WORKFLOW);
|
|
186
|
+
setVerbose(true);
|
|
187
|
+
const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
|
|
188
|
+
const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
|
|
189
|
+
expect(perSpec).toHaveLength(2);
|
|
190
|
+
const summary = lines.filter((l) => l.includes("workflow specs skipped due to validation errors"));
|
|
191
|
+
expect(summary).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
test("AKM_VERBOSE=1 restores per-spec output even with the verbose flag unset", async () => {
|
|
194
|
+
const stashDir = tmpStash();
|
|
195
|
+
writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
|
|
196
|
+
process.env.AKM_VERBOSE = "1";
|
|
197
|
+
const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
|
|
198
|
+
const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
|
|
199
|
+
expect(perSpec).toHaveLength(1);
|
|
200
|
+
const summary = lines.filter((l) => l.includes("workflow spec skipped"));
|
|
201
|
+
expect(summary).toHaveLength(0);
|
|
202
|
+
});
|
|
203
|
+
test("AKM_VERBOSE=0 hard-disables verbose output even when --verbose flag was set", async () => {
|
|
204
|
+
const stashDir = tmpStash();
|
|
205
|
+
writeWorkflow(stashDir, "bad", BROKEN_WORKFLOW);
|
|
206
|
+
setVerbose(true);
|
|
207
|
+
process.env.AKM_VERBOSE = "0";
|
|
208
|
+
const { lines } = await captureStderr(() => akmIndex({ stashDir, full: true }));
|
|
209
|
+
const perSpec = lines.filter((l) => l.startsWith("Skipped workflow "));
|
|
210
|
+
expect(perSpec).toHaveLength(0);
|
|
211
|
+
const summary = lines.filter((l) => l.includes("workflow spec skipped"));
|
|
212
|
+
expect(summary).toHaveLength(1);
|
|
213
|
+
});
|
package/docs/README.md
CHANGED
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
- [Getting Started](getting-started.md) -- Quick setup guide
|
|
7
7
|
- [Agent Install Guide](agents/agent-install.md) -- Step-by-step automated install for agents
|
|
8
8
|
- [Stash Maker's Guide](stash-makers.md) -- Build and share a stash on GitHub, npm, or a network directory
|
|
9
|
+
- [Wikis](wikis.md) -- Multi-wiki knowledge bases (Karpathy-style)
|
|
9
10
|
|
|
10
11
|
## Upgrading
|
|
11
12
|
|
|
13
|
+
- [v1 migration guide](migration/v1.md) -- The path from 0.x to v1.0
|
|
14
|
+
- [Release notes (latest: 0.7.0)](migration/release-notes/0.7.0.md) -- Per-release notes drop into `migration/release-notes/`
|
|
12
15
|
- [v0.5 → v0.6 migration guide](migration/v0.5-to-v0.6.md) -- Every breaking change with before/after code, publisher checklist, and troubleshooting
|
|
13
16
|
|
|
14
17
|
## Reference
|
|
@@ -28,3 +31,8 @@
|
|
|
28
31
|
- [Ref Format](technical/ref.md) -- Wire format for asset references
|
|
29
32
|
- [Test Coverage Guide](technical/test-coverage-guide.md) -- High-value testing areas
|
|
30
33
|
- [Core Principles](technical/akm-core-principles.md) -- Design principles and constraints
|
|
34
|
+
- [akm-bench](technical/benchmark.md) -- Search-quality benchmark suite
|
|
35
|
+
|
|
36
|
+
## Posts
|
|
37
|
+
|
|
38
|
+
- [Blog posts](posts/) -- Articles and posts about akm
|