simt-dev 0.7.0 → 0.7.1
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 +158 -872
- package/README.md +19 -133
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.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/package.json +10 -2
- package/rules/core.md +7 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/shim.mjs +1 -1
- package/src/api.mjs +29 -0
- package/src/argv.mjs +29 -3
- package/src/bench/adapters.json +16 -0
- package/src/bench/corpus.mjs +150 -0
- package/src/bench/grade.mjs +23 -49
- package/src/bench/mine.mjs +199 -35
- package/src/bench/probes.mjs +99 -62
- package/src/bench/process.mjs +128 -0
- package/src/bench/report.mjs +17 -4
- package/src/bench/run.mjs +242 -142
- package/src/brief/assemble.mjs +38 -3
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +3 -4
- package/src/commands/agenthook.mjs +4 -5
- package/src/commands/bench.mjs +190 -17
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +2 -1
- 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/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +16 -12
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +3 -2
- package/src/commands/help.mjs +125 -17
- package/src/commands/hooks.mjs +100 -30
- package/src/commands/ignore.mjs +2 -1
- package/src/commands/impact.mjs +4 -7
- package/src/commands/index.mjs +273 -188
- package/src/commands/init.mjs +2 -1
- package/src/commands/lease.mjs +6 -7
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +14 -9
- package/src/commands/mcp.mjs +0 -1
- 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 +4 -3
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +33 -16
- package/src/commands/shared.mjs +2 -3
- package/src/commands/skills.mjs +28 -26
- package/src/commands/skillscompile.mjs +4 -5
- package/src/commands/skillsinstall.mjs +2 -2
- package/src/commands/slice.mjs +21 -10
- package/src/commands/spec.mjs +151 -0
- package/src/commands/status.mjs +4 -7
- package/src/commands/sync.mjs +13 -11
- package/src/commands/tests.mjs +6 -9
- package/src/commands/tokens.mjs +2 -2
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +7 -3
- package/src/commands/why.mjs +9 -8
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/graph/load.mjs +4 -1
- package/src/hooks/capability.mjs +3 -3
- package/src/hooks/dispatcher.mjs +198 -43
- package/src/hooks/gates.mjs +0 -43
- package/src/hooks/resolve.mjs +2 -7
- package/src/hooks/shim.mjs +5 -2
- package/src/identity.mjs +29 -1
- package/src/journal.mjs +78 -9
- package/src/ledger/index.mjs +30 -1
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/server.mjs +70 -16
- package/src/mcp/tools.mjs +45 -7
- package/src/measure/session.mjs +3 -2
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +111 -3
- package/src/packs/manage.mjs +17 -3
- package/src/packs.mjs +20 -2
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +20 -34
- package/src/projection/rules.mjs +4 -3
- package/src/repo-root.mjs +19 -0
- package/src/resolve/fingerprint.mjs +19 -15
- package/src/review/critic.mjs +2 -1
- package/src/toolwords.mjs +3 -0
- package/src/workspace.mjs +55 -10
- package/docs/decisions/README.md +0 -600
package/src/commands/hooks.mjs
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
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 { mkdir, readFile, realpath } from 'node:fs/promises';
|
|
15
|
+
import { mkdir, readFile, readdir, realpath, rm, stat } from 'node:fs/promises';
|
|
16
|
+
import { repoContext } from '../context.mjs';
|
|
16
17
|
import { commitJournal, journaledGitConfig, journaledRemoveFile, journaledWriteFile } from '../journal.mjs';
|
|
17
18
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
18
19
|
import { invalidateCheckCache } from '../status.mjs';
|
|
@@ -20,19 +21,23 @@ import { isAbsolute, join, relative } from 'node:path';
|
|
|
20
21
|
|
|
21
22
|
import { EXIT, emit, emitJson, fail, isJson, note, writeMode } from '../output/index.mjs';
|
|
22
23
|
import { MANIFEST, readManifest } from '../projection/manifest.mjs';
|
|
23
|
-
import { EVENTS, HOOKS_DIR, HOOKS_PATH_KEY, SHIM_MARKER, fileFor, renderChainedShim, renderEvidenceShim, renderShim } from '../hooks/shim.mjs';
|
|
24
|
+
import { CHAIN_MARKER, EVENTS, HOOKS_DIR, HOOKS_DIR_LEGACY, HOOKS_PATH_KEY, SHIM_MARKER, fileFor, renderChainedShim, renderEvidenceShim, renderShim } from '../hooks/shim.mjs';
|
|
24
25
|
import { GATE_KEY, gateCount, readHooksPath, readHooksState, wantsShim } from '../compare/hooksstate.mjs';
|
|
25
26
|
import { GATES } from '../hooks/gates.mjs';
|
|
26
|
-
import {
|
|
27
|
+
import { gateNames } from '../hooks/gates.mjs';
|
|
27
28
|
import { renderGateReport, runGates } from '../hooks/dispatcher.mjs';
|
|
28
29
|
import { CLI } from '../name.mjs';
|
|
29
30
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
30
31
|
import { packageVersion, hasNpm } from '../package-version.mjs';
|
|
32
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
31
33
|
|
|
32
34
|
export async function hooksCommand(sub, args, flags) {
|
|
33
|
-
const root = flags
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const root = commandRoot(flags, args);
|
|
36
|
+
// One context for the subcommand: install and status each ask about the same
|
|
37
|
+
// repository, and `doctor --fix` asks alongside them (review 0003 Q67).
|
|
38
|
+
const ctx = repoContext(root);
|
|
39
|
+
if (sub === 'install') return installHooks(root, flags, ctx);
|
|
40
|
+
if (sub === 'status') return hooksStatus(root, flags, ctx);
|
|
36
41
|
if (sub === 'run') return runHook(args, flags);
|
|
37
42
|
return fail(`unknown hooks subcommand ${sub}`, EXIT.USAGE, `run: ${CLI} hooks status`);
|
|
38
43
|
}
|
|
@@ -40,11 +45,12 @@ export async function hooksCommand(sub, args, flags) {
|
|
|
40
45
|
/** The shim execs this; the event name is baked into the shim, so it is arg 0. */
|
|
41
46
|
async function runHook(args, flags) {
|
|
42
47
|
const event = args[0];
|
|
43
|
-
const root = flags
|
|
48
|
+
const root = commandRoot(flags);
|
|
49
|
+
const ctx = repoContext(root);
|
|
44
50
|
if (!EVENTS.includes(event)) {
|
|
45
51
|
return fail(`unknown hook event ${event}`, EXIT.USAGE, `known events: ${EVENTS.join(', ')}`);
|
|
46
52
|
}
|
|
47
|
-
const { manifest, exists, error } = await
|
|
53
|
+
const { manifest, exists, error } = await ctx.manifest();
|
|
48
54
|
if (error) return fail(`${MANIFEST_FILE}: ${error}`, EXIT.CONFIG);
|
|
49
55
|
if (!exists) {
|
|
50
56
|
return fail(`${root} has no ${MANIFEST_FILE}; the hook cannot know its gate list`, EXIT.CONFIG, `run: ${CLI} init --root ${root}`);
|
|
@@ -64,7 +70,7 @@ async function runHook(args, flags) {
|
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
for (const n of result.notes ?? []) note(`note ${n}`);
|
|
67
|
-
emit(renderGateReport(event, manifest.gates[GATE_KEY[event]] ?? [], result).join('\n')
|
|
73
|
+
emit(renderGateReport(event, manifest.gates[GATE_KEY[event]] ?? [], result).join('\n'));
|
|
68
74
|
return result.failed ? EXIT.FAIL : EXIT.OK;
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -135,7 +141,7 @@ async function relativeTarget(root, target) {
|
|
|
135
141
|
* caller enforces in its own voice (`takeover`, and the `wanted`/`obsolete`
|
|
136
142
|
* emptiness the command refuses on)
|
|
137
143
|
*/
|
|
138
|
-
export async function planHooksInstall(root, { manifest, exists }) {
|
|
144
|
+
export async function planHooksInstall(root, { manifest, exists }, ctx = null) {
|
|
139
145
|
const declaresGate = (event) => wantsShim(manifest, exists, event);
|
|
140
146
|
const state = await readHooksState(root, { declaresGate });
|
|
141
147
|
const current = state.current;
|
|
@@ -159,8 +165,10 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
159
165
|
} catch {
|
|
160
166
|
// Unreadable here leaves the npm default; the manifest path reports it.
|
|
161
167
|
}
|
|
162
|
-
|
|
163
|
-
|
|
168
|
+
// The dynamic import stays: a hook run must not pull the fingerprint module in
|
|
169
|
+
// just by loading this one. A caller that already holds a context passes it and
|
|
170
|
+
// the walk is shared (review 0003 Q67).
|
|
171
|
+
const detected = ctx ? await ctx.fingerprint() : await (await import('../resolve/fingerprint.mjs')).fingerprint(root);
|
|
164
172
|
manager = resolveNodeRunner({ facts: detected.facts, packageManager }).id;
|
|
165
173
|
}
|
|
166
174
|
|
|
@@ -190,8 +198,8 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
190
198
|
//
|
|
191
199
|
// `foreignHooks` lists every executable file in the directory git reads hooks from -
|
|
192
200
|
// and when that directory is this tool's own, a file a person dropped in there is
|
|
193
|
-
// listed too. Chaining it means writing a shim at `.simt
|
|
194
|
-
// `.simt
|
|
201
|
+
// listed too. Chaining it means writing a shim at `.simt/hooks/<name>` whose TARGET is
|
|
202
|
+
// `.simt/hooks/<name>`: the same path. The shim overwrites the hook it was written to
|
|
195
203
|
// preserve and then runs itself, so the hook stops working and git recurses in a loop
|
|
196
204
|
// nothing reports. Confirmed at the previous release, which printed
|
|
197
205
|
// `UPDATED .simt-hooks/mine (chained: runs .simt-hooks/mine)`.
|
|
@@ -298,9 +306,50 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
298
306
|
evidenceExisting,
|
|
299
307
|
routed,
|
|
300
308
|
takeover,
|
|
309
|
+
// The legacy directory's removal rides on the same planned write, so the
|
|
310
|
+
// scan is here and the deletion is in `applyHooksInstall` (ADR-122).
|
|
311
|
+
legacy: await legacyHooksScan(root),
|
|
301
312
|
};
|
|
302
313
|
}
|
|
303
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Scan the legacy top-level shim directory (ADR-122). A repository installed
|
|
317
|
+
* before the move into the state directory still carries it, and it is
|
|
318
|
+
* removable only when every entry in it is a regular file that carries a
|
|
319
|
+
* simt-dev marker: the install re-routes git to the new directory, and a
|
|
320
|
+
* file this tool cannot recognise as its own is never deleted on its way.
|
|
321
|
+
*
|
|
322
|
+
* @returns {Promise<{path: string, removable: boolean}|null>} null when the
|
|
323
|
+
* directory is absent or empty; otherwise the directory and whether the
|
|
324
|
+
* install may take it down. `applyHooksInstall` acts on this, so the
|
|
325
|
+
* judgment is made once, in the plan. Module-local: one caller needs no
|
|
326
|
+
* export, and an export is a promise to a caller (deadcode).
|
|
327
|
+
*/
|
|
328
|
+
async function legacyHooksScan(root) {
|
|
329
|
+
const dir = join(root, HOOKS_DIR_LEGACY);
|
|
330
|
+
let entries;
|
|
331
|
+
try {
|
|
332
|
+
entries = await readdir(dir);
|
|
333
|
+
} catch {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
if (entries.length === 0) return null;
|
|
337
|
+
let removable = true;
|
|
338
|
+
for (const name of entries) {
|
|
339
|
+
const st = await stat(join(dir, name));
|
|
340
|
+
if (!st.isFile()) {
|
|
341
|
+
removable = false;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
const text = await readFile(join(dir, name), 'utf8');
|
|
345
|
+
if (!text.includes(SHIM_MARKER) && !text.includes(CHAIN_MARKER)) {
|
|
346
|
+
removable = false;
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return { path: HOOKS_DIR_LEGACY, removable };
|
|
351
|
+
}
|
|
352
|
+
|
|
304
353
|
/**
|
|
305
354
|
* Write a planned install: shims, chains, removals, and the git routing (plan 0003 R47).
|
|
306
355
|
*
|
|
@@ -309,10 +358,11 @@ export async function planHooksInstall(root, { manifest, exists }) {
|
|
|
309
358
|
* names the run that actually changed something. Reporting stays with the
|
|
310
359
|
* caller for the same reason it does in `planHooksInstall`.
|
|
311
360
|
*
|
|
312
|
-
* @returns {{wrote: string[]}} the shim paths written,
|
|
361
|
+
* @returns {{wrote: string[], removed: string[]}} the shim paths written,
|
|
362
|
+
* removed or refreshed, and the legacy-directory files taken down
|
|
313
363
|
*/
|
|
314
364
|
export async function applyHooksInstall(root, planned, journal) {
|
|
315
|
-
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current } =
|
|
365
|
+
const { wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current, legacy } =
|
|
316
366
|
planned;
|
|
317
367
|
// Shim and evidence bytes are journaled so `undo` restores them (R49), with
|
|
318
368
|
// the executable bit they are written with: a shim the run chmodded is one
|
|
@@ -322,6 +372,7 @@ export async function applyHooksInstall(root, planned, journal) {
|
|
|
322
372
|
// at their directory silently stops the repository's own hooks (review 0003
|
|
323
373
|
// Q22).
|
|
324
374
|
const wrote = [];
|
|
375
|
+
const removed = [];
|
|
325
376
|
await mkdir(join(root, HOOKS_DIR), { recursive: true });
|
|
326
377
|
for (const event of wanted) {
|
|
327
378
|
const path = join(root, fileFor(event));
|
|
@@ -352,7 +403,19 @@ export async function applyHooksInstall(root, planned, journal) {
|
|
|
352
403
|
} else if (current === wants) {
|
|
353
404
|
await journaledGitConfig(root, journal, HOOKS_PATH_KEY, null);
|
|
354
405
|
}
|
|
355
|
-
|
|
406
|
+
// The move into the state directory (ADR-122): the routing now points
|
|
407
|
+
// elsewhere, so a legacy directory left behind is the only place that looks
|
|
408
|
+
// like a hooks directory while holding nothing git reads. The scan in the
|
|
409
|
+
// plan already judged it removable — every file a simt-dev marker — so
|
|
410
|
+
// deletion is journaled per file, and `undo` restores the directory.
|
|
411
|
+
if (legacy?.removable) {
|
|
412
|
+
for (const name of await readdir(join(root, HOOKS_DIR_LEGACY))) {
|
|
413
|
+
await journaledRemoveFile(root, journal, join(root, HOOKS_DIR_LEGACY, name));
|
|
414
|
+
removed.push(`${HOOKS_DIR_LEGACY}/${name}`);
|
|
415
|
+
}
|
|
416
|
+
await rm(join(root, HOOKS_DIR_LEGACY), { recursive: true });
|
|
417
|
+
}
|
|
418
|
+
return { wrote, removed };
|
|
356
419
|
}
|
|
357
420
|
|
|
358
421
|
/**
|
|
@@ -382,8 +445,8 @@ export async function plannedDiffs(root, planned) {
|
|
|
382
445
|
return pairs;
|
|
383
446
|
}
|
|
384
447
|
|
|
385
|
-
async function installHooks(root, flags) {
|
|
386
|
-
const { manifest, exists, error } = await
|
|
448
|
+
async function installHooks(root, flags, ctx = repoContext(root)) {
|
|
449
|
+
const { manifest, exists, error } = await ctx.manifest();
|
|
387
450
|
if (error) return fail(`${MANIFEST_FILE}: ${error}`, EXIT.CONFIG);
|
|
388
451
|
if (!exists) {
|
|
389
452
|
return fail(`${root} has no ${MANIFEST_FILE}; gates must be declared before hooks can run them`, EXIT.CONFIG, `run: ${CLI} init --root ${root}`);
|
|
@@ -409,7 +472,8 @@ async function installHooks(root, flags) {
|
|
|
409
472
|
evidenceExisting,
|
|
410
473
|
routed,
|
|
411
474
|
takeover,
|
|
412
|
-
|
|
475
|
+
legacy,
|
|
476
|
+
} = await planHooksInstall(root, { manifest, exists }, ctx);
|
|
413
477
|
|
|
414
478
|
// Read through `writeMode` like every other writer, and only now: this command
|
|
415
479
|
// used to consult `flags.yes` and `flags.dryRun` directly and write when neither
|
|
@@ -438,7 +502,6 @@ async function installHooks(root, flags) {
|
|
|
438
502
|
' and replace it with a gate that checks nothing.',
|
|
439
503
|
`REMEDY add the gates this repo can actually run to ${MANIFEST_FILE}, then re-run.`,
|
|
440
504
|
].join('\n'),
|
|
441
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
442
505
|
);
|
|
443
506
|
return EXIT.FAIL;
|
|
444
507
|
}
|
|
@@ -487,11 +550,21 @@ async function installHooks(root, flags) {
|
|
|
487
550
|
note: 'commit-msg checks a verification claim against the ledger',
|
|
488
551
|
});
|
|
489
552
|
}
|
|
553
|
+
if (legacy?.removable) {
|
|
554
|
+
plan.push({
|
|
555
|
+
file: HOOKS_DIR_LEGACY,
|
|
556
|
+
action: 'removed',
|
|
557
|
+
// Named in the plan for the same reason the obsolete shims are: the
|
|
558
|
+
// reader approving this write is about to lose a directory, and the
|
|
559
|
+
// routing line above does not say the old one goes too (ADR-122).
|
|
560
|
+
note: `legacy shim directory; the shims now live in ${HOOKS_DIR}`,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
490
563
|
|
|
491
564
|
// The routing this install leaves behind, which is not always `wants`. A repository
|
|
492
565
|
// that removed its last gate has no shims to route to, and leaving `core.hooksPath`
|
|
493
566
|
// pointed at the directory would stop its own hooks running - the plan has to say so,
|
|
494
|
-
// because a plan that prints "core.hooksPath updated .simt
|
|
567
|
+
// because a plan that prints "core.hooksPath updated .simt/hooks -> .simt/hooks" while
|
|
495
568
|
// the command unsets it is worse than no plan.
|
|
496
569
|
const leaves = routed ? wants : null;
|
|
497
570
|
const pathAction =
|
|
@@ -521,7 +594,6 @@ async function installHooks(root, flags) {
|
|
|
521
594
|
' Taking over replaces that routing. Inspect the existing hooks first,',
|
|
522
595
|
' then re-run with --yes to accept, or set core.hooksPath yourself.',
|
|
523
596
|
].join('\n'),
|
|
524
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
525
597
|
);
|
|
526
598
|
return EXIT.FAIL;
|
|
527
599
|
}
|
|
@@ -566,7 +638,6 @@ async function installHooks(root, flags) {
|
|
|
566
638
|
: `PLAN nothing written; apply with: ${CLI} hooks install --root ${root} --yes`,
|
|
567
639
|
manifest,
|
|
568
640
|
),
|
|
569
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
570
641
|
);
|
|
571
642
|
// After the report: the report says what, the diff shows it (R50).
|
|
572
643
|
emitDiffs(diffs, flags);
|
|
@@ -578,7 +649,7 @@ async function installHooks(root, flags) {
|
|
|
578
649
|
const journal = [];
|
|
579
650
|
await applyHooksInstall(
|
|
580
651
|
root,
|
|
581
|
-
{ wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current },
|
|
652
|
+
{ wanted, chained, obsolete, evidenceSource, evidenceFile, npm, manager, version, routed, wants, current, legacy },
|
|
582
653
|
journal,
|
|
583
654
|
);
|
|
584
655
|
await commitJournal(root, 'hooks install', journal);
|
|
@@ -655,7 +726,6 @@ async function installHooks(root, flags) {
|
|
|
655
726
|
routed ? 'WROTE shims and set core.hooksPath' : 'WROTE no shims remain; core.hooksPath unset',
|
|
656
727
|
manifest,
|
|
657
728
|
),
|
|
658
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
659
729
|
);
|
|
660
730
|
// After the report: the report says what, the diff shows it (R50).
|
|
661
731
|
emitDiffs(diffs, flags);
|
|
@@ -684,8 +754,8 @@ function describeGates(manifest, key) {
|
|
|
684
754
|
}).join(', ');
|
|
685
755
|
}
|
|
686
756
|
|
|
687
|
-
async function hooksStatus(root, flags) {
|
|
688
|
-
const { manifest, exists, error } = await
|
|
757
|
+
async function hooksStatus(root, flags, ctx = repoContext(root)) {
|
|
758
|
+
const { manifest, exists, error } = await ctx.manifest();
|
|
689
759
|
if (error) return fail(`${MANIFEST_FILE}: ${error}`, EXIT.CONFIG);
|
|
690
760
|
|
|
691
761
|
const state = await readHooksState(root, { declaresGate: (e) => wantsShim(manifest, exists, e) });
|
|
@@ -770,14 +840,14 @@ async function hooksStatus(root, flags) {
|
|
|
770
840
|
const unknown = [...(manifest?.gates?.preCommit ?? []), ...(manifest?.gates?.prePush ?? [])].filter((n) => !GATES[n]);
|
|
771
841
|
if (unknown.length > 0) {
|
|
772
842
|
lines.push('', `unknown gates (the hook will fail): ${unknown.join(', ')}`);
|
|
773
|
-
lines.push(`known gates: ${
|
|
843
|
+
lines.push(`known gates: ${gateNames().join(', ')}`);
|
|
774
844
|
}
|
|
775
845
|
}
|
|
776
846
|
if (problems.length > 0) {
|
|
777
847
|
lines.push('', 'problems:');
|
|
778
848
|
for (const p of problems) lines.push(` ${p}`);
|
|
779
849
|
}
|
|
780
|
-
emit(lines.join('\n')
|
|
850
|
+
emit(lines.join('\n'));
|
|
781
851
|
return code;
|
|
782
852
|
}
|
|
783
853
|
|
package/src/commands/ignore.mjs
CHANGED
|
@@ -14,9 +14,10 @@ import { CLI } from '../name.mjs';
|
|
|
14
14
|
import { commitJournal, journaledWriteFile } from '../journal.mjs';
|
|
15
15
|
import { diffField, emitDiffs, readBefore } from '../diff.mjs';
|
|
16
16
|
import { CLAUDE_SETTINGS, IGNORE_FILE, parseIgnoreList, planIgnore } from '../ignore.mjs';
|
|
17
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
17
18
|
|
|
18
19
|
export async function ignoreCommand(args, flags) {
|
|
19
|
-
const root = flags
|
|
20
|
+
const root = commandRoot(flags);
|
|
20
21
|
|
|
21
22
|
let source;
|
|
22
23
|
try {
|
package/src/commands/impact.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
9
9
|
import { basename, join, resolve } from 'node:path';
|
|
10
10
|
import { promisify } from 'node:util';
|
|
11
11
|
|
|
12
|
-
import { EXIT,
|
|
12
|
+
import { EXIT, fail, present } from '../output/index.mjs';
|
|
13
13
|
import { CLI } from '../name.mjs';
|
|
14
14
|
import { discover } from '../compare/discover.mjs';
|
|
15
15
|
import { extract } from '../index/extract.mjs';
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { decisionHits, scopeHits, sortHits, sourceRoots } from '../index/govern.mjs';
|
|
26
26
|
import { decisionRecords, ruleScopes } from './why.mjs';
|
|
27
27
|
import { resolveWorkspace } from '../workspace.mjs';
|
|
28
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
28
29
|
|
|
29
30
|
const exec = promisify(execFile);
|
|
30
31
|
const TEXT_EXT = new Set(['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'yaml', 'yml', 'md', 'toml', 'xml', 'py', 'go', 'kt', 'java', 'rs']);
|
|
@@ -35,7 +36,7 @@ export async function impactCommand(args, flags, commands = []) {
|
|
|
35
36
|
if (args.length > 1) return fail(`unknown impact argument ${args[1]}`, EXIT.USAGE, `run: ${CLI} impact <file|symbol>`);
|
|
36
37
|
const target = args[0];
|
|
37
38
|
if (!target) return fail(`a file or a symbol is required`, EXIT.USAGE, `run: ${CLI} impact <file|symbol>`);
|
|
38
|
-
const root = flags
|
|
39
|
+
const root = commandRoot(flags);
|
|
39
40
|
const listed = await tracked(root);
|
|
40
41
|
if (listed.error) return listed.error;
|
|
41
42
|
const files = new Set(listed.files);
|
|
@@ -63,11 +64,7 @@ export async function impactCommand(args, flags, commands = []) {
|
|
|
63
64
|
consumers: consumers.references,
|
|
64
65
|
truncated,
|
|
65
66
|
};
|
|
66
|
-
|
|
67
|
-
emitJson(payload, { pretty: flags.pretty === true, maxBytes: flags.maxBytes ?? 0 });
|
|
68
|
-
return EXIT.OK;
|
|
69
|
-
}
|
|
70
|
-
emit(render(payload), { maxBytes: flags.maxBytes ?? 0 });
|
|
67
|
+
present(payload, () => render(payload), flags);
|
|
71
68
|
return EXIT.OK;
|
|
72
69
|
}
|
|
73
70
|
|