simt-dev 0.7.0 → 0.8.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 +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
package/src/commands/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Command registry and dispatch.
|
|
2
2
|
//
|
|
3
|
-
// Every command named in docs/
|
|
3
|
+
// Every command named in docs/research/0002-simt-dev-cli.md section 5 is registered
|
|
4
4
|
// here from day one. A command that is declared but not yet implemented exits
|
|
5
5
|
// UNSUPPORTED with a pointer to its implementation step; it never silently
|
|
6
6
|
// succeeds. An agent that calls `simt compare` on an unreleased build must get a
|
|
@@ -8,111 +8,47 @@
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
EXIT,
|
|
11
|
+
jsonCaptureStart,
|
|
12
|
+
jsonCaptureTake,
|
|
13
|
+
jsonCommand,
|
|
11
14
|
emit,
|
|
12
15
|
emitJson,
|
|
13
16
|
fail,
|
|
17
|
+
clearWarnings,
|
|
18
|
+
note,
|
|
19
|
+
warn,
|
|
14
20
|
isJson,
|
|
15
21
|
jsonFailMode,
|
|
16
22
|
jsonIndentDefault,
|
|
23
|
+
maxBytesLimit,
|
|
24
|
+
withOutputState,
|
|
17
25
|
} from '../output/index.mjs';
|
|
18
26
|
import { help } from './help.mjs';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
indexCommand as skillsIndex,
|
|
22
|
-
findCommand as skillsFind,
|
|
23
|
-
briefCommand as skillsBrief,
|
|
24
|
-
showCommand as skillsShow,
|
|
25
|
-
whyCommand as skillsWhy,
|
|
26
|
-
evalCommand as skillsEval,
|
|
27
|
-
} from './skills.mjs';
|
|
28
|
-
import { briefCommand } from './brief.mjs';
|
|
29
|
-
import { syncCommand, checkCommand } from './sync.mjs';
|
|
30
|
-
import { initCommand } from './init.mjs';
|
|
31
|
-
import { ciCommand } from './ci.mjs';
|
|
32
|
-
import { hooksCommand } from './hooks.mjs';
|
|
33
|
-
import { agentHookCommand, agentHooksCommand } from './agenthook.mjs';
|
|
34
|
-
import { agentsCommand } from './agent.mjs';
|
|
35
|
-
import { compareCommand } from './compare.mjs';
|
|
36
|
-
import { upgradeCommand } from './upgrade.mjs';
|
|
37
|
-
import { mcpCommand } from './mcp.mjs';
|
|
38
|
-
import { pluginCommand } from './plugin.mjs';
|
|
39
|
-
import { packCommand } from './pack.mjs';
|
|
40
|
-
import { installCommand } from './skillsinstall.mjs';
|
|
41
|
-
import { compileCommand } from './skillscompile.mjs';
|
|
42
|
-
import { decisionsCommand as rulesDecisions, listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
43
|
-
import { tokensCommand } from './tokens.mjs';
|
|
44
|
-
import { harnessCommand, harnessLoadersCommand } from './harness.mjs';
|
|
45
|
-
import {
|
|
46
|
-
benchMineCommand,
|
|
47
|
-
benchRunCommand,
|
|
48
|
-
benchReportCommand,
|
|
49
|
-
benchAblateCommand,
|
|
50
|
-
benchProbesCommand,
|
|
51
|
-
benchVariantsCommand,
|
|
52
|
-
benchCriticCommand,
|
|
53
|
-
} from './bench.mjs';
|
|
54
|
-
import { planCommand } from './plan.mjs';
|
|
55
|
-
import { sliceCommand } from './slice.mjs';
|
|
56
|
-
import { leaseCommand } from './lease.mjs';
|
|
57
|
-
import { sharedPlanCommand, sharedInstallCommand } from './shared.mjs';
|
|
58
|
-
import { runDoctor } from './doctor.mjs';
|
|
59
|
-
import { statusCommand } from './status.mjs';
|
|
60
|
-
import { evidenceCommand } from './evidence.mjs';
|
|
61
|
-
import { indexCommand } from './repoindex.mjs';
|
|
62
|
-
import { mapCommand } from './map.mjs';
|
|
63
|
-
import { testsCommand } from './tests.mjs';
|
|
64
|
-
import { whyCommand } from './why.mjs';
|
|
65
|
-
import { gateCommand } from './gate.mjs';
|
|
66
|
-
import { impactCommand } from './impact.mjs';
|
|
67
|
-
import { lessonsCaptureCommand, lessonsListCommand, lessonsReviewCommand } from './lessons.mjs';
|
|
68
|
-
import { decideCommand } from './decide.mjs';
|
|
69
|
-
import { explainCommand } from './explain.mjs';
|
|
70
|
-
import { undoCommand } from './undo.mjs';
|
|
71
|
-
import { prBodyCommand } from './prbody.mjs';
|
|
72
|
-
import { ignoreCommand } from './ignore.mjs';
|
|
73
|
-
import { completionsCommand } from './completions.mjs';
|
|
74
|
-
import { reviewCommand } from './review.mjs';
|
|
75
|
-
import { localDoctorCommand } from './local.mjs';
|
|
76
|
-
import { distillCommand } from './distill.mjs';
|
|
27
|
+
import { ALIASES, GROUP_DEFAULTS, SPEC, SPEC_PATHS } from './spec.mjs';
|
|
77
28
|
import { parse, rawJsonFlag } from '../argv.mjs';
|
|
78
29
|
import { CLI } from '../name.mjs';
|
|
30
|
+
import { packageVersion } from '../package-version.mjs';
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Groups whose subcommand may be omitted, and the default for each. Read from the spec
|
|
35
|
+
* (review 0003 Q60) rather than kept here: this was the second copy of a fact the spec
|
|
36
|
+
* owns, and the reason for each default now sits on the row that declares it.
|
|
37
|
+
*/
|
|
38
|
+
export const GROUPS = GROUP_DEFAULTS;
|
|
79
39
|
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// first: a declared-but-unimplemented command must exit UNSUPPORTED with a pointer to
|
|
84
|
-
// its step, never silently succeed with empty output an agent will read as a result.
|
|
85
|
-
const STEPS = new Map([]);
|
|
40
|
+
/** path -> the flags the spec declares for it. The parser knows every flag; this is
|
|
41
|
+
* what one command reads, and the difference is what the caller is told (Q62). */
|
|
42
|
+
const DECLARED = new Map(SPEC.map((row) => [row.path, new Set(row.flags ?? [])]));
|
|
86
43
|
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// `start` is the default because with the id it is missing it fails with a usage
|
|
96
|
-
// error before anything is created — the one default here that cannot mutate.
|
|
97
|
-
['slice', 'start'],
|
|
98
|
-
['agent-hooks', 'status'],
|
|
99
|
-
// A group like `agent-hooks`: `install` writes the agent file into each harness's
|
|
100
|
-
// own directory, `status` reports what each harness carries. The default is the
|
|
101
|
-
// read, the way `agent-hooks`'s is.
|
|
102
|
-
['agents', 'status'],
|
|
103
|
-
['harness', 'detect'],
|
|
104
|
-
['bench', 'mine'],
|
|
105
|
-
// ADR-084. `shared` is the one group whose default writes nothing, and it is `plan` for
|
|
106
|
-
// the same reason `skills`'s is `index`: the read answers "what would this do, and
|
|
107
|
-
// where", and `install` — the subcommand that touches a reader's home directory — is the
|
|
108
|
-
// one that needs `--yes` before it does anything.
|
|
109
|
-
['shared', 'plan'],
|
|
110
|
-
['lessons', 'list'],
|
|
111
|
-
['local', 'doctor'],
|
|
112
|
-
// Plan 0003 R45. The default is the read: `pack` alone lints the working
|
|
113
|
-
// directory, the way `skills` alone indexes and `graph` alone lints.
|
|
114
|
-
['pack', 'lint'],
|
|
115
|
-
]);
|
|
44
|
+
/** Flags dispatch reads for every command: the envelope and the guard rails. */
|
|
45
|
+
/** path -> the values a flag of that path accepts, where the set is closed. */
|
|
46
|
+
const VALUES = new Map(SPEC.map((row) => [row.path, row.values ?? {}]));
|
|
47
|
+
|
|
48
|
+
// `dashdash` is the parser's record that `--` appeared, not a flag any reader typed:
|
|
49
|
+
// it is an envelope fact like `root`, so the per-command declaration check skips it
|
|
50
|
+
// and lets the one command that reads it (slice start) act on it.
|
|
51
|
+
export const GLOBAL_FLAGS = new Set(['root', 'json', 'pretty', 'help', 'version', 'maxBytes', 'dashdash']);
|
|
116
52
|
|
|
117
53
|
/**
|
|
118
54
|
* Every command this binary dispatches, in one place.
|
|
@@ -124,126 +60,265 @@ const GROUPS = new Map([
|
|
|
124
60
|
* an agent choosing a command — the one consumer with no way to discover the omission
|
|
125
61
|
* except by guessing.
|
|
126
62
|
*
|
|
127
|
-
* Module scope so `test/cli.test.mjs` can assert help against it.
|
|
128
|
-
*
|
|
63
|
+
* Module scope so `test/cli.test.mjs` can assert help against it. Each value is a
|
|
64
|
+
* loader rather than a handler: dispatch awaits the one the matched path names, so a
|
|
65
|
+
* command pays for its own module graph and not for the other seventy-five (review 0003
|
|
66
|
+
* Q64). A loader that never resolves is a command that cannot run, and `test/spec.test.mjs`
|
|
67
|
+
* still holds the key set against the spec.
|
|
129
68
|
*/
|
|
130
69
|
export const IMPLEMENTED = {
|
|
131
|
-
brief:
|
|
132
|
-
|
|
70
|
+
brief: async () => {
|
|
71
|
+
const { briefCommand } = await import('./brief.mjs');
|
|
72
|
+
return (args, flags) => briefCommand(args, flags, COMMANDS);
|
|
73
|
+
},
|
|
74
|
+
doctor: async () => (await import('./doctor.mjs')).runDoctor,
|
|
133
75
|
// Plan 0003 R48. A bare command: the only mode is `--line`, so there is no
|
|
134
76
|
// group and no default subcommand to resolve.
|
|
135
|
-
status: statusCommand,
|
|
136
|
-
'graph lint': lintCommand,
|
|
137
|
-
'skills index':
|
|
138
|
-
'skills find':
|
|
139
|
-
'skills brief':
|
|
140
|
-
'skills show':
|
|
141
|
-
'skills why':
|
|
142
|
-
'skills eval':
|
|
143
|
-
'skills install': installCommand,
|
|
144
|
-
'skills compile': compileCommand,
|
|
145
|
-
'rules decisions':
|
|
146
|
-
'rules list':
|
|
147
|
-
'rules show':
|
|
148
|
-
'rules why':
|
|
149
|
-
sync: syncCommand,
|
|
150
|
-
check: checkCommand,
|
|
151
|
-
'plan status': (
|
|
152
|
-
|
|
153
|
-
|
|
77
|
+
status: async () => (await import('./status.mjs')).statusCommand,
|
|
78
|
+
'graph lint': async () => (await import('./graph.mjs')).lintCommand,
|
|
79
|
+
'skills index': async () => (await import('./skills.mjs')).indexCommand,
|
|
80
|
+
'skills find': async () => (await import('./skills.mjs')).findCommand,
|
|
81
|
+
'skills brief': async () => (await import('./skills.mjs')).briefCommand,
|
|
82
|
+
'skills show': async () => (await import('./skills.mjs')).showCommand,
|
|
83
|
+
'skills why': async () => (await import('./skills.mjs')).whyCommand,
|
|
84
|
+
'skills eval': async () => (await import('./skills.mjs')).evalCommand,
|
|
85
|
+
'skills install': async () => (await import('./skillsinstall.mjs')).installCommand,
|
|
86
|
+
'skills compile': async () => (await import('./skillscompile.mjs')).compileCommand,
|
|
87
|
+
'rules decisions': async () => (await import('./rules.mjs')).decisionsCommand,
|
|
88
|
+
'rules list': async () => (await import('./rules.mjs')).listCommand,
|
|
89
|
+
'rules show': async () => (await import('./rules.mjs')).showCommand,
|
|
90
|
+
'rules why': async () => (await import('./rules.mjs')).whyCommand,
|
|
91
|
+
sync: async () => (await import('./sync.mjs')).syncCommand,
|
|
92
|
+
check: async () => (await import('./sync.mjs')).checkCommand,
|
|
93
|
+
'plan status': async () => {
|
|
94
|
+
const { planCommand } = await import('./plan.mjs');
|
|
95
|
+
return (args, flags) => planCommand('status', args, flags);
|
|
96
|
+
},
|
|
97
|
+
'plan next': async () => {
|
|
98
|
+
const { planCommand } = await import('./plan.mjs');
|
|
99
|
+
return (args, flags) => planCommand('next', args, flags);
|
|
100
|
+
},
|
|
101
|
+
'plan done': async () => {
|
|
102
|
+
const { planCommand } = await import('./plan.mjs');
|
|
103
|
+
return (args, flags) => planCommand('done', args, flags);
|
|
104
|
+
},
|
|
154
105
|
// Plan 0003 R53. A group: `start` isolates the work on a branch with its brief,
|
|
155
106
|
// `finish` runs the gates, commits and records the evidence, and `abandon`
|
|
156
107
|
// (review 0003 Q23) takes the slice back down. All three take the slice id
|
|
157
108
|
// as a positional.
|
|
158
|
-
'slice start': (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
'
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
|
|
173
|
-
|
|
109
|
+
'slice start': async () => {
|
|
110
|
+
const { sliceCommand } = await import('./slice.mjs');
|
|
111
|
+
return (args, flags) => sliceCommand('start', args, flags);
|
|
112
|
+
},
|
|
113
|
+
'slice finish': async () => {
|
|
114
|
+
const { sliceCommand } = await import('./slice.mjs');
|
|
115
|
+
return (args, flags) => sliceCommand('finish', args, flags);
|
|
116
|
+
},
|
|
117
|
+
'slice abandon': async () => {
|
|
118
|
+
const { sliceCommand } = await import('./slice.mjs');
|
|
119
|
+
return (args, flags) => sliceCommand('abandon', args, flags);
|
|
120
|
+
},
|
|
121
|
+
'shared plan': async () => (await import('./shared.mjs')).sharedPlanCommand,
|
|
122
|
+
'shared install': async () => (await import('./shared.mjs')).sharedInstallCommand,
|
|
123
|
+
evidence: async () => (await import('./evidence.mjs')).evidenceCommand,
|
|
124
|
+
index: async () => (await import('./repoindex.mjs')).indexCommand,
|
|
125
|
+
map: async () => (await import('./map.mjs')).mapCommand,
|
|
126
|
+
tests: async () => (await import('./tests.mjs')).testsCommand,
|
|
127
|
+
why: async () => {
|
|
128
|
+
const { whyCommand } = await import('./why.mjs');
|
|
129
|
+
return (args, flags) => whyCommand(args, flags, COMMANDS);
|
|
130
|
+
},
|
|
131
|
+
gate: async () => (await import('./gate.mjs')).gateCommand,
|
|
132
|
+
impact: async () => {
|
|
133
|
+
const { impactCommand } = await import('./impact.mjs');
|
|
134
|
+
return (args, flags) => impactCommand(args, flags, COMMANDS);
|
|
135
|
+
},
|
|
136
|
+
'lessons capture': async () => (await import('./lessons.mjs')).lessonsCaptureCommand,
|
|
137
|
+
'lessons list': async () => (await import('./lessons.mjs')).lessonsListCommand,
|
|
138
|
+
'lessons review': async () => (await import('./lessons.mjs')).lessonsReviewCommand,
|
|
139
|
+
decide: async () => (await import('./decide.mjs')).decideCommand,
|
|
174
140
|
// Plan 0003 R46. A bare command: the code is a positional, so there is no
|
|
175
141
|
// group and no default subcommand to resolve.
|
|
176
|
-
explain: explainCommand,
|
|
142
|
+
explain: async () => (await import('./explain.mjs')).explainCommand,
|
|
177
143
|
// Plan 0003 R49. A bare command: restoring the last journaled set needs no
|
|
178
144
|
// subcommand, so there is no group and no default to resolve.
|
|
179
|
-
undo: undoCommand,
|
|
145
|
+
undo: async () => (await import('./undo.mjs')).undoCommand,
|
|
180
146
|
// Plan 0003 R55. A bare command: the body is derived from the tree, the
|
|
181
147
|
// ledger and the last check, so there is no subcommand to resolve and no
|
|
182
148
|
// write to confirm.
|
|
183
|
-
'pr-body': prBodyCommand,
|
|
149
|
+
'pr-body': async () => (await import('./prbody.mjs')).prBodyCommand,
|
|
184
150
|
// Plan 0003 R52. A bare command: the one list is `.simtignore`, so there is no
|
|
185
151
|
// group and no default subcommand to resolve.
|
|
186
|
-
ignore: ignoreCommand,
|
|
152
|
+
ignore: async () => (await import('./ignore.mjs')).ignoreCommand,
|
|
187
153
|
// Plan 0003 R54. A bare command: paths claim, no paths list, so the argument itself
|
|
188
154
|
// is the mode and there is no group to resolve.
|
|
189
|
-
lease: leaseCommand,
|
|
190
|
-
|
|
155
|
+
lease: async () => (await import('./lease.mjs')).leaseCommand,
|
|
156
|
+
// Review 0003 Q60. The README's command list is the last copy of the command table
|
|
157
|
+
// the spec owns; the renderer lives in src/docs so a test can hold the file to it.
|
|
158
|
+
'docs readme': async () => {
|
|
159
|
+
const { docsCommand } = await import('./docs.mjs');
|
|
160
|
+
return (args, flags) => docsCommand('readme', args, flags);
|
|
161
|
+
},
|
|
162
|
+
distill: async () => (await import('./distill.mjs')).distillCommand,
|
|
191
163
|
// R25 machinery needs the runnable command names for Affects command tokens,
|
|
192
164
|
// same wiring `why` and `impact` get through their lambdas.
|
|
193
|
-
review: (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
'
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
165
|
+
review: async () => {
|
|
166
|
+
const { reviewCommand } = await import('./review.mjs');
|
|
167
|
+
return (args, flags) => reviewCommand(args, flags, COMMANDS);
|
|
168
|
+
},
|
|
169
|
+
'local doctor': async () => (await import('./local.mjs')).localDoctorCommand,
|
|
170
|
+
'evidence check': async () => {
|
|
171
|
+
const { evidenceCommand } = await import('./evidence.mjs');
|
|
172
|
+
return (args, flags) => evidenceCommand(['check', ...args], flags);
|
|
173
|
+
},
|
|
174
|
+
init: async () => (await import('./init.mjs')).initCommand,
|
|
175
|
+
ci: async () => (await import('./ci.mjs')).ciCommand,
|
|
176
|
+
'hooks install': async () => {
|
|
177
|
+
const { hooksCommand } = await import('./hooks.mjs');
|
|
178
|
+
return (args, flags) => hooksCommand('install', args, flags);
|
|
179
|
+
},
|
|
180
|
+
'hooks status': async () => {
|
|
181
|
+
const { hooksCommand } = await import('./hooks.mjs');
|
|
182
|
+
return (args, flags) => hooksCommand('status', args, flags);
|
|
183
|
+
},
|
|
184
|
+
'hooks run': async () => {
|
|
185
|
+
const { hooksCommand } = await import('./hooks.mjs');
|
|
186
|
+
return (args, flags) => hooksCommand('run', args, flags);
|
|
187
|
+
},
|
|
188
|
+
'agent-hook': async () => (await import('./agenthook.mjs')).agentHookCommand,
|
|
189
|
+
'agent-hooks install': async () => {
|
|
190
|
+
const { agentHooksCommand } = await import('./agenthook.mjs');
|
|
191
|
+
return (args, flags) => agentHooksCommand('install', args, flags);
|
|
192
|
+
},
|
|
193
|
+
'agent-hooks status': async () => {
|
|
194
|
+
const { agentHooksCommand } = await import('./agenthook.mjs');
|
|
195
|
+
return (args, flags) => agentHooksCommand('status', args, flags);
|
|
196
|
+
},
|
|
204
197
|
// ADR-111. The one agent this tool ships, written into each harness's own agents
|
|
205
198
|
// directory. A group like `agent-hooks`: `install` writes, `status` reads.
|
|
206
|
-
'agents install': (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
'
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
'
|
|
217
|
-
'
|
|
199
|
+
'agents install': async () => {
|
|
200
|
+
const { agentsCommand } = await import('./agent.mjs');
|
|
201
|
+
return (args, flags) => agentsCommand('install', args, flags);
|
|
202
|
+
},
|
|
203
|
+
'agents status': async () => {
|
|
204
|
+
const { agentsCommand } = await import('./agent.mjs');
|
|
205
|
+
return (args, flags) => agentsCommand('status', args, flags);
|
|
206
|
+
},
|
|
207
|
+
compare: async () => (await import('./compare.mjs')).compareCommand,
|
|
208
|
+
tokens: async () => (await import('./tokens.mjs')).tokensCommand,
|
|
209
|
+
'harness detect': async () => (await import('./harness.mjs')).harnessCommand,
|
|
210
|
+
'harness loaders': async () => (await import('./harness.mjs')).harnessLoadersCommand,
|
|
211
|
+
'bench mine': async () => (await import('./bench.mjs')).benchMineCommand,
|
|
212
|
+
'bench run': async () => (await import('./bench.mjs')).benchRunCommand,
|
|
213
|
+
'bench report': async () => (await import('./bench.mjs')).benchReportCommand,
|
|
214
|
+
'bench ablate': async () => (await import('./bench.mjs')).benchAblateCommand,
|
|
215
|
+
'bench probes': async () => (await import('./bench.mjs')).benchProbesCommand,
|
|
216
|
+
'bench variants': async () => (await import('./bench.mjs')).benchVariantsCommand,
|
|
218
217
|
// plan 0003 R39. The critic's acceptance bench measures BLOCK-precision and
|
|
219
218
|
// recall over a labelled set; until it reports its floor, the critic entries
|
|
220
219
|
// stay advisory and no gate reads them. COMMANDS rides along so the brief
|
|
221
220
|
// resolves Affects command tokens the way `simt why` does.
|
|
222
|
-
'bench critic': (
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
'bench critic': async () => {
|
|
222
|
+
const { benchCriticCommand } = await import('./bench.mjs');
|
|
223
|
+
return (args, flags) => benchCriticCommand(args, flags, COMMANDS);
|
|
224
|
+
},
|
|
225
|
+
// review 0003 Q48. Sealed corpus transfer: export writes one signed JSON
|
|
226
|
+
// document carrying every task and both hidden patches; import verifies the
|
|
227
|
+
// seal before it touches the vault.
|
|
228
|
+
'bench export': async () => (await import('./bench.mjs')).benchExportCommand,
|
|
229
|
+
'bench import': async () => (await import('./bench.mjs')).benchImportCommand,
|
|
230
|
+
upgrade: async () => (await import('./upgrade.mjs')).upgradeCommand,
|
|
231
|
+
mcp: async () => (await import('./mcp.mjs')).mcpCommand,
|
|
225
232
|
// plan 0003 R43. A bare command: the kind is a positional, so there is no group and
|
|
226
233
|
// no default subcommand to resolve.
|
|
227
|
-
plugin: pluginCommand,
|
|
234
|
+
plugin: async () => (await import('./plugin.mjs')).pluginCommand,
|
|
228
235
|
// Plan 0003 R51. A bare command: the shell is a positional. `COMMANDS` rides in
|
|
229
236
|
// from here so the engine keeps no second copy of the registry it completes.
|
|
230
|
-
completions: (
|
|
237
|
+
completions: async () => {
|
|
238
|
+
const { completionsCommand } = await import('./completions.mjs');
|
|
239
|
+
return (args, flags) => completionsCommand(args, flags, COMMANDS);
|
|
240
|
+
},
|
|
231
241
|
// Plan 0003 R45. A group, not a bare command: `new` and `publish` write (or
|
|
232
242
|
// publish) and `lint` reads, and the dispatcher routes all three through one
|
|
233
243
|
// subcommand switch the way `hooks` does.
|
|
234
|
-
'pack new': (
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
'pack new': async () => {
|
|
245
|
+
const { packCommand } = await import('./pack.mjs');
|
|
246
|
+
return (args, flags) => packCommand('new', args, flags);
|
|
247
|
+
},
|
|
248
|
+
'pack lint': async () => {
|
|
249
|
+
const { packCommand } = await import('./pack.mjs');
|
|
250
|
+
return (args, flags) => packCommand('lint', args, flags);
|
|
251
|
+
},
|
|
252
|
+
'pack publish': async () => {
|
|
253
|
+
const { packCommand } = await import('./pack.mjs');
|
|
254
|
+
return (args, flags) => packCommand('publish', args, flags);
|
|
255
|
+
},
|
|
237
256
|
};
|
|
238
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Resolve one command path to its handler, its declared flags and its closed value sets.
|
|
260
|
+
*
|
|
261
|
+
* The library surface needs the same answer `dispatch` gets and must not build a second
|
|
262
|
+
* registry to get it: a command reachable from the CLI and absent from `run` is the drift
|
|
263
|
+
* `test/spec.test.mjs` exists to catch, and it would be invisible here (review 0003 Q63).
|
|
264
|
+
*/
|
|
265
|
+
export async function resolveCommand(path) {
|
|
266
|
+
const load = IMPLEMENTED[path];
|
|
267
|
+
if (!load) return null;
|
|
268
|
+
return {
|
|
269
|
+
run: await load(),
|
|
270
|
+
declared: DECLARED.get(path) ?? new Set(),
|
|
271
|
+
values: VALUES.get(path) ?? {},
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The release that turns the tolerated flag into a usage error (review 0003 Q62). */
|
|
276
|
+
export const DEPRECATION_RELEASE = '0.9.0';
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Whether a tolerance that ends at `removedIn` has ended, comparing the way versions
|
|
280
|
+
* are numbered rather than the way strings are ordered: 0.10.0 is later than 0.9.0, and
|
|
281
|
+
* a string comparison says the opposite. An unreadable version refuses nothing - a build
|
|
282
|
+
* whose version cannot be read has not outlived anything, and guessing would break it.
|
|
283
|
+
*/
|
|
284
|
+
export function refusesAt(removedIn, version) {
|
|
285
|
+
const parse = (value) => {
|
|
286
|
+
const parts = String(value ?? '').split('.');
|
|
287
|
+
if (parts.length < 2 || parts.some((part) => !/^\d+$/.test(part))) return null;
|
|
288
|
+
return parts.map(Number);
|
|
289
|
+
};
|
|
290
|
+
const limit = parse(removedIn);
|
|
291
|
+
const current = parse(version);
|
|
292
|
+
if (limit === null || current === null) return false;
|
|
293
|
+
const [a, b] = [current[0] - limit[0], current[1] - limit[1]];
|
|
294
|
+
if (a !== 0) return a > 0;
|
|
295
|
+
return b >= 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
239
298
|
/**
|
|
240
299
|
* The dispatchable command paths, sorted. The authoritative answer to "what can this
|
|
241
|
-
* binary do", as opposed to the answer `--help` gives
|
|
300
|
+
* binary do", as opposed to the answer `--help` gives - and the same list the spec
|
|
301
|
+
* declares, so a command cannot be dispatchable and undeclared.
|
|
242
302
|
*/
|
|
243
|
-
export const COMMANDS =
|
|
303
|
+
export const COMMANDS = SPEC_PATHS;
|
|
244
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Dispatch one argv.
|
|
307
|
+
*
|
|
308
|
+
* The state is per call: an MCP server and the tests both dispatch more than once in a
|
|
309
|
+
* process, and a shared envelope let one run's ceiling, notices and command path land in
|
|
310
|
+
* another run's document (review 0003 Q63/Q65).
|
|
311
|
+
*/
|
|
245
312
|
export async function dispatch(argv) {
|
|
313
|
+
return withOutputState(() => dispatchInState(argv));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function dispatchInState(argv) {
|
|
246
317
|
const { flags, positional, parseError } = parseOrExit(argv);
|
|
318
|
+
// One run, one set of notices: a process can dispatch repeatedly (review 0003 Q62).
|
|
319
|
+
clearWarnings();
|
|
320
|
+
// Cleared per run for the same reason the notices are: a process can dispatch twice.
|
|
321
|
+
jsonCommand(null);
|
|
247
322
|
|
|
248
323
|
// The JSON shape is decided once, here, and every command inherits it. Threading a
|
|
249
324
|
// `--pretty` through twenty-five call sites would mean twenty-five places to forget,
|
|
@@ -254,6 +329,10 @@ export async function dispatch(argv) {
|
|
|
254
329
|
// command answers in the same shape a working one does, and the caller's parse
|
|
255
330
|
// never sees an empty document.
|
|
256
331
|
jsonIndentDefault(flags.pretty === true);
|
|
332
|
+
// The envelope owns both presentation flags. A command that decided its own ceiling or
|
|
333
|
+
// its own indentation could disagree with the flag the caller passed, and the caller has
|
|
334
|
+
// no way to see which one won (review 0003 Q65).
|
|
335
|
+
maxBytesLimit(flags.maxBytes);
|
|
257
336
|
jsonFailMode(flags.json === true);
|
|
258
337
|
|
|
259
338
|
// A parse failure is already the whole answer: report it once, in the asked-for
|
|
@@ -263,8 +342,12 @@ export async function dispatch(argv) {
|
|
|
263
342
|
// Help is shown only when explicitly asked for. An unknown command must fail with
|
|
264
343
|
// a usage code, not print 40 lines of prose and exit 0 — an agent that mistyped
|
|
265
344
|
// needs to be told so, not handed a wall of text it may misread as output.
|
|
266
|
-
|
|
267
|
-
|
|
345
|
+
// `-h` is the alias a reader types first, and it used to reach `unknown command '-h'`.
|
|
346
|
+
if (flags.help || flags.version || positional[0] === 'help' || positional[0] === '-h') {
|
|
347
|
+
// The argv is parsed once, here, and help renders what that parse produced: it used
|
|
348
|
+
// to parse the same argv again, so `simt --help` paid for two parses and could in
|
|
349
|
+
// principle answer a different question than the dispatcher asked (review 0003 Q83).
|
|
350
|
+
return help(argv, { flags, positional, parseError });
|
|
268
351
|
}
|
|
269
352
|
|
|
270
353
|
const [first, second] = positional;
|
|
@@ -277,31 +360,75 @@ export async function dispatch(argv) {
|
|
|
277
360
|
return fail(`unknown command \`${[first, second].filter(Boolean).join(' ')}\``, EXIT.USAGE);
|
|
278
361
|
}
|
|
279
362
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// simt-dev: they run this tool, not its source tree. The step number is the
|
|
284
|
-
// stable handle; the file that documents it is an implementation detail.
|
|
285
|
-
return fail(
|
|
286
|
-
`\`${CLI} ${path}\` is declared but not implemented`,
|
|
287
|
-
EXIT.UNSUPPORTED,
|
|
288
|
-
`implementation step ${step} is not built yet`,
|
|
289
|
-
);
|
|
290
|
-
}
|
|
363
|
+
// One path, stated once, by the only code that knows which one resolved.
|
|
364
|
+
jsonCommand(path);
|
|
365
|
+
|
|
291
366
|
const args = positional.slice(offset);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
367
|
+
|
|
368
|
+
// Review 0003 Q62, deprecation window. The parser accepts every flag for every
|
|
369
|
+
// command, so `status --frozen` ran, ignored the flag and reported success - the
|
|
370
|
+
// caller's intent discarded with nothing to notice. A flag the spec does not declare
|
|
371
|
+
// for this path is named on stderr and still runs for one release; the next release
|
|
372
|
+
// makes it EXIT.USAGE. `--json` has no warnings array yet, so the JSON half of the
|
|
373
|
+
// window waits on the envelope (Q65).
|
|
374
|
+
const declared = DECLARED.get(path);
|
|
375
|
+
const refusing = refusesAt(DEPRECATION_RELEASE, packageVersion());
|
|
376
|
+
for (const key of Object.keys(flags)) {
|
|
377
|
+
if (!declared || GLOBAL_FLAGS.has(key)) continue;
|
|
378
|
+
const name = key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
379
|
+
if (declared.has(name)) continue;
|
|
380
|
+
if (refusing) {
|
|
381
|
+
return fail(`--${name} is not a flag of \`${path}\`; it was tolerated until ${DEPRECATION_RELEASE} and is refused now`, EXIT.USAGE);
|
|
382
|
+
}
|
|
383
|
+
const message = `--${name} is not a flag of \`${path}\`; it was ignored, and naming one this command does not take is a usage error from ${DEPRECATION_RELEASE}`;
|
|
384
|
+
warn(message);
|
|
385
|
+
if (!isJson(flags)) note(message);
|
|
386
|
+
}
|
|
387
|
+
const load = IMPLEMENTED[path];
|
|
388
|
+
if (!load) return fail(`unknown command \`${path}\``, EXIT.USAGE);
|
|
389
|
+
// Import only the module the matched path needs. The registry used to pull every
|
|
390
|
+
// command's module graph - detection, projection, the bench corpus - before dispatch
|
|
391
|
+
// began, which cost 36.5 ms on a `status --line` that reads one file (review 0003 Q64).
|
|
392
|
+
// A closed set is refused before the command runs, so the caller learns the valid
|
|
393
|
+
// values from the refusal and every reader of the spec sees the same set (review
|
|
394
|
+
// 0003 Q62). Unset is not the same as invalid: the default stays the command's.
|
|
395
|
+
for (const [flag, allowed] of Object.entries(VALUES.get(path) ?? {})) {
|
|
396
|
+
const value = flags[flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase())];
|
|
397
|
+
if (value !== undefined && !allowed.includes(value)) {
|
|
398
|
+
return fail(`--${flag} takes ${allowed.join(', ')}; received ${value}`, EXIT.USAGE);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const run = await load();
|
|
403
|
+
if (!isJson(flags)) return run(args, flags);
|
|
404
|
+
// Capture, then write once: the envelope names an exit code that does not exist until
|
|
405
|
+
// the command returns, and a document written before it is a document that can lie.
|
|
406
|
+
jsonCaptureStart();
|
|
407
|
+
const code = await run(args, flags);
|
|
408
|
+
const docs = jsonCaptureTake() ?? [];
|
|
409
|
+
const [only] = docs;
|
|
410
|
+
if (docs.length === 1 && only.value !== null && typeof only.value === 'object' && !Array.isArray(only.value)) {
|
|
411
|
+
emitJson({ ...only.value, ok: only.value.ok ?? code === 0, exitCode: code });
|
|
412
|
+
return code;
|
|
413
|
+
}
|
|
414
|
+
// A stream of documents (NDJSON) has no single place for one exit code; each is written
|
|
415
|
+
// as the command produced it, and the process exit status still carries the code.
|
|
416
|
+
for (const doc of docs) emitJson(doc.value);
|
|
417
|
+
return code;
|
|
295
418
|
}
|
|
296
419
|
|
|
297
420
|
/**
|
|
298
421
|
* Normalise argv into a registry key, or null when unrecognised.
|
|
299
422
|
* A group with an omitted subcommand resolves to that group's default, so
|
|
300
423
|
* `simt skills` is `simt skills index`.
|
|
424
|
+
*
|
|
425
|
+
* An alias resolves here rather than as a second registry entry: the spec path stays the
|
|
426
|
+
* one dispatch key, so an alias cannot load a different handler or escape the flag
|
|
427
|
+
* declaration the path carries (review 0003 Q81).
|
|
301
428
|
*/
|
|
302
429
|
function commandPath(first, second) {
|
|
303
430
|
if (first === undefined) return null;
|
|
304
|
-
if (!GROUPS.has(first)) return first;
|
|
431
|
+
if (!GROUPS.has(first)) return ALIASES.get(first) ?? first;
|
|
305
432
|
|
|
306
433
|
const group = GROUPS.get(first);
|
|
307
434
|
if (second === undefined) return `${first} ${group}`;
|
|
@@ -315,10 +442,8 @@ function commandPath(first, second) {
|
|
|
315
442
|
// `doctor` is not a group and this branch is only reached when `GROUPS.has(first)`.
|
|
316
443
|
// Two lists of the same fact is the defect S15 was about in the help text.
|
|
317
444
|
const key = `${first} ${second}`;
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
return key;
|
|
445
|
+
if (key in IMPLEMENTED) return key;
|
|
446
|
+
return ALIASES.get(key) ?? null;
|
|
322
447
|
}
|
|
323
448
|
|
|
324
449
|
function parseOrExit(argv) {
|