simt-dev 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +2 -2
- package/agents/simt-agent.md +84 -0
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/README.md +9 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +3 -3
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +3 -2
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +141 -9
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/prompt.mjs +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +8 -0
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +109 -15
- package/src/commands/bench.mjs +2 -1
- package/src/commands/compare.mjs +8 -7
- package/src/commands/decide.mjs +6 -5
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +35 -14
- package/src/commands/hooks.mjs +68 -25
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +17 -4
- package/src/commands/init.mjs +169 -37
- package/src/commands/lease.mjs +203 -35
- package/src/commands/lessons.mjs +65 -9
- package/src/commands/map.mjs +33 -18
- package/src/commands/plugin.mjs +1 -1
- package/src/commands/repoindex.mjs +1 -0
- package/src/commands/rules.mjs +92 -18
- package/src/commands/skills.mjs +60 -25
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +12 -0
- package/src/commands/slice.mjs +377 -18
- package/src/commands/sync.mjs +37 -22
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +24 -16
- package/src/commands/upgrade.mjs +33 -1
- package/src/commands/why.mjs +10 -4
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -1
- package/src/completions.mjs +5 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +38 -19
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +335 -13
- package/src/hooks/resolve.mjs +9 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +307 -47
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +296 -77
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +97 -15
- package/src/ledger/index.mjs +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +81 -22
- package/src/mcp/tools.mjs +45 -9
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- package/src/plan/index.mjs +31 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +6 -2
- package/src/prbody.mjs +7 -5
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +21 -14
- package/src/projection/manifest.mjs +69 -19
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +54 -3
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- package/templates/plan/tracker.json +1 -1
package/src/commands/help.mjs
CHANGED
|
@@ -47,6 +47,7 @@ ${CLI} rules list the rule catalogue (--root marks what applies to a
|
|
|
47
47
|
${CLI} rules show <id> one rule body
|
|
48
48
|
${CLI} rules why <id|ADR-NNN> the decision that settled it, and whether it is still in force
|
|
49
49
|
(--all prints every ruling, not the first two)
|
|
50
|
+
${CLI} rules decisions every known decision, shipped and repo, and which layer each came from
|
|
50
51
|
${CLI} hooks install [--yes] install the shared dispatcher; report what it would run
|
|
51
52
|
(dry-run unless --yes; refuses to take over routing git
|
|
52
53
|
already uses, and --yes is that consent)
|
|
@@ -58,11 +59,16 @@ ${CLI} agent-hook <event> the entry point a harness calls: pre-tool-use dec
|
|
|
58
59
|
(reads one JSON payload on stdin and answers in that
|
|
59
60
|
harness's dialect; exit 2 blocks; needs --harness)
|
|
60
61
|
${CLI} agent-hooks install write this repo's policies into the harnesses that
|
|
61
|
-
read a hooks file
|
|
62
|
+
read a hooks file (--harness <id> writes one, not all)
|
|
62
63
|
(dry-run unless --yes; merges into the file, keeps every
|
|
63
64
|
key it does not own; Codex needs the hook trusted)
|
|
64
65
|
${CLI} agent-hooks status what each harness will run, and what is not trusted yet
|
|
65
|
-
${CLI}
|
|
66
|
+
${CLI} agents install write the Simt-Agent into the harnesses that read an
|
|
67
|
+
agents file (--harness <id> writes one, not all; dry-run
|
|
68
|
+
unless --yes; markdown with a frontmatter the harness
|
|
69
|
+
documents, TOML for Codex)
|
|
70
|
+
${CLI} agents status what each harness carries, and what is not installed yet
|
|
71
|
+
${CLI} plugin <claude|gemini|cursor|opencode> generate a harness package from the same tables
|
|
66
72
|
(dry-run unless --yes; writes a self-contained runtime, the
|
|
67
73
|
hooks and MCP registration, and for cursor the kernel rules;
|
|
68
74
|
the skill registry is workspace data and is not shipped)
|
|
@@ -77,17 +83,24 @@ ${CLI} plan status count tracker rows
|
|
|
77
83
|
${CLI} plan next [<id>] one ready slice's section, or the one named; ~1 KB
|
|
78
84
|
${CLI} plan done <id> [<sha>] mark one row; dry-run unless --yes
|
|
79
85
|
${CLI} slice start <id> give a slice its own worktree and branch, preload the
|
|
80
|
-
brief, and mark the row
|
|
81
|
-
--yes; the worktree is ${STATE_DIR}/worktrees/<id>
|
|
82
|
-
branch slice/<id>, the brief ${STATE_DIR}/
|
|
86
|
+
brief, and mark the row started (dry-run unless
|
|
87
|
+
--yes; the worktree is ${STATE_DIR}/local/worktrees/<id>
|
|
88
|
+
on branch slice/<id>, the brief ${STATE_DIR}/local/briefs/<id>.md
|
|
89
|
+
beside it; --session <id> leases the slice)
|
|
83
90
|
${CLI} slice finish <id> run the pre-commit gates, commit what is staged with a
|
|
84
91
|
Conventional subject from the row's title, then record
|
|
85
92
|
the SHA and the --test command on the row (dry-run
|
|
86
|
-
unless --yes; --type picks the type, default feat
|
|
93
|
+
unless --yes; --type picks the type, default feat;
|
|
94
|
+
refuses unless HEAD is the slice branch; gates the
|
|
95
|
+
ledger already proves for this tree are not re-run)
|
|
96
|
+
${CLI} slice abandon <id> remove the worktree, delete the branch, and return
|
|
97
|
+
the row to ready (dry-run unless --yes; refuses from
|
|
98
|
+
inside the worktree)
|
|
87
99
|
${CLI} lease [<paths>...] claim files for one session and a TTL, or list the
|
|
88
100
|
claims (dry-run unless --yes; the session is --session
|
|
89
101
|
<id> or ${SESSION_ENV}; a pre-edit hook refuses an edit
|
|
90
|
-
to a path another session holds — ADR-101
|
|
102
|
+
to a path another session holds — ADR-101;
|
|
103
|
+
release and renew end and extend your own claims)
|
|
91
104
|
${CLI} graph lint check the skill registry against the skills on disk
|
|
92
105
|
(byte counts, dangling edges, unresolvable variants)
|
|
93
106
|
${CLI} ci simt-dev's own .github files: create or refresh them
|
|
@@ -100,7 +113,8 @@ ${CLI} ignore [--yes] render the one .simtignore list into .cursorignore
|
|
|
100
113
|
Claude settings file is merged, never replaced)
|
|
101
114
|
${CLI} compare org-wide matrix across every sibling repo
|
|
102
115
|
(read-only; exits non-zero on defects, not on
|
|
103
|
-
repos that have not adopted yet
|
|
116
|
+
repos that have not adopted yet;
|
|
117
|
+
--concurrency <n> caps the rows built at once)
|
|
104
118
|
${CLI} index rebuild the local file index from git blob hashes
|
|
105
119
|
${CLI} why [path] what governs a file: a decision whose Affects line names
|
|
106
120
|
it, a rule whose scope matches it, a skill, a gate
|
|
@@ -127,8 +141,9 @@ ${CLI} pr-body [--base <ref>] a pull request body: the evidence block for this
|
|
|
127
141
|
${CLI} evidence the runs recorded against the tree on disk
|
|
128
142
|
${CLI} evidence check <file> a commit message that claims a run this tree does not
|
|
129
143
|
have: a warning, or a block when gates.evidence is "block"
|
|
130
|
-
${CLI} lessons list deterministic lesson candidates in .simt/lessons.jsonl
|
|
131
|
-
and promoted lessons in docs/lessons/*.md
|
|
144
|
+
${CLI} lessons list deterministic lesson candidates in .simt/local/lessons.jsonl
|
|
145
|
+
and promoted lessons in docs/lessons/*.md (reads nothing else)
|
|
146
|
+
${CLI} lessons capture scan the ledger and git history for new candidates (the write half of list)
|
|
132
147
|
${CLI} lessons review [<id>] promote a lesson candidate to docs/lessons/NNNN-<slug>.md
|
|
133
148
|
(dry-run unless --yes; writes Appendix A frontmatter)
|
|
134
149
|
${CLI} explain <CODE> what a check finding means: the decision behind it, how
|
|
@@ -147,7 +162,7 @@ ${CLI} local doctor probe configured local model endpoints (chat and
|
|
|
147
162
|
unset; refuses non-loopback URLs without --allow-remote)
|
|
148
163
|
${CLI} distill <skill|doc> a budgeted proposal summary of a skill body or doc,
|
|
149
164
|
every claim citing the source line range it rests on
|
|
150
|
-
(writes .simt/distilled/<digest>.md; a proposal a
|
|
165
|
+
(writes .simt/local/distilled/<digest>.md; a proposal a
|
|
151
166
|
human merges - never projected or committed;
|
|
152
167
|
deterministic outline by default; --local proposes
|
|
153
168
|
from the configured chat model instead; the cache is
|
|
@@ -157,7 +172,7 @@ ${CLI} review [--local] an ADR-008 verdict skeleton for the staged diff,
|
|
|
157
172
|
(deterministic without --local: the brief and its
|
|
158
173
|
governing ids only; --local proposes from the
|
|
159
174
|
configured chat model and writes
|
|
160
|
-
.simt/reviews/<digest>.md — a proposal a human
|
|
175
|
+
.simt/local/reviews/<digest>.md — a proposal a human
|
|
161
176
|
reads, verified against the verdict vocabulary and
|
|
162
177
|
the brief's ids; advisory always, never a gate)
|
|
163
178
|
${CLI} shared plan what a workspace's repeated rules cost to carry
|
|
@@ -197,10 +212,10 @@ ${CLI} bench probes [--yes] 12 synthetic rule-compliance probes with determin
|
|
|
197
212
|
(dry-run unless --yes; grades git state, transcript and ledger,
|
|
198
213
|
and reports compliance per arm)
|
|
199
214
|
${CLI} bench variants [--yes] evaluate simt-variants rule slots on the canary suite and lock
|
|
200
|
-
winning variants in simt
|
|
215
|
+
winning variants in simt.lock.json (dry-run unless --yes;
|
|
201
216
|
re-opens when model or harness changes)
|
|
202
217
|
${CLI} bench critic precision and recall of the local critic over a labelled
|
|
203
|
-
set of historical diffs (.simt/critic-labels.jsonl;
|
|
218
|
+
set of historical diffs (.simt/local/critic-labels.jsonl;
|
|
204
219
|
overrides --labels; labels are human-written verdicts
|
|
205
220
|
under ADR-008; the critic is advisory until precision
|
|
206
221
|
reaches 0.8 on 30 labelled diffs)
|
|
@@ -240,6 +255,12 @@ const FLAGS = `--json structured output (compact; one document,
|
|
|
240
255
|
--workspace <path> where the skill sources live; "off" declines it
|
|
241
256
|
(default: SIMT_DEV_WORKSPACE, then the user config
|
|
242
257
|
file, then the sibling heuristic if it verifies)
|
|
258
|
+
--registry <path> which skill registry to read (skills commands and
|
|
259
|
+
graph lint; default: SIMT_REGISTRY, then the checkout's
|
|
260
|
+
own graph/skills.graph.json, then the one member of the
|
|
261
|
+
workspace that holds one)
|
|
262
|
+
--concurrency <n> compare: rows built at once (default 4, or
|
|
263
|
+
SIMT_CONCURRENCY; order-preserving whatever it is)
|
|
243
264
|
--dry-run report what would change, change nothing
|
|
244
265
|
--yes assume yes; required in automation, never prompts
|
|
245
266
|
--check report drift and write nothing (sync, ci)
|
package/src/commands/hooks.mjs
CHANGED
|
@@ -12,13 +12,11 @@
|
|
|
12
12
|
// DEFAULT location, which core.hooksPath being unset does not reveal, so a
|
|
13
13
|
// foreign hook is found by asking git rather than by reading config.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { commitJournal, journaledRemoveFile, journaledWriteFile } from '../journal.mjs';
|
|
15
|
+
import { mkdir, readFile, realpath } from 'node:fs/promises';
|
|
16
|
+
import { commitJournal, journaledGitConfig, journaledRemoveFile, journaledWriteFile } from '../journal.mjs';
|
|
18
17
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
19
18
|
import { invalidateCheckCache } from '../status.mjs';
|
|
20
19
|
import { isAbsolute, join, relative } from 'node:path';
|
|
21
|
-
import { promisify } from 'node:util';
|
|
22
20
|
|
|
23
21
|
import { EXIT, emit, emitJson, fail, isJson, note, writeMode } from '../output/index.mjs';
|
|
24
22
|
import { MANIFEST, readManifest } from '../projection/manifest.mjs';
|
|
@@ -31,8 +29,6 @@ import { CLI } from '../name.mjs';
|
|
|
31
29
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
32
30
|
import { packageVersion, hasNpm } from '../package-version.mjs';
|
|
33
31
|
|
|
34
|
-
const exec = promisify(execFile);
|
|
35
|
-
|
|
36
32
|
export async function hooksCommand(sub, args, flags) {
|
|
37
33
|
const root = flags.root ?? args[0] ?? process.cwd();
|
|
38
34
|
if (sub === 'install') return installHooks(root, flags);
|
|
@@ -54,7 +50,12 @@ async function runHook(args, flags) {
|
|
|
54
50
|
return fail(`${root} has no ${MANIFEST_FILE}; the hook cannot know its gate list`, EXIT.CONFIG, `run: ${CLI} init --root ${root}`);
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
|
|
53
|
+
// git feeds pre-push its ref list on stdin, and the docs-only skip (ADR-113)
|
|
54
|
+
// needs it. A manual run from a terminal has a TTY and reads empty, which the
|
|
55
|
+
// dispatcher treats as "no push to classify" and answers with the full suite.
|
|
56
|
+
const pushed = event === 'pre-push' ? await readStdin() : undefined;
|
|
57
|
+
|
|
58
|
+
const result = await runGates(event, { root, manifest, pushed });
|
|
58
59
|
if (result.error) return fail(result.error, EXIT.USAGE);
|
|
59
60
|
|
|
60
61
|
if (isJson(flags)) {
|
|
@@ -67,6 +68,29 @@ async function runHook(args, flags) {
|
|
|
67
68
|
return result.failed ? EXIT.FAIL : EXIT.OK;
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Read all of stdin, or '' when there is none to read.
|
|
73
|
+
*
|
|
74
|
+
* The pre-push shim execs this command, so git's ref list arrives on the
|
|
75
|
+
* dispatcher's stdin. A TTY (a manual run from a terminal) has no ref list and
|
|
76
|
+
* reads empty - and empty is the fail-closed answer: the dispatcher cannot
|
|
77
|
+
* classify a push it was never told about, so the suite runs.
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<string>}
|
|
80
|
+
*/
|
|
81
|
+
function readStdin() {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
if (process.stdin.isTTY) {
|
|
84
|
+
resolve('');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const chunks = [];
|
|
88
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
89
|
+
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
90
|
+
process.stdin.resume();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
/**
|
|
71
95
|
* The path to write into a chained shim: relative to the repository's top level
|
|
72
96
|
* wherever the hook lives inside it.
|
|
@@ -123,6 +147,22 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
123
147
|
// install where there is no node_modules to pin in.
|
|
124
148
|
const version = packageVersion();
|
|
125
149
|
const npm = await hasNpm(root);
|
|
150
|
+
// Q31: installed shims name the repo's own installer in their remedy.
|
|
151
|
+
// Lockfile first, `packageManager` next, npm default — the one resolver.
|
|
152
|
+
let manager = null;
|
|
153
|
+
if (npm) {
|
|
154
|
+
const { resolveNodeRunner } = await import('../ecosystem/node-runner.mjs');
|
|
155
|
+
let packageManager = null;
|
|
156
|
+
try {
|
|
157
|
+
const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
158
|
+
if (typeof pkg.packageManager === 'string') packageManager = pkg.packageManager;
|
|
159
|
+
} catch {
|
|
160
|
+
// Unreadable here leaves the npm default; the manifest path reports it.
|
|
161
|
+
}
|
|
162
|
+
const { fingerprint } = await import('../resolve/fingerprint.mjs');
|
|
163
|
+
const detected = await fingerprint(root);
|
|
164
|
+
manager = resolveNodeRunner({ facts: detected.facts, packageManager }).id;
|
|
165
|
+
}
|
|
126
166
|
|
|
127
167
|
// Only install a shim for an event that declares at least one gate. A shim with
|
|
128
168
|
// an empty gate list enforces nothing, yet merely existing it REPLACES whatever
|
|
@@ -224,7 +264,7 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
224
264
|
const evidenceDeclared = manifest?.gates?.evidence === 'warn' || manifest?.gates?.evidence === 'block';
|
|
225
265
|
const evidenceFile = fileFor('commit-msg');
|
|
226
266
|
const evidenceExisting = await readIfExists(join(root, evidenceFile));
|
|
227
|
-
const evidenceSource = evidenceDeclared ? renderEvidenceShim({ npm, version }) : null;
|
|
267
|
+
const evidenceSource = evidenceDeclared ? renderEvidenceShim({ npm, manager, version }) : null;
|
|
228
268
|
const evidenceOurs = evidenceExisting !== null && evidenceExisting.includes(SHIM_MARKER);
|
|
229
269
|
if (!evidenceDeclared && evidenceOurs) obsolete.push({ event: 'commit-msg' });
|
|
230
270
|
// A foreign commit-msg would otherwise be chained onto the same path. The chain
|
|
@@ -243,6 +283,7 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
243
283
|
current,
|
|
244
284
|
wants,
|
|
245
285
|
npm,
|
|
286
|
+
manager,
|
|
246
287
|
version,
|
|
247
288
|
wanted,
|
|
248
289
|
chained,
|
|
@@ -271,23 +312,25 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
271
312
|
* @returns {{wrote: string[]}} the shim paths written, removed, or refreshed
|
|
272
313
|
*/
|
|
273
314
|
export async function applyHooksInstall(root, planned, journal) {
|
|
274
|
-
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, version, routed, wants, current } =
|
|
315
|
+
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current } =
|
|
275
316
|
planned;
|
|
276
|
-
// Shim and evidence bytes are journaled so `undo` restores them (R49)
|
|
277
|
-
//
|
|
278
|
-
//
|
|
317
|
+
// Shim and evidence bytes are journaled so `undo` restores them (R49), with
|
|
318
|
+
// the executable bit they are written with: a shim the run chmodded is one
|
|
319
|
+
// change, and `undo` must restore the mode it found, not just the bytes.
|
|
320
|
+
// The `core.hooksPath` routing below is journaled as a `git-config` entry
|
|
321
|
+
// for the same reason — unwinding the shims while the config still points
|
|
322
|
+
// at their directory silently stops the repository's own hooks (review 0003
|
|
323
|
+
// Q22).
|
|
279
324
|
const wrote = [];
|
|
280
325
|
await mkdir(join(root, HOOKS_DIR), { recursive: true });
|
|
281
326
|
for (const event of wanted) {
|
|
282
327
|
const path = join(root, fileFor(event));
|
|
283
|
-
await journaledWriteFile(root, journal, path, renderShim(event, { npm, version }));
|
|
284
|
-
await chmod(path, 0o755);
|
|
328
|
+
await journaledWriteFile(root, journal, path, renderShim(event, { npm, manager, version }), { mode: 0o755 });
|
|
285
329
|
wrote.push(fileFor(event));
|
|
286
330
|
}
|
|
287
331
|
for (const c of chained) {
|
|
288
332
|
const path = join(root, fileFor(c.event));
|
|
289
|
-
await journaledWriteFile(root, journal, path, renderChainedShim(c.event, c.target));
|
|
290
|
-
await chmod(path, 0o755);
|
|
333
|
+
await journaledWriteFile(root, journal, path, renderChainedShim(c.event, c.target), { mode: 0o755 });
|
|
291
334
|
wrote.push(fileFor(c.event));
|
|
292
335
|
}
|
|
293
336
|
for (const h of obsolete) {
|
|
@@ -297,8 +340,7 @@ export async function applyHooksInstall(root, planned, journal) {
|
|
|
297
340
|
// After the chain loop, so a chained commit-msg cannot replace the claim check.
|
|
298
341
|
if (evidenceSource) {
|
|
299
342
|
const path = join(root, evidenceFile);
|
|
300
|
-
await journaledWriteFile(root, journal, path, evidenceSource);
|
|
301
|
-
await chmod(path, 0o755);
|
|
343
|
+
await journaledWriteFile(root, journal, path, evidenceSource, { mode: 0o755 });
|
|
302
344
|
wrote.push(evidenceFile);
|
|
303
345
|
}
|
|
304
346
|
// `core.hooksPath` tells git to read ONE directory for EVERY event, so leaving it
|
|
@@ -306,9 +348,9 @@ export async function applyHooksInstall(root, planned, journal) {
|
|
|
306
348
|
// `.git/hooks` that predate this tool - from running, silently. Unsetting it is the
|
|
307
349
|
// only way back, and this is the only place that knows the last shim just left.
|
|
308
350
|
if (routed) {
|
|
309
|
-
await
|
|
351
|
+
await journaledGitConfig(root, journal, HOOKS_PATH_KEY, wants);
|
|
310
352
|
} else if (current === wants) {
|
|
311
|
-
await
|
|
353
|
+
await journaledGitConfig(root, journal, HOOKS_PATH_KEY, null);
|
|
312
354
|
}
|
|
313
355
|
return { wrote };
|
|
314
356
|
}
|
|
@@ -320,11 +362,11 @@ export async function applyHooksInstall(root, planned, journal) {
|
|
|
320
362
|
* `doctor --fix` reuses it rather than deriving a second answer.
|
|
321
363
|
*/
|
|
322
364
|
export async function plannedDiffs(root, planned) {
|
|
323
|
-
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, version } = planned;
|
|
365
|
+
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version } = planned;
|
|
324
366
|
const pairs = [];
|
|
325
367
|
for (const event of wanted) {
|
|
326
368
|
const path = fileFor(event);
|
|
327
|
-
pairs.push({ path, before: await readBefore(join(root, path)), after: renderShim(event, { npm, version }) });
|
|
369
|
+
pairs.push({ path, before: await readBefore(join(root, path)), after: renderShim(event, { npm, manager, version }) });
|
|
328
370
|
}
|
|
329
371
|
for (const c of chained) {
|
|
330
372
|
const path = fileFor(c.event);
|
|
@@ -355,6 +397,7 @@ async function installHooks(root, flags) {
|
|
|
355
397
|
current,
|
|
356
398
|
wants,
|
|
357
399
|
npm,
|
|
400
|
+
manager,
|
|
358
401
|
version,
|
|
359
402
|
wanted,
|
|
360
403
|
chained,
|
|
@@ -403,7 +446,7 @@ async function installHooks(root, flags) {
|
|
|
403
446
|
const plan = [];
|
|
404
447
|
for (const event of wanted) {
|
|
405
448
|
const path = join(root, fileFor(event));
|
|
406
|
-
const source = renderShim(event, { npm, version });
|
|
449
|
+
const source = renderShim(event, { npm, manager, version });
|
|
407
450
|
const existing = await readIfExists(path);
|
|
408
451
|
if (existing === source) {
|
|
409
452
|
plan.push({ file: fileFor(event), action: 'unchanged' });
|
|
@@ -487,7 +530,7 @@ async function installHooks(root, flags) {
|
|
|
487
530
|
// true in preview and in apply alike (R50).
|
|
488
531
|
const diffs =
|
|
489
532
|
flags.diff === true
|
|
490
|
-
? await plannedDiffs(root, { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, version })
|
|
533
|
+
? await plannedDiffs(root, { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version })
|
|
491
534
|
: [];
|
|
492
535
|
|
|
493
536
|
if (!mode.apply) {
|
|
@@ -535,7 +578,7 @@ async function installHooks(root, flags) {
|
|
|
535
578
|
const journal = [];
|
|
536
579
|
await applyHooksInstall(
|
|
537
580
|
root,
|
|
538
|
-
{ wanted, chained, obsolete, evidenceSource, evidenceFile, npm, version, routed, wants, current },
|
|
581
|
+
{ wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current },
|
|
539
582
|
journal,
|
|
540
583
|
);
|
|
541
584
|
await commitJournal(root, 'hooks install', journal);
|
package/src/commands/impact.mjs
CHANGED
|
@@ -191,8 +191,10 @@ function textExt(file) {
|
|
|
191
191
|
|
|
192
192
|
async function tracked(root) {
|
|
193
193
|
try {
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
// `-z`: without it git C-quotes a non-ASCII path and the reader's real
|
|
195
|
+
// filename never matches it (review 0003 Q39).
|
|
196
|
+
const { stdout } = await exec('git', ['ls-files', '-z'], { cwd: root, maxBuffer: 16 << 20 });
|
|
197
|
+
return { files: stdout.split('\0').filter(Boolean) };
|
|
196
198
|
} catch (error) {
|
|
197
199
|
return {
|
|
198
200
|
error: fail(
|
package/src/commands/index.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { initCommand } from './init.mjs';
|
|
|
31
31
|
import { ciCommand } from './ci.mjs';
|
|
32
32
|
import { hooksCommand } from './hooks.mjs';
|
|
33
33
|
import { agentHookCommand, agentHooksCommand } from './agenthook.mjs';
|
|
34
|
+
import { agentsCommand } from './agent.mjs';
|
|
34
35
|
import { compareCommand } from './compare.mjs';
|
|
35
36
|
import { upgradeCommand } from './upgrade.mjs';
|
|
36
37
|
import { mcpCommand } from './mcp.mjs';
|
|
@@ -38,7 +39,7 @@ import { pluginCommand } from './plugin.mjs';
|
|
|
38
39
|
import { packCommand } from './pack.mjs';
|
|
39
40
|
import { installCommand } from './skillsinstall.mjs';
|
|
40
41
|
import { compileCommand } from './skillscompile.mjs';
|
|
41
|
-
import { listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
42
|
+
import { decisionsCommand as rulesDecisions, listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
42
43
|
import { tokensCommand } from './tokens.mjs';
|
|
43
44
|
import { harnessCommand, harnessLoadersCommand } from './harness.mjs';
|
|
44
45
|
import {
|
|
@@ -63,7 +64,7 @@ import { testsCommand } from './tests.mjs';
|
|
|
63
64
|
import { whyCommand } from './why.mjs';
|
|
64
65
|
import { gateCommand } from './gate.mjs';
|
|
65
66
|
import { impactCommand } from './impact.mjs';
|
|
66
|
-
import { lessonsListCommand, lessonsReviewCommand } from './lessons.mjs';
|
|
67
|
+
import { lessonsCaptureCommand, lessonsListCommand, lessonsReviewCommand } from './lessons.mjs';
|
|
67
68
|
import { decideCommand } from './decide.mjs';
|
|
68
69
|
import { explainCommand } from './explain.mjs';
|
|
69
70
|
import { undoCommand } from './undo.mjs';
|
|
@@ -95,6 +96,10 @@ const GROUPS = new Map([
|
|
|
95
96
|
// error before anything is created — the one default here that cannot mutate.
|
|
96
97
|
['slice', 'start'],
|
|
97
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'],
|
|
98
103
|
['harness', 'detect'],
|
|
99
104
|
['bench', 'mine'],
|
|
100
105
|
// ADR-084. `shared` is the one group whose default writes nothing, and it is `plan` for
|
|
@@ -137,6 +142,7 @@ export const IMPLEMENTED = {
|
|
|
137
142
|
'skills eval': skillsEval,
|
|
138
143
|
'skills install': installCommand,
|
|
139
144
|
'skills compile': compileCommand,
|
|
145
|
+
'rules decisions': rulesDecisions,
|
|
140
146
|
'rules list': rulesList,
|
|
141
147
|
'rules show': rulesShow,
|
|
142
148
|
'rules why': rulesWhy,
|
|
@@ -146,10 +152,12 @@ export const IMPLEMENTED = {
|
|
|
146
152
|
'plan next': (args, flags) => planCommand('next', args, flags),
|
|
147
153
|
'plan done': (args, flags) => planCommand('done', args, flags),
|
|
148
154
|
// Plan 0003 R53. A group: `start` isolates the work on a branch with its brief,
|
|
149
|
-
// `finish` runs the gates, commits and records the evidence
|
|
150
|
-
//
|
|
155
|
+
// `finish` runs the gates, commits and records the evidence, and `abandon`
|
|
156
|
+
// (review 0003 Q23) takes the slice back down. All three take the slice id
|
|
157
|
+
// as a positional.
|
|
151
158
|
'slice start': (args, flags) => sliceCommand('start', args, flags),
|
|
152
159
|
'slice finish': (args, flags) => sliceCommand('finish', args, flags),
|
|
160
|
+
'slice abandon': (args, flags) => sliceCommand('abandon', args, flags),
|
|
153
161
|
'shared plan': sharedPlanCommand,
|
|
154
162
|
'shared install': sharedInstallCommand,
|
|
155
163
|
evidence: evidenceCommand,
|
|
@@ -159,6 +167,7 @@ export const IMPLEMENTED = {
|
|
|
159
167
|
why: (args, flags) => whyCommand(args, flags, COMMANDS),
|
|
160
168
|
gate: gateCommand,
|
|
161
169
|
impact: (args, flags) => impactCommand(args, flags, COMMANDS),
|
|
170
|
+
'lessons capture': lessonsCaptureCommand,
|
|
162
171
|
'lessons list': lessonsListCommand,
|
|
163
172
|
'lessons review': lessonsReviewCommand,
|
|
164
173
|
decide: decideCommand,
|
|
@@ -192,6 +201,10 @@ export const IMPLEMENTED = {
|
|
|
192
201
|
'agent-hook': agentHookCommand,
|
|
193
202
|
'agent-hooks install': (args, flags) => agentHooksCommand('install', args, flags),
|
|
194
203
|
'agent-hooks status': (args, flags) => agentHooksCommand('status', args, flags),
|
|
204
|
+
// ADR-111. The one agent this tool ships, written into each harness's own agents
|
|
205
|
+
// directory. A group like `agent-hooks`: `install` writes, `status` reads.
|
|
206
|
+
'agents install': (args, flags) => agentsCommand('install', args, flags),
|
|
207
|
+
'agents status': (args, flags) => agentsCommand('status', args, flags),
|
|
195
208
|
compare: compareCommand,
|
|
196
209
|
tokens: tokensCommand,
|
|
197
210
|
'harness detect': harnessCommand,
|