simt-dev 0.7.1 → 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 +36 -1
- package/README.md +169 -80
- package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
- 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-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 +3 -1
- package/rules/mdc/intake-questions.mdc +8 -0
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +7 -5
- package/src/argv.mjs +19 -0
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +155 -113
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +12 -26
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +17 -535
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +15 -1037
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +116 -971
- package/src/bench/run.mjs +164 -881
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +11 -13
- package/src/ci/dependabot.mjs +25 -55
- package/src/commands/agent.mjs +1 -9
- package/src/commands/agenthook.mjs +66 -35
- package/src/commands/bench.mjs +2 -2
- package/src/commands/ci.mjs +11 -13
- package/src/commands/docs.mjs +88 -0
- package/src/commands/gate.mjs +10 -13
- package/src/commands/harness.mjs +3 -3
- package/src/commands/help.mjs +44 -3
- package/src/commands/hooks.mjs +20 -20
- package/src/commands/ignore.mjs +11 -7
- package/src/commands/impact.mjs +4 -8
- package/src/commands/index.mjs +49 -9
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +122 -104
- package/src/commands/lease.mjs +67 -18
- package/src/commands/map.mjs +10 -5
- package/src/commands/mcp.mjs +2 -17
- package/src/commands/repoindex.mjs +8 -8
- package/src/commands/rules.mjs +8 -4
- package/src/commands/shared.mjs +12 -16
- package/src/commands/skills.mjs +5 -6
- package/src/commands/skillscompile.mjs +3 -3
- package/src/commands/skillsinstall.mjs +5 -4
- package/src/commands/slice.mjs +199 -64
- package/src/commands/spec.mjs +78 -62
- package/src/commands/status.mjs +24 -12
- package/src/commands/sync.mjs +14 -14
- package/src/commands/tests.mjs +12 -12
- package/src/commands/tokens.mjs +19 -24
- package/src/commands/upgrade.mjs +8 -4
- package/src/commands/why.mjs +6 -13
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/decisions/index.mjs +3 -2
- 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 +2 -1
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +5 -3
- package/src/hooks/dispatcher.mjs +26 -32
- package/src/hooks/gates.mjs +12 -2
- package/src/hooks/resolve.mjs +2 -7
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +2 -11
- 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 +16 -11
- 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 +124 -17
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +49 -9
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -1005
- package/src/{bench → measure}/corpus.mjs +1 -1
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- 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/output/index.mjs +122 -31
- package/src/packs/manage.mjs +15 -9
- 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/manifest.mjs +10 -435
- 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 +16 -547
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +13 -1
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +0 -7
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +8 -12
- 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/{bench/process.mjs → sys/proc.mjs} +28 -5
- 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 +1 -1
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +3 -1
- 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/src/bench/tasks.mjs +0 -36
package/src/commands/spec.mjs
CHANGED
|
@@ -21,98 +21,100 @@
|
|
|
21
21
|
* @property {string} path the dispatch key: `status`, or `group subcommand`
|
|
22
22
|
* @property {string} summary one phrase, printed by help and by the generated docs
|
|
23
23
|
* @property {string} [defaultFor] the group whose bare form resolves to this row
|
|
24
|
+
* @property {string[]} [aliases] spellings that resolve to this row, never listed in help
|
|
24
25
|
* @property {string} [mcp] the MCP tool name that answers this row, when one does
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
/** @type {SpecRow[]} */
|
|
28
29
|
export const SPEC = [
|
|
29
|
-
{ path: 'agent-hook', summary: 'the harness entry point: decide one tool call, or deliver the prompt packet', flags: ["diff","harness"], },
|
|
30
|
-
{ path: 'agent-hooks install', summary: "write this repository's policies into each harness hooks file", writes: 'repo', flags: ["yes","diff","harness"], },
|
|
30
|
+
{ path: 'agent-hook', usage: '<event> --harness', summary: 'the harness entry point: decide one tool call, or deliver the prompt packet', aliases: ['harness hook'], flags: ["diff","harness"], },
|
|
31
|
+
{ path: 'agent-hooks install', usage: '--yes', summary: "write this repository's policies into each harness hooks file", writes: 'repo', aliases: ['harness install'], flags: ["yes","diff","harness"], },
|
|
31
32
|
{ path: 'agent-hooks status', summary: 'what each harness will run, and what is not trusted yet', defaultFor: 'agent-hooks', flags: ["diff","harness"], },
|
|
32
|
-
{ path: 'agents install', summary: 'write the Simt-Agent into each harness agents file', writes: 'repo', flags: ["yes","diff","harness"], },
|
|
33
|
+
{ path: 'agents install', usage: '--yes', summary: 'write the Simt-Agent into each harness agents file', writes: 'repo', flags: ["yes","diff","harness"], },
|
|
33
34
|
// A group like `agent-hooks`: `install` writes the agent file into each harness's own
|
|
34
35
|
// directory and `status` reports what a harness carries. The default is the read.
|
|
35
36
|
{ path: 'agents status', summary: 'what each harness carries, and what is not installed yet', defaultFor: 'agents', flags: ["diff","harness"], },
|
|
36
|
-
{ path: 'bench ablate', summary: 'measure one arm of the brief against its ablation', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
37
|
+
{ path: 'bench ablate', usage: '--yes', summary: 'measure one arm of the brief against its ablation', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
37
38
|
{ path: 'bench critic', summary: 'score the review critic against the labelled bench set', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
38
|
-
{ path: 'bench export', summary: 'write the sealed bench corpus as one signed document', writes: 'repo', flags: ["yes","all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
39
|
+
{ path: 'bench export', usage: '--yes', summary: 'write the sealed bench corpus as one signed document', writes: 'repo', flags: ["yes","all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
39
40
|
{ path: 'bench import', summary: 'verify a sealed corpus and load it into the vault', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
40
|
-
{ path: 'bench mine', summary: 'mine tasks out of a repository history', writes: 'repo',
|
|
41
|
-
{ path: 'bench probes', summary: 'run the deterministic probes against a workspace', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
42
|
-
{ path: 'bench report', summary: 'summarise the runs recorded in the vault', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
43
|
-
{ path: 'bench run', summary: 'run agents against the mined tasks', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
44
|
-
{ path: 'bench variants', summary: 'run one task across its variant prompts', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
45
|
-
{ path: 'brief', mcp: { name: 'brief', flags: ['budget', 'paths', 'task'] }, summary: 'the packet for a task: rules, skills and the files it names, within a budget', flags: ["budget","explain","paths","task"], },
|
|
41
|
+
{ path: 'bench mine', usage: '--yes', summary: 'mine tasks out of a repository history', writes: 'repo', flags: ["yes","all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
42
|
+
{ path: 'bench probes', usage: '--yes', summary: 'run the deterministic probes against a workspace', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
43
|
+
{ path: 'bench report', usage: '[--compare a b]', summary: 'summarise the runs recorded in the vault', defaultFor: 'bench', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
44
|
+
{ path: 'bench run', usage: '--yes', summary: 'run agents against the mined tasks', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
45
|
+
{ path: 'bench variants', usage: '--yes', summary: 'run one task across its variant prompts', flags: ["all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
46
|
+
{ path: 'brief', usage: '"<task>"', mcp: { name: 'brief', flags: ['budget', 'paths', 'task'] }, summary: 'the packet for a task: rules, skills and the files it names, within a budget', flags: ["budget","explain","paths","task"], },
|
|
46
47
|
{ path: 'check', mcp: { name: 'check', flags: [] }, summary: 'report stale or missing generated regions and every check finding', flags: ["check","diff","dry-run"], },
|
|
47
48
|
{ path: 'ci', summary: "refresh simt-dev's own .github workflow and Dependabot files", flags: ["check","diff"], },
|
|
48
49
|
{ path: 'compare', mcp: { name: 'compare', flags: [] }, summary: 'the adoption and drift matrix across every sibling repository', flags: [], },
|
|
49
|
-
{ path: 'completions', summary: 'print a bash, zsh or fish completion script generated from this spec', flags: [], },
|
|
50
|
-
{ path: 'decide', summary: 'scaffold a numbered decision record with its frontmatter', writes: 'repo', flags: ["yes","check","diff","dry-run","paths","supersedes","task"], },
|
|
51
|
-
{ path: 'distill', summary: 'turn a repository into a skill or rule proposal', flags: [], },
|
|
52
|
-
{ path: '
|
|
50
|
+
{ path: 'completions', usage: '<bash|zsh|fish>', summary: 'print a bash, zsh or fish completion script generated from this spec', flags: [], },
|
|
51
|
+
{ path: 'decide', usage: '"<title>" --yes', summary: 'scaffold a numbered decision record with its frontmatter', writes: 'repo', flags: ["yes","check","diff","dry-run","paths","supersedes","task"], },
|
|
52
|
+
{ path: 'distill', usage: '<skill|doc>', summary: 'turn a repository into a skill or rule proposal', flags: [], },
|
|
53
|
+
{ path: 'docs readme', summary: 'render the README command list from the spec', writes: 'repo', defaultFor: 'docs', flags: ["yes","diff"], },
|
|
54
|
+
{ path: 'doctor', usage: '[--fix]', mcp: { name: 'doctor', flags: [] }, summary: 'report what is wrong with this installation', writes: 'repo', flags: ["yes","diff","fix"], },
|
|
53
55
|
{ path: 'evidence', summary: 'the runs recorded against the tree on disk', flags: [], },
|
|
54
|
-
{ path: 'evidence check', summary: 'a commit message claiming a run this tree does not have', flags: [], },
|
|
55
|
-
{ path: 'explain', summary: 'what one check finding code means, and how to fix it', flags: [], },
|
|
56
|
-
{ path: 'gate', summary: 'the tests a change reaches, and only those', flags: ["changed"], },
|
|
57
|
-
{ path: 'graph lint', summary: 'check the skill registry against the skills on disk', defaultFor: 'graph', flags: ["spec"], },
|
|
58
|
-
{ path: 'harness detect', summary: 'which harness is installed, and what it carries', defaultFor: 'harness', flags: ["at","reader","table","user"], },
|
|
59
|
-
{ path: 'harness loaders', summary: 'the instruction files each harness loads, and what they cost', flags: ["at","reader","table","user"], },
|
|
60
|
-
{ path: 'hooks install', summary: 'install the shared dispatcher and report what it would run', writes: 'repo', flags: ["yes","diff","dry-run"], },
|
|
61
|
-
{ path: 'hooks run', summary: "run one git event's gates now, as git would", writes: 'repo', flags: ["yes","diff","dry-run"], },
|
|
56
|
+
{ path: 'evidence check', usage: '<msg-file>', summary: 'a commit message claiming a run this tree does not have', flags: [], },
|
|
57
|
+
{ path: 'explain', usage: '<CODE>', summary: 'what one check finding code means, and how to fix it', flags: [], },
|
|
58
|
+
{ path: 'gate', usage: '--changed', summary: 'the tests a change reaches, and only those', flags: ["changed"], },
|
|
59
|
+
{ path: 'graph lint', usage: '[--spec agentskills]', summary: 'check the skill registry against the skills on disk', defaultFor: 'graph', aliases: ['skills lint'], flags: ["spec"], },
|
|
60
|
+
{ path: 'harness detect', usage: '[--user]', summary: 'which harness is installed, and what it carries', defaultFor: 'harness', flags: ["at","reader","table","user"], },
|
|
61
|
+
{ path: 'harness loaders', usage: '[--table]', summary: 'the instruction files each harness loads, and what they cost', flags: ["at","reader","table","user"], },
|
|
62
|
+
{ path: 'hooks install', usage: '--yes', summary: 'install the shared dispatcher and report what it would run', writes: 'repo', flags: ["yes","diff","dry-run"], },
|
|
63
|
+
{ path: 'hooks run', usage: '<event>', summary: "run one git event's gates now, as git would", writes: 'repo', flags: ["yes","diff","dry-run"], },
|
|
62
64
|
{ path: 'hooks status', summary: 'what each git event routes to, and whether it is wired', defaultFor: 'hooks', flags: ["diff","dry-run"], },
|
|
63
|
-
{ path: 'ignore', summary: 'render the one ignore list into every harness ignore file', flags: ["diff"], },
|
|
64
|
-
{ path: 'impact', summary: 'who a change reaches: importers, their tests and the contract', flags: [], },
|
|
65
|
-
{ path: 'index', summary: 'rebuild the local file index from git blob hashes', flags: ["allow-remote","embed"], },
|
|
66
|
-
{ path: 'init', summary: 'adopt this repository: manifest, region, hooks and
|
|
67
|
-
{ path: 'lease', summary: 'claim files for one session, or list the claims', flags: ["harness","session","ttl"], },
|
|
65
|
+
{ path: 'ignore', usage: '[--yes] [--diff]', summary: 'render the one ignore list into every harness ignore file', flags: ["diff"], },
|
|
66
|
+
{ path: 'impact', usage: '<file|symbol>', summary: 'who a change reaches: importers, their tests and the contract', flags: [], },
|
|
67
|
+
{ path: 'index', usage: '[--embed]', summary: 'rebuild the local file index from git blob hashes', flags: ["allow-remote","embed"], },
|
|
68
|
+
{ path: 'init', usage: '[path]', summary: 'adopt this repository: manifest, region, hooks, agents and agent-hooks', writes: 'repo', flags: ["yes","accept-conflicts","backup","diff","harness","agents","surfaces"], },
|
|
69
|
+
{ path: 'lease', usage: '[<paths>] --session', summary: 'claim files for one session, or list the claims', flags: ["harness","session","ttl"], },
|
|
68
70
|
{ path: 'lessons capture', summary: 'scan the ledger and history for new lesson candidates', flags: ["diff","expires","paths","task"], },
|
|
69
71
|
{ path: 'lessons list', summary: 'deterministic lesson candidates and the promoted lessons', defaultFor: 'lessons', flags: ["diff","expires","paths","task"], },
|
|
70
|
-
{ path: 'lessons review', summary: 'promote one lesson candidate into docs/lessons', flags: ["diff","expires","paths","task"], },
|
|
71
|
-
{ path: 'local doctor', summary: 'the local model endpoints, and whether they answer', defaultFor: 'local', flags: ["allow-remote"], },
|
|
72
|
-
{ path: 'map', summary: 'the files a task is most likely to touch, within a byte budget', flags: ["allow-remote","budget","embed","level","recall","task"], values: { level: ['outline', 'names', 'signatures'] }, },
|
|
73
|
-
{ path: 'mcp', summary: 'serve the read-only MCP surface over stdio', flags: ["list"], },
|
|
72
|
+
{ path: 'lessons review', usage: '<id> --yes', summary: 'promote one lesson candidate into docs/lessons', flags: ["diff","expires","paths","task"], },
|
|
73
|
+
{ path: 'local doctor', usage: '[--allow-remote]', summary: 'the local model endpoints, and whether they answer', defaultFor: 'local', flags: ["allow-remote"], },
|
|
74
|
+
{ path: 'map', usage: '--task "<task>"', summary: 'the files a task is most likely to touch, within a byte budget', flags: ["allow-remote","budget","embed","level","recall","task"], values: { level: ['outline', 'names', 'signatures'] }, },
|
|
75
|
+
{ path: 'mcp', usage: '[--list]', summary: 'serve the read-only MCP surface over stdio', flags: ["list"], },
|
|
74
76
|
// Plan 0003 R45. The default is the read: `pack` alone lints the working directory,
|
|
75
77
|
// the way `skills` alone indexes and `graph` alone lints.
|
|
76
|
-
{ path: 'pack lint', summary: 'lint a rule pack in the working directory', defaultFor: 'pack', flags: ["diff","require-measured","source"], },
|
|
77
|
-
{ path: 'pack new', summary: 'scaffold a rule pack', flags: ["diff","require-measured","source"], },
|
|
78
|
-
{ path: 'pack publish', summary: 'publish a rule pack', flags: ["diff","require-measured","source"], },
|
|
79
|
-
{ path: 'plan done', mcp: { name: 'plan_done', flags: ['state', 'test'] }, summary: 'mark one tracker row complete, with its SHA and its evidence', flags: ["diff","state","test"], },
|
|
80
|
-
{ path: 'plan next', mcp: { name: 'plan_next', flags: [] }, summary: "one ready slice's section, or the one named", flags: ["diff","state","test"], },
|
|
78
|
+
{ path: 'pack lint', usage: '[dir] [--require-measured]', summary: 'lint a rule pack in the working directory', defaultFor: 'pack', flags: ["diff","require-measured","source"], },
|
|
79
|
+
{ path: 'pack new', usage: '<name> --source <spec>', summary: 'scaffold a rule pack', flags: ["diff","require-measured","source"], },
|
|
80
|
+
{ path: 'pack publish', usage: '[dir] --yes', summary: 'publish a rule pack', flags: ["diff","require-measured","source"], },
|
|
81
|
+
{ path: 'plan done', usage: '<id> [<sha>] --yes', mcp: { name: 'plan_done', flags: ['state', 'test'] }, summary: 'mark one tracker row complete, with its SHA and its evidence', flags: ["diff","state","test"], },
|
|
82
|
+
{ path: 'plan next', usage: '[<id>]', mcp: { name: 'plan_next', flags: [] }, summary: "one ready slice's section, or the one named", flags: ["diff","state","test"], },
|
|
81
83
|
{ path: 'plan status', summary: 'count the tracker rows', defaultFor: 'plan', flags: ["diff","state","test"], },
|
|
82
|
-
{ path: 'plugin', summary: 'generate a harness package from the same tables the hooks use', flags: ["diff","out"], },
|
|
83
|
-
{ path: 'pr-body', mcp: { name: 'pr_body', flags: ['base'] }, summary: 'the pull request body: evidence, decisions, gate state and residual risk', flags: ["base"], },
|
|
84
|
-
{ path: 'review', summary: 'review the staged diff against the estate rules', flags: [], },
|
|
84
|
+
{ path: 'plugin', usage: '<claude|gemini|cursor|opencode>', summary: 'generate a harness package from the same tables the hooks use', aliases: ['harness plugin'], flags: ["diff","out"], },
|
|
85
|
+
{ path: 'pr-body', usage: '[--base <ref>]', mcp: { name: 'pr_body', flags: ['base'] }, summary: 'the pull request body: evidence, decisions, gate state and residual risk', flags: ["base"], },
|
|
86
|
+
{ path: 'review', usage: '[--local]', summary: 'review the staged diff against the estate rules', flags: [], },
|
|
85
87
|
{ path: 'rules decisions', summary: 'every known decision, and which layer each came from', writes: 'repo', flags: ["yes","all"], },
|
|
86
|
-
{ path: 'rules list', mcp: { name: 'rules_list', flags: [] }, summary: 'the rule catalogue, marked with what applies to this repository', defaultFor: 'rules', flags: ["all"], },
|
|
87
|
-
{ path: 'rules show', summary: 'one rule body', flags: ["all"], },
|
|
88
|
-
{ path: 'rules why', mcp: { name: 'rules_why', flags: [] }, summary: 'the decision that settled a rule, and whether it is still in force', flags: ["all"], },
|
|
89
|
-
{ path: 'shared install', summary: 'install the shared rules into a reader home directory', writes: 'repo', flags: ["yes","diff"], },
|
|
88
|
+
{ path: 'rules list', usage: '[--root <path>]', mcp: { name: 'rules_list', flags: [] }, summary: 'the rule catalogue, marked with what applies to this repository', defaultFor: 'rules', flags: ["all"], },
|
|
89
|
+
{ path: 'rules show', usage: '<id>', summary: 'one rule body', flags: ["all"], },
|
|
90
|
+
{ path: 'rules why', usage: '<id|ADR-NNN> [--all]', mcp: { name: 'rules_why', flags: [] }, summary: 'the decision that settled a rule, and whether it is still in force', flags: ["all"], },
|
|
91
|
+
{ path: 'shared install', usage: '--yes', summary: 'install the shared rules into a reader home directory', writes: 'repo', flags: ["yes","diff"], },
|
|
90
92
|
// ADR-084. `shared` is the one group whose default writes nothing, and it is `plan`
|
|
91
93
|
// for the same reason `skills`'s is `index`: the read answers "what would this do, and
|
|
92
94
|
// where", and `install` - the subcommand that touches a reader's home directory - is
|
|
93
95
|
// the one that needs `--yes` before it does anything.
|
|
94
96
|
{ path: 'shared plan', summary: 'what the shared install would write, and where', writes: 'repo', defaultFor: 'shared', flags: ["yes","diff"], },
|
|
95
|
-
{ path: 'skills brief', mcp: { name: 'skills_brief', flags: ['all', 'task'] }, summary: 'the packet for a task, read from the skill registry', flags: ["all", "plan", "task"], },
|
|
96
|
-
{ path: 'skills compile', summary: 'compile a skill to the Agent Skills standard', flags: [], },
|
|
97
|
-
{ path: 'skills eval', summary: 'score the skill registry against transcripts', flags: ["all","plan"], },
|
|
98
|
-
{ path: 'skills find', mcp: { name: 'skills_find', flags: [] }, summary: 'skills that match a query', flags: ["all","plan"], },
|
|
99
|
-
{ path: 'skills index', mcp: { name: 'skills_index', flags: [] }, summary: 'rebuild the skill index', defaultFor: 'skills', flags: ["all","plan"], },
|
|
100
|
-
{ path: 'skills install', summary: 'install skills into the workspace', writes: 'repo', flags: ["yes","diff","from","variant"], },
|
|
101
|
-
{ path: 'skills show', mcp: { name: 'skills_show', flags: [] }, summary: 'one skill packet', flags: ["all","plan"], },
|
|
102
|
-
{ path: 'skills why', mcp: { name: 'skills_why', flags: [] }, summary: 'which skill covers a task', flags: ["all","plan"], },
|
|
103
|
-
{ path: 'slice abandon', summary: 'remove a slice worktree, delete its branch, and return the row', flags: ["session","test","type"], },
|
|
104
|
-
{ path: 'slice finish', summary: 'run the gates, commit the slice and record its evidence', writes: 'repo', flags: ["yes","session","test","type"], },
|
|
97
|
+
{ path: 'skills brief', usage: '--task "<task>"', mcp: { name: 'skills_brief', flags: ['all', 'task'] }, summary: 'the packet for a task, read from the skill registry', aliases: ['skills select'], flags: ["all", "plan", "task"], },
|
|
98
|
+
{ path: 'skills compile', usage: '[<name>]', summary: 'compile a skill to the Agent Skills standard', flags: [], },
|
|
99
|
+
{ path: 'skills eval', usage: '[corpus]', summary: 'score the skill registry against transcripts', flags: ["all","plan"], },
|
|
100
|
+
{ path: 'skills find', usage: '<query>', mcp: { name: 'skills_find', flags: [] }, summary: 'skills that match a query', flags: ["all","plan"], },
|
|
101
|
+
{ path: 'skills index', mcp: { name: 'skills_index', flags: [] }, summary: 'rebuild the skill index', defaultFor: 'skills', aliases: ['skills list'], flags: ["all","plan"], },
|
|
102
|
+
{ path: 'skills install', usage: '<name> --yes', summary: 'install skills into the workspace', writes: 'repo', flags: ["yes","diff","from","variant"], },
|
|
103
|
+
{ path: 'skills show', usage: '<name>', mcp: { name: 'skills_show', flags: [] }, summary: 'one skill packet', flags: ["all","plan"], },
|
|
104
|
+
{ path: 'skills why', usage: '<name>', mcp: { name: 'skills_why', flags: [] }, summary: 'which skill covers a task', flags: ["all","plan"], },
|
|
105
|
+
{ path: 'slice abandon', usage: '<id>', summary: 'remove a slice worktree, delete its branch, and return the row', aliases: ['plan abandon'], flags: ["session","test","type"], },
|
|
106
|
+
{ path: 'slice finish', usage: '<id> --yes', summary: 'run the gates, commit the slice and record its evidence', writes: 'repo', aliases: ['plan finish'], flags: ["yes","session","test","type"], },
|
|
105
107
|
// Plan 0003 R53. Both halves write, so a bare `slice` has no read to default to.
|
|
106
108
|
// `start` is the default because with the id it is missing it fails with a usage error
|
|
107
109
|
// before anything is created - the one default here that cannot mutate.
|
|
108
|
-
{ path: 'slice start', summary: 'give a slice its
|
|
109
|
-
{ path: 'status', summary: '
|
|
110
|
-
{ path: 'sync', summary: 're-render the generated regions from the rule content', writes: 'repo', flags: ["yes","check","diff","dry-run"], },
|
|
111
|
-
{ path: 'tests', summary: 'the tests that exercise a file', flags: ["cochange"], },
|
|
112
|
-
{ path: 'tokens', summary: 'count tokens per instruction file and per skill', flags: ["repos","session"], },
|
|
113
|
-
{ path: 'undo', summary: 'restore the last journaled set of writes, if untouched', writes: 'repo', flags: ["yes","diff"], },
|
|
114
|
-
{ path: 'upgrade', summary: 'replace this installation with the pinned release', writes: 'repo', flags: ["yes","all","diff","workspace"], },
|
|
115
|
-
{ path: 'why', summary: 'what governs a file: decisions, rules, skills, gates and lessons', flags: [], },
|
|
110
|
+
{ path: 'slice start', usage: '<id> --yes | -- "<text>"', summary: 'give a slice its worktree, branch and brief; -- starts one from a prompt, no row', defaultFor: 'slice', aliases: ['plan start'], flags: ["id","session","test","type"], },
|
|
111
|
+
{ path: 'status', usage: '[--line]', summary: 'the repository snapshot, or one line of the last check', flags: ["line"], },
|
|
112
|
+
{ path: 'sync', usage: '[--explain] --yes', summary: 're-render the generated regions from the rule content', writes: 'repo', flags: ["yes","check","diff","dry-run"], },
|
|
113
|
+
{ path: 'tests', usage: '<path> [--cochange]', summary: 'the tests that exercise a file', flags: ["cochange"], },
|
|
114
|
+
{ path: 'tokens', usage: '[--repos] [--session]', summary: 'count tokens per instruction file and per skill', aliases: ['harness cost'], flags: ["repos","session"], },
|
|
115
|
+
{ path: 'undo', usage: '--yes', summary: 'restore the last journaled set of writes, if untouched', writes: 'repo', flags: ["yes","diff"], },
|
|
116
|
+
{ path: 'upgrade', usage: '--yes', summary: 'replace this installation with the pinned release', writes: 'repo', flags: ["yes","all","diff","workspace"], },
|
|
117
|
+
{ path: 'why', usage: '[path]', summary: 'what governs a file: decisions, rules, skills, gates and lessons', flags: [], },
|
|
116
118
|
];
|
|
117
119
|
|
|
118
120
|
|
|
@@ -149,3 +151,17 @@ export const SPEC_PATHS = SPEC.map((row) => row.path).sort();
|
|
|
149
151
|
|
|
150
152
|
/** group -> the subcommand its bare form resolves to. */
|
|
151
153
|
export const GROUP_DEFAULTS = new Map(SPEC.filter((row) => row.defaultFor !== undefined).map((row) => [row.defaultFor, row.path.split(' ')[1]]));
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The spellings this binary answers that are not paths (review 0003 Q81).
|
|
157
|
+
*
|
|
158
|
+
* A rename costs every script and every habit a reader already has, so a new name
|
|
159
|
+
* arrives as an alias instead of a replacement: the path stays the dispatch key, help
|
|
160
|
+
* lists the path, and the alias resolves before the registry is consulted. An alias is
|
|
161
|
+
* deliberately absent from help and from the generated docs — the point of a second
|
|
162
|
+
* spelling is that the reader who already knows it can type it, not that a new reader
|
|
163
|
+
* has to choose between two names for one command.
|
|
164
|
+
*/
|
|
165
|
+
export const ALIASES = new Map(
|
|
166
|
+
SPEC.flatMap((row) => (row.aliases ?? []).map((alias) => [alias, row.path])),
|
|
167
|
+
);
|
package/src/commands/status.mjs
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
// `simt status --line`
|
|
1
|
+
// `simt status [--line]`
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// `
|
|
7
|
-
//
|
|
3
|
+
// Two questions, one command. `--line` is for shell prompts, tmux and agent status lines:
|
|
4
|
+
// one line from the last `check`, read only from `.simt/check.json` plus one
|
|
5
|
+
// `git rev-parse HEAD`, so it answers in milliseconds while `check` itself takes the time
|
|
6
|
+
// projection takes. Bare `status` is the aggregate a UI's home view asks for - last
|
|
7
|
+
// check, git hooks, agent hooks, evidence for the tree on disk, active leases, tracker
|
|
8
|
+
// state, the last journal set - and it was a usage error until review 0003 Q82.
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// The line's cache lives in `status.mjs`; the aggregate lives in `status-snapshot.mjs`.
|
|
11
|
+
// Both report the gate *as of the last check* rather than measuring: a status command
|
|
12
|
+
// that ran the gate would cost what the gate costs, and a prompt that waited for it is a
|
|
13
|
+
// prompt nobody reads.
|
|
12
14
|
|
|
13
|
-
import { EXIT, emit, emitJson,
|
|
15
|
+
import { EXIT, emit, emitJson, isJson, present } from '../output/index.mjs';
|
|
14
16
|
import { CLI } from '../name.mjs';
|
|
15
17
|
import { currentHead, readCheckCache, renderStatusLine } from '../status.mjs';
|
|
18
|
+
import { renderSnapshot, statusSnapshot } from '../status-snapshot.mjs';
|
|
16
19
|
import { commandRoot } from '../repo-root.mjs';
|
|
17
20
|
|
|
18
21
|
export async function statusCommand(args, flags) {
|
|
19
22
|
const root = commandRoot(flags, args);
|
|
20
|
-
if (flags.line
|
|
21
|
-
|
|
23
|
+
if (flags.line === true) return statusLine(root, flags);
|
|
24
|
+
|
|
25
|
+
const snapshot = await statusSnapshot(root);
|
|
26
|
+
const code = snapshot.ok ? EXIT.OK : EXIT.FAIL;
|
|
27
|
+
if (isJson(flags)) {
|
|
28
|
+
emitJson(snapshot);
|
|
29
|
+
return code;
|
|
22
30
|
}
|
|
31
|
+
emit(renderSnapshot(snapshot));
|
|
32
|
+
return code;
|
|
33
|
+
}
|
|
23
34
|
|
|
35
|
+
async function statusLine(root, flags) {
|
|
24
36
|
const record = await readCheckCache(root);
|
|
25
37
|
if (record === null) {
|
|
26
38
|
// Informational, not a failure: a repository that never ran `check` — or
|
package/src/commands/sync.mjs
CHANGED
|
@@ -29,7 +29,8 @@ import { surfacesLine } from '../projection/surfaces.mjs';
|
|
|
29
29
|
import { CLI, LOCK_FILE, MANIFEST_FILE, MANIFEST_LEGACY } from '../name.mjs';
|
|
30
30
|
import { assertVersionPin, packageVersion } from '../package-version.mjs';
|
|
31
31
|
import { resolvePacks } from '../packs.mjs';
|
|
32
|
-
import { commitJournal
|
|
32
|
+
import { commitJournal } from '../journal.mjs';
|
|
33
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
33
34
|
import { buildCacheRecord, hooksSummary, invalidateCheckCache, writeCheckCache } from '../status.mjs';
|
|
34
35
|
import { computeLock, readLock } from '../projection/lock.mjs';
|
|
35
36
|
import { runDeclaredChecks } from '../kernel/checks.mjs';
|
|
@@ -326,19 +327,18 @@ export async function syncCommand(positional, flags) {
|
|
|
326
327
|
await writeManifest(root, manifestPlan.manifest, journal);
|
|
327
328
|
note(`wrote ${manifestPlan.file} (generated field(s) ${manifestPlan.stale.join(', ')})`);
|
|
328
329
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
330
|
+
// The parent is derived from the target, never from the surface table, so a future
|
|
331
|
+
// surface with a deeper path needs no change here. Three of the four rule surfaces
|
|
332
|
+
// live in directories that do not exist yet in a repository that has only ever been
|
|
333
|
+
// written to by hand, and the first `sync` after a surface was added failed with
|
|
334
|
+
// ENOENT on the second target having already written the first.
|
|
335
|
+
const targetChanges = await Promise.all(
|
|
336
|
+
changed.map(async (t) =>
|
|
337
|
+
change({ path: t.path, before: await readBefore(t.path), after: t.text, owner: 'sync', reason: 'the region is stale' }),
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
await applyPlan(root, targetChanges, { mode, journal, commit: false });
|
|
341
|
+
for (const t of changed) note(`wrote ${t.file}`);
|
|
342
342
|
await commitJournal(root, 'sync', journal);
|
|
343
343
|
// The regions moved, so the last `check` no longer describes this tree.
|
|
344
344
|
// Invalidated rather than refreshed: `sync` does not run gates, checkers
|
package/src/commands/tests.mjs
CHANGED
|
@@ -5,19 +5,17 @@
|
|
|
5
5
|
// batch. Coverage is read only from a fixed list of paths. History is the last
|
|
6
6
|
// 200 non-merge commits, which is a map and not the whole changelog.
|
|
7
7
|
|
|
8
|
-
import { execFile } from 'node:child_process';
|
|
9
8
|
import { readFile } from 'node:fs/promises';
|
|
10
9
|
import { join } from 'node:path';
|
|
11
|
-
import { promisify } from 'node:util';
|
|
12
10
|
|
|
13
11
|
import { EXIT, emit, emitJson, fail, isJson, present } from '../output/index.mjs';
|
|
14
12
|
import { CLI } from '../name.mjs';
|
|
15
|
-
import { isTestPath } from '../
|
|
13
|
+
import { isTestPath } from '../testing/paths.mjs';
|
|
16
14
|
import { blobTexts, isIndexStale, langOf, readEdges, readFiles, reindex } from '../index/store.mjs';
|
|
17
|
-
import {
|
|
15
|
+
import { coverage } from '../index/coverage.mjs';
|
|
16
|
+
import { HISTORY, importsOf, parseCoverage, testEdges } from '../index/tests.mjs';
|
|
18
17
|
import { commandRoot } from '../repo-root.mjs';
|
|
19
|
-
|
|
20
|
-
const exec = promisify(execFile);
|
|
18
|
+
import { git } from '../sys/git.mjs';
|
|
21
19
|
|
|
22
20
|
const COVERAGE_PATHS = [
|
|
23
21
|
'coverage/lcov.info',
|
|
@@ -36,7 +34,7 @@ export async function testsCommand(args, flags) {
|
|
|
36
34
|
const loaded = await loadFiles(root);
|
|
37
35
|
if (loaded.error) return loaded.error;
|
|
38
36
|
const { files } = loaded;
|
|
39
|
-
const [stored, texts,
|
|
37
|
+
const [stored, texts, reports, commits] = await Promise.all([
|
|
40
38
|
readEdges(root),
|
|
41
39
|
testBlobs(root, files),
|
|
42
40
|
coverageTexts(root),
|
|
@@ -50,13 +48,16 @@ export async function testsCommand(args, flags) {
|
|
|
50
48
|
if (!text) continue;
|
|
51
49
|
imports.push(...importsOf(file.path, text, langOf(file.path), paths));
|
|
52
50
|
}
|
|
53
|
-
const edges = testEdges({ files, imports, coverage, commits });
|
|
51
|
+
const edges = testEdges({ files, imports, coverage: reports, commits });
|
|
54
52
|
const target = args[0];
|
|
55
53
|
if (!target) return summarise(root, flags, edges);
|
|
56
54
|
if (!paths.has(target)) {
|
|
57
55
|
return fail(`not in the index: ${target}`, EXIT.USAGE, `pass a path ${CLI} index knows`);
|
|
58
56
|
}
|
|
59
|
-
const tests =
|
|
57
|
+
const tests = coverage({ files, imports, coverage: reports, commits }, [target], {
|
|
58
|
+
transitive: false,
|
|
59
|
+
cochange: flags.cochange === true,
|
|
60
|
+
});
|
|
60
61
|
const payload = { ok: true, command: 'tests', root, path: target, tests };
|
|
61
62
|
if (isJson(flags)) {
|
|
62
63
|
emitJson(payload);
|
|
@@ -125,10 +126,9 @@ async function coverageTexts(root) {
|
|
|
125
126
|
async function history(root) {
|
|
126
127
|
let stdout = '';
|
|
127
128
|
try {
|
|
128
|
-
|
|
129
|
-
cwd: root,
|
|
129
|
+
stdout = await git(root, ['log', '-n', String(HISTORY), '--no-merges', '--name-only', '--pretty=format:%x1e'], {
|
|
130
130
|
maxBuffer: 16 << 20,
|
|
131
|
-
})
|
|
131
|
+
});
|
|
132
132
|
} catch {
|
|
133
133
|
return [];
|
|
134
134
|
}
|
package/src/commands/tokens.mjs
CHANGED
|
@@ -35,15 +35,10 @@ import {
|
|
|
35
35
|
} from '../measure/context.mjs';
|
|
36
36
|
import { measureSession } from '../measure/session.mjs';
|
|
37
37
|
import { sharedTotals } from '../projection/shared.mjs';
|
|
38
|
-
|
|
39
|
-
/** `1234567`, never `1,234,567`. `toLocaleString` output depends on the locale, and
|
|
40
|
-
* this command exists so two machines produce two numbers a pull request can diff. */
|
|
41
|
-
function plain(n) {
|
|
42
|
-
return String(n);
|
|
43
|
-
}
|
|
38
|
+
import { plainNumber } from '../util/fs.mjs';
|
|
44
39
|
|
|
45
40
|
function files(n) {
|
|
46
|
-
return n === 1 ? '1 file' : `${
|
|
41
|
+
return n === 1 ? '1 file' : `${plainNumber(n)} files`;
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
export async function tokensCommand(positional, flags) {
|
|
@@ -68,10 +63,10 @@ export async function tokensCommand(positional, flags) {
|
|
|
68
63
|
}
|
|
69
64
|
const lines = [
|
|
70
65
|
`tokens ${session.path} (session)`,
|
|
71
|
-
`rules ${
|
|
72
|
-
`file reads ${
|
|
73
|
-
`tool output ${
|
|
74
|
-
`total ${
|
|
66
|
+
`rules ${plainNumber(session.rules.bytes)} B ~${plainNumber(session.rules.tokens)} tokens (${plainNumber(session.rules.share)}%)`,
|
|
67
|
+
`file reads ${plainNumber(session.fileReads.bytes)} B ~${plainNumber(session.fileReads.tokens)} tokens (${plainNumber(session.fileReads.share)}%)`,
|
|
68
|
+
`tool output ${plainNumber(session.toolOutput.bytes)} B ~${plainNumber(session.toolOutput.tokens)} tokens (${plainNumber(session.toolOutput.share)}%)`,
|
|
69
|
+
`total ${plainNumber(session.total.bytes)} B ~${plainNumber(session.total.tokens)} tokens`,
|
|
75
70
|
];
|
|
76
71
|
emit(lines.join('\n'));
|
|
77
72
|
return EXIT.OK;
|
|
@@ -140,27 +135,27 @@ export async function tokensCommand(positional, flags) {
|
|
|
140
135
|
|
|
141
136
|
const lines = [];
|
|
142
137
|
if (flags.repos) {
|
|
143
|
-
lines.push(`tokens ${root} ${
|
|
138
|
+
lines.push(`tokens ${root} ${plainNumber(summary.roots)} repositories (${target.source})`);
|
|
144
139
|
for (const row of rows) {
|
|
145
|
-
const budget = row.budget.over ? ` over budget by ${
|
|
140
|
+
const budget = row.budget.over ? ` over budget by ${plainNumber(row.budget.overBy)} B` : '';
|
|
146
141
|
lines.push(
|
|
147
|
-
`${row.name} generated ${
|
|
142
|
+
`${row.name} generated ${plainNumber(row.generatedBytes)} B duplicate ${plainNumber(row.redundancy.duplicateBytes)} B unique ${plainNumber(row.redundancy.uniqueBytes)} B${budget}`,
|
|
148
143
|
);
|
|
149
144
|
for (const finding of row.findings) lines.push(`note ${row.name}: ${finding}`);
|
|
150
145
|
}
|
|
151
146
|
const share =
|
|
152
147
|
summary.generatedBytes === 0 ? 0 : Math.round((summary.duplicateBytes / summary.generatedBytes) * 100);
|
|
153
148
|
lines.push(
|
|
154
|
-
`totals generated ${
|
|
149
|
+
`totals generated ${plainNumber(summary.generatedBytes)} B duplicate ${plainNumber(summary.duplicateBytes)} B (${plainNumber(share)}% of generated) unique ${plainNumber(summary.uniqueBytes)} B repeat ${plainNumber(summary.repeatBytes)} B ~${plainNumber(summary.tokens)} tokens`,
|
|
155
150
|
);
|
|
156
151
|
// Only when there is a copy to count. The line a reader sees most is the one that
|
|
157
152
|
// says what their whole estate loads, and adding a second number to it that is
|
|
158
153
|
// usually zero is how a report stops being read.
|
|
159
154
|
if (summary.sharedBytes > 0) {
|
|
160
155
|
lines.push(
|
|
161
|
-
`shared ${
|
|
156
|
+
`shared ${plainNumber(summary.sharedBytes)} B carried once in ${plainNumber(summary.sharedFiles.length)} user-level file(s) estate total ${plainNumber(summary.estateBytes)} B`,
|
|
162
157
|
);
|
|
163
|
-
for (const f of summary.sharedFiles) lines.push(` ${f.path} ${
|
|
158
|
+
for (const f of summary.sharedFiles) lines.push(` ${f.path} ${plainNumber(f.bytes)} B ${plainNumber(f.rules)} rule(s)`);
|
|
164
159
|
}
|
|
165
160
|
} else {
|
|
166
161
|
const row = rows[0];
|
|
@@ -168,29 +163,29 @@ export async function tokensCommand(positional, flags) {
|
|
|
168
163
|
lines.push(`state ${row.adoption} surfaces ${row.surfaces.length === 0 ? '(AGENTS.md only)' : row.surfaces.join(', ')}`);
|
|
169
164
|
for (const channel of row.channels) {
|
|
170
165
|
lines.push(
|
|
171
|
-
`${channel.channel.padEnd(9)}${files(channel.files)} ${
|
|
166
|
+
`${channel.channel.padEnd(9)}${files(channel.files)} ${plainNumber(channel.generatedBytes)} B generated ${plainNumber(channel.readerBytes)} B read`,
|
|
172
167
|
);
|
|
173
168
|
}
|
|
174
|
-
const verdict = row.budget.over ? `over by ${
|
|
169
|
+
const verdict = row.budget.over ? `over by ${plainNumber(row.budget.overBy)} B` : 'within budget';
|
|
175
170
|
lines.push(
|
|
176
|
-
`always-on ${
|
|
171
|
+
`always-on ${plainNumber(row.generatedBytes)} B generated ~${plainNumber(row.generatedTokens)} tokens budget ${plainNumber(row.budget.bytes)} B ${verdict}`,
|
|
177
172
|
);
|
|
178
173
|
// The same reason as the `--repos` line, and it matters more here: measuring one
|
|
179
174
|
// repository is the default, so a reader who owns the workspace copy and never saw it
|
|
180
175
|
// counted would read a saving their own session does not get.
|
|
181
176
|
if (summary.sharedBytes > 0) {
|
|
182
177
|
lines.push(
|
|
183
|
-
`shared ${
|
|
178
|
+
`shared ${plainNumber(summary.sharedBytes)} B carried once in ${plainNumber(summary.sharedFiles.length)} user-level file(s) always-on for you: ${plainNumber(summary.estateBytes)} B`,
|
|
184
179
|
);
|
|
185
|
-
for (const f of summary.sharedFiles) lines.push(` ${f.path} ${
|
|
180
|
+
for (const f of summary.sharedFiles) lines.push(` ${f.path} ${plainNumber(f.bytes)} B ${plainNumber(f.rules)} rule(s)`);
|
|
186
181
|
}
|
|
187
182
|
lines.push(
|
|
188
|
-
`redundant ${
|
|
183
|
+
`redundant ${plainNumber(row.redundancy.duplicateBytes)} B repeats an earlier repository ${plainNumber(row.redundancy.repeatBytes)} B repeats an earlier document here ${plainNumber(row.redundancy.uniqueBytes)} B unique of ${plainNumber(row.redundancy.blockBytes)} B counted in blocks`,
|
|
189
184
|
);
|
|
190
185
|
for (const finding of row.findings) lines.push(`note ${finding}`);
|
|
191
186
|
}
|
|
192
187
|
lines.push(
|
|
193
|
-
`mcp tools/list ${
|
|
188
|
+
`mcp tools/list ${plainNumber(mcp.bytes)} B of ${plainNumber(mcp.tools)} tools (the same for every repository, so it is not in any total)`,
|
|
194
189
|
);
|
|
195
190
|
|
|
196
191
|
emit(lines.join('\n'));
|
package/src/commands/upgrade.mjs
CHANGED
|
@@ -26,12 +26,14 @@
|
|
|
26
26
|
// default resolved the workspace and a dry run inside one repo planned all sixteen.
|
|
27
27
|
|
|
28
28
|
import { mkdir } from 'node:fs/promises';
|
|
29
|
-
import { commitJournal
|
|
29
|
+
import { commitJournal } from '../journal.mjs';
|
|
30
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
30
31
|
import { invalidateCheckCache } from '../status.mjs';
|
|
31
32
|
|
|
32
33
|
import { basename, dirname, resolve } from 'node:path';
|
|
33
34
|
|
|
34
35
|
import { EXIT, emit, emitJson, fail, isJson, note, writeMode } from '../output/index.mjs';
|
|
36
|
+
import { namedRoot } from '../repo-root.mjs';
|
|
35
37
|
import { provenanceDigest, plan } from '../projection/project.mjs';
|
|
36
38
|
import { MANIFEST, MANIFEST_COMMENT, manifestPath, manifestText, readManifest, writeManifest } from '../projection/manifest.mjs';
|
|
37
39
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
@@ -42,7 +44,7 @@ import { CLI } from '../name.mjs';
|
|
|
42
44
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
43
45
|
|
|
44
46
|
export async function upgradeCommand(positional, flags) {
|
|
45
|
-
const explicit = flags
|
|
47
|
+
const explicit = namedRoot(flags, positional);
|
|
46
48
|
|
|
47
49
|
// `--all` is a claim about SCOPE, so it is checked rather than inferred. "All" of
|
|
48
50
|
// nothing is a contradiction, and defaulting it to the current directory would make
|
|
@@ -179,12 +181,14 @@ export async function upgradeCommand(positional, flags) {
|
|
|
179
181
|
// `undo` in one consumer must never reach into another (R49).
|
|
180
182
|
const journal = [];
|
|
181
183
|
const { targets } = await plan(p.path);
|
|
184
|
+
const targetChanges = [];
|
|
182
185
|
for (const t of targets) {
|
|
183
186
|
if (t.action === 'unchanged' || t.action === 'malformed') continue;
|
|
184
|
-
await
|
|
185
|
-
await journaledWriteFile(p.path, journal, t.path, t.text);
|
|
187
|
+
targetChanges.push(change({ path: t.path, before: await readBefore(t.path), after: t.text, owner: 'upgrade', reason: t.action }));
|
|
186
188
|
writtenFiles.add(t.file);
|
|
187
189
|
}
|
|
190
|
+
// The caller's journal, committed once when the workspace walk ends.
|
|
191
|
+
await applyPlan(p.path, targetChanges, { mode: { apply: true }, journal, commit: false });
|
|
188
192
|
const { manifest } = await readManifest(p.path);
|
|
189
193
|
// Preserve what the repo chose; refresh what simt-dev generates. $comment is
|
|
190
194
|
// ours - it describes this file's provenance - so carrying the previous copy
|
package/src/commands/why.mjs
CHANGED
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
// is `git ls-files`. The index is not rebuilt: governance is the records, and
|
|
6
6
|
// writing the cache to answer a question would change the tree.
|
|
7
7
|
|
|
8
|
-
import { execFile } from 'node:child_process';
|
|
9
8
|
import { readFile, readdir } from 'node:fs/promises';
|
|
10
9
|
import { join } from 'node:path';
|
|
11
|
-
import { promisify } from 'node:util';
|
|
12
10
|
|
|
13
11
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
14
12
|
import { CLI } from '../name.mjs';
|
|
@@ -16,18 +14,15 @@ import { FORMATTABLE, GATES } from '../hooks/gates.mjs';
|
|
|
16
14
|
import { loadManifest } from '../manifest/schema.mjs';
|
|
17
15
|
import { decisionsDir } from '../decisions/dir.mjs';
|
|
18
16
|
import {
|
|
19
|
-
|
|
20
|
-
scopeHits,
|
|
17
|
+
governs,
|
|
21
18
|
scopePaths,
|
|
22
|
-
sortHits,
|
|
23
19
|
sourceRoots,
|
|
24
20
|
unresolvedAffects,
|
|
25
21
|
} from '../index/govern.mjs';
|
|
26
|
-
import {
|
|
22
|
+
import { parseFrontmatter } from '../text/frontmatter.mjs';
|
|
27
23
|
import { listPromotedLessons } from '../memory/lessons.mjs';
|
|
28
24
|
import { commandRoot } from '../repo-root.mjs';
|
|
29
|
-
|
|
30
|
-
const exec = promisify(execFile);
|
|
25
|
+
import { gitPaths } from '../sys/git.mjs';
|
|
31
26
|
|
|
32
27
|
const RULE_DIRS = ['rules', 'rules/stacks', 'rules/mdc', '.simt/profiles'];
|
|
33
28
|
|
|
@@ -43,8 +38,7 @@ export async function whyCommand(args, flags, commands = []) {
|
|
|
43
38
|
if (!target) return reportUnresolved(root, flags, unresolvedAffects(records, ctx));
|
|
44
39
|
if (!files.has(target)) return fail(`not in the tree: ${target}`, EXIT.USAGE, `pass a path git tracks`);
|
|
45
40
|
const corpus = await loadCorpus(root);
|
|
46
|
-
const
|
|
47
|
-
const items = sortHits([...hits, ...scopeHits(target, corpus)]);
|
|
41
|
+
const items = governs(target, { records, ...corpus, ctx });
|
|
48
42
|
const payload = { ok: true, command: 'why', root, path: target, items };
|
|
49
43
|
if (isJson(flags)) {
|
|
50
44
|
emitJson(payload);
|
|
@@ -72,8 +66,7 @@ export async function tracked(root) {
|
|
|
72
66
|
try {
|
|
73
67
|
// `-z`: without it git C-quotes a non-ASCII path and the reader's real
|
|
74
68
|
// filename never matches it (review 0003 Q39).
|
|
75
|
-
|
|
76
|
-
return { files: stdout.split('\0').filter(Boolean) };
|
|
69
|
+
return { files: await gitPaths(root, ['ls-files']) };
|
|
77
70
|
} catch (error) {
|
|
78
71
|
return {
|
|
79
72
|
error: fail(
|
|
@@ -99,7 +92,7 @@ export async function decisionRecords(root) {
|
|
|
99
92
|
const records = [];
|
|
100
93
|
for (const name of names) {
|
|
101
94
|
const text = await readFile(join(dir, name), 'utf8');
|
|
102
|
-
const { body } =
|
|
95
|
+
const { body } = parseFrontmatter(text);
|
|
103
96
|
const id = /^ADR-\d{3,}/i.exec(name)[0].toUpperCase();
|
|
104
97
|
const heading = /^#\s+(.*)$/m.exec(body)?.[1] ?? id;
|
|
105
98
|
const title = heading.replace(/^ADR-\d{3,}\s*[—–-]\s*/i, '').trim();
|