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/skills.mjs
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// black box.
|
|
11
11
|
|
|
12
12
|
import { readFile, realpath } from 'node:fs/promises';
|
|
13
|
+
import { repoContext } from '../context.mjs';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
15
|
import { existsSync } from 'node:fs';
|
|
15
16
|
|
|
@@ -24,6 +25,7 @@ import { localSkillNodes } from '../resolve/local.mjs';
|
|
|
24
25
|
import { nearMatches } from '../resolve/near.mjs';
|
|
25
26
|
import { CLI } from '../name.mjs';
|
|
26
27
|
import { parsePathList } from '../argv.mjs';
|
|
28
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
27
29
|
|
|
28
30
|
export { parsePathList };
|
|
29
31
|
|
|
@@ -82,11 +84,11 @@ async function skillsRegistry(args) {
|
|
|
82
84
|
* costs a fingerprint plus a directory scan, so it runs only when the registry did
|
|
83
85
|
* not answer.
|
|
84
86
|
*/
|
|
85
|
-
async function findSkill(registry, root, name, verb) {
|
|
87
|
+
async function findSkill(registry, root, name, verb, ctx) {
|
|
86
88
|
if (!name) skillsFailure(`${verb} needs a skill name`, `usage: ${CLI} skills ${verb} <name>`, EXIT.USAGE);
|
|
87
89
|
const skill = registry.skills.find((s) => s.name === name);
|
|
88
90
|
if (skill) return skill;
|
|
89
|
-
const repoStack = (await fingerprint(
|
|
91
|
+
const repoStack = (await ctx.fingerprint()).signals;
|
|
90
92
|
const local = (await localSkillNodes(root, registry, repoStack)).find((s) => s.name === name);
|
|
91
93
|
if (local) return local;
|
|
92
94
|
const near = nearMatches(name, registry.skills.map((s) => s.name));
|
|
@@ -114,7 +116,10 @@ function skillFacts(skill) {
|
|
|
114
116
|
/** Tier 1: the selection the task resolves to, with the text packet's display sets. */
|
|
115
117
|
export async function skillsBriefFacts(root, args = {}) {
|
|
116
118
|
const registry = await skillsRegistry(args);
|
|
117
|
-
|
|
119
|
+
// One context per call: a second question about the same repository inside this
|
|
120
|
+
// command costs nothing (review 0003 Q67).
|
|
121
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
122
|
+
const repo = await ctx.fingerprint();
|
|
118
123
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
119
124
|
const repoStack = repo.signals;
|
|
120
125
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
@@ -246,7 +251,7 @@ export async function skillsFindFacts(root, query, args = {}) {
|
|
|
246
251
|
...registry.skills
|
|
247
252
|
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
248
253
|
.filter((r) => selectionVerdict(r.evidence, r.reasons) === null),
|
|
249
|
-
...(await localSkillNodes(root, registry, (await
|
|
254
|
+
...(await localSkillNodes(root, registry, (await (args.ctx ?? repoContext(root)).fingerprint()).signals))
|
|
250
255
|
.map((skill) => ({ skill, ...scoreSkill(skill, context) }))
|
|
251
256
|
.filter((r) => r.score > 0),
|
|
252
257
|
].sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
|
|
@@ -259,7 +264,7 @@ export async function skillsFindFacts(root, query, args = {}) {
|
|
|
259
264
|
/** Tier 2: one skill body and the metadata needed to judge it. */
|
|
260
265
|
export async function skillsShowFacts(root, name, args = {}) {
|
|
261
266
|
const registry = await skillsRegistry(args);
|
|
262
|
-
const skill = await findSkill(registry, root, name, 'show');
|
|
267
|
+
const skill = await findSkill(registry, root, name, 'show', args.ctx ?? repoContext(root));
|
|
263
268
|
const { note: workspace, root: workspaceRoot } = await announceWorkspace(args);
|
|
264
269
|
// The repo is a location for a declared source too, so `show` reads a body the
|
|
265
270
|
// caller already has instead of reporting the body missing.
|
|
@@ -287,8 +292,9 @@ export async function skillsShowFacts(root, name, args = {}) {
|
|
|
287
292
|
/** The signal chain for one skill in one repository. */
|
|
288
293
|
export async function skillsWhyFacts(root, name, args = {}) {
|
|
289
294
|
const registry = await skillsRegistry(args);
|
|
290
|
-
const
|
|
291
|
-
const
|
|
295
|
+
const ctx = args.ctx ?? repoContext(root);
|
|
296
|
+
const skill = await findSkill(registry, root, name, 'why', ctx);
|
|
297
|
+
const repo = await ctx.fingerprint();
|
|
292
298
|
const paths = args.paths ? parsePathList(args.paths) : await dirtyPaths(root);
|
|
293
299
|
const tokens = tokenize(args.task ?? '');
|
|
294
300
|
const { score, reasons } = scoreSkill(skill, { repoStack: repo.signals, taskTokens: tokens, paths });
|
|
@@ -386,7 +392,6 @@ export async function indexCommand(args, flags) {
|
|
|
386
392
|
'',
|
|
387
393
|
`deferred entries return a pointer, not a body. Fetch one with: ${CLI} skills show <name>`,
|
|
388
394
|
].join('\n'),
|
|
389
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
390
395
|
);
|
|
391
396
|
return EXIT.OK;
|
|
392
397
|
}
|
|
@@ -429,14 +434,14 @@ async function emitAllFacts(facts, flags) {
|
|
|
429
434
|
if (deferred.length > 0) {
|
|
430
435
|
lines.push('', `DEFERRED ${deferred.length}: ${deferred.map((s) => s.name).join(', ')}`);
|
|
431
436
|
}
|
|
432
|
-
emit(lines.join('\n')
|
|
437
|
+
emit(lines.join('\n'));
|
|
433
438
|
return EXIT.OK;
|
|
434
439
|
}
|
|
435
440
|
|
|
436
441
|
// --- find ---------------------------------------------------------------------
|
|
437
442
|
|
|
438
443
|
export async function findCommand(args, flags) {
|
|
439
|
-
const root = flags
|
|
444
|
+
const root = commandRoot(flags);
|
|
440
445
|
let facts;
|
|
441
446
|
try {
|
|
442
447
|
facts = await skillsFindFacts(root, args.join(' '), flags);
|
|
@@ -449,7 +454,6 @@ export async function findCommand(args, flags) {
|
|
|
449
454
|
if (isJson(flags)) {
|
|
450
455
|
emitJson(
|
|
451
456
|
{ ok: true, query, workspace, results },
|
|
452
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
453
457
|
);
|
|
454
458
|
return EXIT.OK;
|
|
455
459
|
}
|
|
@@ -469,17 +473,17 @@ export async function findCommand(args, flags) {
|
|
|
469
473
|
`${r.name.padEnd(26)} ${kb(r.bytes).padStart(7)} ${r.reasons.join(' ')}`,
|
|
470
474
|
),
|
|
471
475
|
].join('\n'),
|
|
472
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
473
476
|
);
|
|
474
477
|
return EXIT.OK;
|
|
475
478
|
}
|
|
476
479
|
|
|
477
480
|
// --- brief: tier 1 ------------------------------------------------------------
|
|
478
481
|
export async function briefCommand(args, flags) {
|
|
479
|
-
const root = flags
|
|
482
|
+
const root = commandRoot(flags);
|
|
483
|
+
const ctx = repoContext(root);
|
|
480
484
|
let facts;
|
|
481
485
|
try {
|
|
482
|
-
facts = await skillsBriefFacts(root, flags);
|
|
486
|
+
facts = await skillsBriefFacts(root, { ...flags, ctx });
|
|
483
487
|
} catch (error) {
|
|
484
488
|
return fail(error.message, error.exitCode ?? EXIT.FAIL, error.remedy);
|
|
485
489
|
}
|
|
@@ -498,21 +502,20 @@ export async function briefCommand(args, flags) {
|
|
|
498
502
|
}
|
|
499
503
|
|
|
500
504
|
if (facts.warning) {
|
|
501
|
-
const repo = await fingerprint(
|
|
505
|
+
const repo = await ctx.fingerprint();
|
|
502
506
|
emit(
|
|
503
507
|
[
|
|
504
508
|
`repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
505
509
|
'',
|
|
506
510
|
facts.warning,
|
|
507
511
|
].join('\n'),
|
|
508
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
509
512
|
);
|
|
510
513
|
return EXIT.OK;
|
|
511
514
|
}
|
|
512
515
|
|
|
513
516
|
const { shown, shownDeferred, shownUsed, pointerOnly, excludedEntries, workspace, workspaceRoot, budget } = facts;
|
|
514
517
|
const lines = [];
|
|
515
|
-
const repo = await fingerprint(
|
|
518
|
+
const repo = await ctx.fingerprint();
|
|
516
519
|
|
|
517
520
|
lines.push(
|
|
518
521
|
`RESOLVED ${shown.length} skills, ${kb(shownUsed)}${flags.all || shownUsed <= budget ? '' : ' (budget capped)'} - repo ${root.split('/').pop()} (${repoLine(repo)})`,
|
|
@@ -533,7 +536,7 @@ export async function briefCommand(args, flags) {
|
|
|
533
536
|
lines.push('', `NOT SELECTED ${hidden.length}`, '', ` notes: ${CLI} skills why <name> | ${CLI} skills show <name>`);
|
|
534
537
|
}
|
|
535
538
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
536
|
-
emit(lines.join('\n')
|
|
539
|
+
emit(lines.join('\n'));
|
|
537
540
|
return EXIT.OK;
|
|
538
541
|
}
|
|
539
542
|
|
|
@@ -578,14 +581,14 @@ export async function briefCommand(args, flags) {
|
|
|
578
581
|
}
|
|
579
582
|
|
|
580
583
|
lines.push('', 'OVERRIDE --skill <name> | --budget <bytes> | --all | --paths <globs>');
|
|
581
|
-
emit(lines.join('\n')
|
|
584
|
+
emit(lines.join('\n'));
|
|
582
585
|
return EXIT.OK;
|
|
583
586
|
}
|
|
584
587
|
|
|
585
588
|
// --- show: tier 2 -------------------------------------------------------------
|
|
586
589
|
|
|
587
590
|
export async function showCommand(args, flags) {
|
|
588
|
-
const root = flags
|
|
591
|
+
const root = commandRoot(flags);
|
|
589
592
|
let facts;
|
|
590
593
|
try {
|
|
591
594
|
facts = await skillsShowFacts(root, args[0], flags);
|
|
@@ -595,7 +598,7 @@ export async function showCommand(args, flags) {
|
|
|
595
598
|
const { skill, workspace, body, bodyFrom, bodyText } = facts;
|
|
596
599
|
const source = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
597
600
|
if (isJson(flags)) {
|
|
598
|
-
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText }
|
|
601
|
+
emitJson({ ok: true, ...skill, workspace, body, bodyFrom, bodyText });
|
|
599
602
|
return EXIT.OK;
|
|
600
603
|
}
|
|
601
604
|
emit(
|
|
@@ -618,7 +621,6 @@ export async function showCommand(args, flags) {
|
|
|
618
621
|
]
|
|
619
622
|
.filter((l) => l !== null)
|
|
620
623
|
.join('\n'),
|
|
621
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
622
624
|
);
|
|
623
625
|
return EXIT.OK;
|
|
624
626
|
}
|
|
@@ -626,7 +628,7 @@ export async function showCommand(args, flags) {
|
|
|
626
628
|
// --- why ----------------------------------------------------------------------
|
|
627
629
|
|
|
628
630
|
export async function whyCommand(args, flags) {
|
|
629
|
-
const root = flags
|
|
631
|
+
const root = commandRoot(flags);
|
|
630
632
|
let facts;
|
|
631
633
|
try {
|
|
632
634
|
facts = await skillsWhyFacts(root, args[0], flags);
|
|
@@ -641,7 +643,7 @@ export async function whyCommand(args, flags) {
|
|
|
641
643
|
// to scrape: a skill that can never be selected in this repo is a decision, not
|
|
642
644
|
// a line of output.
|
|
643
645
|
if (isJson(flags)) {
|
|
644
|
-
emitJson({ ok: true, command: 'skills why', ...facts }
|
|
646
|
+
emitJson({ ok: true, command: 'skills why', ...facts });
|
|
645
647
|
return EXIT.OK;
|
|
646
648
|
}
|
|
647
649
|
|
|
@@ -667,7 +669,7 @@ export async function whyCommand(args, flags) {
|
|
|
667
669
|
if (facts.variantOf) lines.push('', `variant-of ${facts.variantOf} (sibling group, one body per stack)`);
|
|
668
670
|
if (facts.requires?.length > 0) lines.push(`requires ${facts.requires.join(', ')}`);
|
|
669
671
|
|
|
670
|
-
emit(lines.join('\n')
|
|
672
|
+
emit(lines.join('\n'));
|
|
671
673
|
return EXIT.OK;
|
|
672
674
|
}
|
|
673
675
|
|
|
@@ -773,7 +775,7 @@ export async function evalCommand(positional, flags) {
|
|
|
773
775
|
}
|
|
774
776
|
|
|
775
777
|
lines.push('', okRun ? 'OK the resolver meets the gold corpus' : 'REGRESSION: the resolver no longer meets the gold corpus');
|
|
776
|
-
emit(lines.join('\n')
|
|
778
|
+
emit(lines.join('\n'));
|
|
777
779
|
return okRun ? EXIT.OK : EXIT.FAIL;
|
|
778
780
|
}
|
|
779
781
|
|
|
@@ -22,6 +22,7 @@ import { nearMatches } from '../resolve/near.mjs';
|
|
|
22
22
|
import { resolveWorkspace, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
23
23
|
import { CLI } from '../name.mjs';
|
|
24
24
|
import { LOCAL_DIR } from '../state.mjs';
|
|
25
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
25
26
|
|
|
26
27
|
async function readSkillRawText(skill, workspaceRoot, root) {
|
|
27
28
|
const canonical = skill.sources?.find((s) => s.role === 'canonical') ?? skill.sources?.[0];
|
|
@@ -68,7 +69,7 @@ export async function compileCommand(args, flags) {
|
|
|
68
69
|
const { registry, errors, ok } = await loadRegistry(null, flags);
|
|
69
70
|
if (!ok) return fail(errors[0], EXIT.REGISTRY, WORKSPACE_REMEDY);
|
|
70
71
|
|
|
71
|
-
const root = resolve(flags
|
|
72
|
+
const root = resolve(commandRoot(flags));
|
|
72
73
|
const ws = await resolveWorkspace(flags);
|
|
73
74
|
const targetName = args[0] ?? null;
|
|
74
75
|
|
|
@@ -117,7 +118,6 @@ export async function compileCommand(args, flags) {
|
|
|
117
118
|
splitPlan: s.splitPlan,
|
|
118
119
|
})),
|
|
119
120
|
},
|
|
120
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
121
121
|
);
|
|
122
122
|
return EXIT.OK;
|
|
123
123
|
}
|
|
@@ -150,7 +150,7 @@ export async function compileCommand(args, flags) {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
emit(lines.join('\n')
|
|
153
|
+
emit(lines.join('\n'));
|
|
154
154
|
return EXIT.OK;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -223,7 +223,6 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
223
223
|
: null,
|
|
224
224
|
proposal,
|
|
225
225
|
},
|
|
226
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
227
226
|
);
|
|
228
227
|
return EXIT.OK;
|
|
229
228
|
}
|
|
@@ -246,7 +245,7 @@ async function runSingleSkillCompile(skill, registry, { root, workspaceRoot, fla
|
|
|
246
245
|
}
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
emit(lines.join('\n')
|
|
248
|
+
emit(lines.join('\n'));
|
|
250
249
|
if (!mode.apply) {
|
|
251
250
|
note(`note dry-run nothing written; apply with: ${CLI} skills compile ${skill.name} --yes`);
|
|
252
251
|
}
|
|
@@ -38,6 +38,7 @@ import { resolveWorkspace, WORKSPACE_REMEDY } from '../workspace.mjs';
|
|
|
38
38
|
import { CLI } from '../name.mjs';
|
|
39
39
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
40
40
|
import { nameProblems } from '../graph/spec.mjs';
|
|
41
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Where skills live in this repo. The estate already has a convention - a
|
|
@@ -82,7 +83,7 @@ export async function installCommand(args, flags) {
|
|
|
82
83
|
const ws = await resolveWorkspace(flags);
|
|
83
84
|
if (ws.error) return fail(`workspace config: ${ws.error}`, EXIT.CONFIG, 'fix the JSON, or remove the file');
|
|
84
85
|
|
|
85
|
-
const root = flags
|
|
86
|
+
const root = commandRoot(flags);
|
|
86
87
|
const repo = await fingerprint(root);
|
|
87
88
|
const pick = await chooseSource(skill, repo.signals, flags, { root, workspaceRoot: ws.root });
|
|
88
89
|
if (pick.error) return fail(pick.error, EXIT.USAGE, pick.remedy);
|
|
@@ -210,7 +211,6 @@ export async function installCommand(args, flags) {
|
|
|
210
211
|
]
|
|
211
212
|
.filter((l) => l !== null)
|
|
212
213
|
.join('\n'),
|
|
213
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
216
|
|
package/src/commands/slice.mjs
CHANGED
|
@@ -52,6 +52,7 @@ import { coreIgnoreCase } from '../repo-root.mjs';
|
|
|
52
52
|
import { readManifest } from '../projection/manifest.mjs';
|
|
53
53
|
import { CLI, MANIFEST_FILE } from '../name.mjs';
|
|
54
54
|
import { LOCAL_DIR } from '../state.mjs';
|
|
55
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
55
56
|
|
|
56
57
|
const exec = promisify(execFile);
|
|
57
58
|
|
|
@@ -125,6 +126,15 @@ function subjectFor(type, id, title) {
|
|
|
125
126
|
return `${type}(${id.toLowerCase()}): ${description}`;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The trailer of every commit this tool writes: attribution to the tool, in the
|
|
131
|
+
* form git renders as a co-author. The slice-commit rule ends every message it
|
|
132
|
+
* defines with the same line, so a commit the tool writes and one an agent
|
|
133
|
+
* writes from the rule agree on attribution. Module-local: one caller needs no
|
|
134
|
+
* export, and an export is a promise to a caller (deadcode).
|
|
135
|
+
*/
|
|
136
|
+
const CO_AUTHOR = 'Co-Authored-By: Simt Dev <noreply@simt.space>';
|
|
137
|
+
|
|
128
138
|
/** Resolve one named slice through `plan next`, which already knows missing and ambiguous. */
|
|
129
139
|
async function resolveSlice(main, id) {
|
|
130
140
|
const found = await planNext(main, id);
|
|
@@ -209,7 +219,7 @@ async function releaseSlice(main, id, session) {
|
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
export async function sliceCommand(sub, args, flags) {
|
|
212
|
-
const root = flags
|
|
222
|
+
const root = commandRoot(flags);
|
|
213
223
|
if (sub === 'start') {
|
|
214
224
|
const id = args[0];
|
|
215
225
|
if (!id) return fail(`${CLI} slice start needs a slice id`, EXIT.USAGE, `run: ${CLI} plan next`);
|
|
@@ -286,7 +296,7 @@ async function startSlice(root, id, flags) {
|
|
|
286
296
|
`state ${preview.from} -> ${preview.to}`,
|
|
287
297
|
`apply ${apply}`,
|
|
288
298
|
];
|
|
289
|
-
emit(lines.join('\n')
|
|
299
|
+
emit(lines.join('\n'));
|
|
290
300
|
return EXIT.OK;
|
|
291
301
|
}
|
|
292
302
|
|
|
@@ -363,7 +373,7 @@ async function startSlice(root, id, flags) {
|
|
|
363
373
|
lease === null ? 'lease (none)' : `lease slice ${id} as ${session}`,
|
|
364
374
|
`state ${row.from} -> ${row.to}`,
|
|
365
375
|
];
|
|
366
|
-
emit(lines.join('\n')
|
|
376
|
+
emit(lines.join('\n'));
|
|
367
377
|
return EXIT.OK;
|
|
368
378
|
}
|
|
369
379
|
|
|
@@ -468,7 +478,7 @@ async function finishSlice(root, id, flags) {
|
|
|
468
478
|
`state ${row.from} -> ${row.to}`,
|
|
469
479
|
`apply ${apply}`,
|
|
470
480
|
];
|
|
471
|
-
emit(lines.join('\n')
|
|
481
|
+
emit(lines.join('\n'));
|
|
472
482
|
return EXIT.OK;
|
|
473
483
|
}
|
|
474
484
|
|
|
@@ -545,17 +555,18 @@ async function finishSlice(root, id, flags) {
|
|
|
545
555
|
return EXIT.FAIL;
|
|
546
556
|
}
|
|
547
557
|
for (const n of gates.notes ?? []) note(`note ${n}`);
|
|
548
|
-
emit(renderGateReport('pre-commit', declared, gates).join('\n')
|
|
558
|
+
emit(renderGateReport('pre-commit', declared, gates).join('\n'));
|
|
549
559
|
return EXIT.FAIL;
|
|
550
560
|
}
|
|
551
561
|
|
|
552
562
|
let sha = null;
|
|
563
|
+
const message = `${subject}\n\n${CO_AUTHOR}`;
|
|
553
564
|
try {
|
|
554
|
-
await git(root, ['commit', '-m',
|
|
565
|
+
await git(root, ['commit', '-m', message]);
|
|
555
566
|
sha = await git(root, ['rev-parse', 'HEAD']);
|
|
556
567
|
} catch (error) {
|
|
557
568
|
const detail = String(error.stderr ?? error.message ?? error).trim().split('\n')[0];
|
|
558
|
-
return fail(`the gates passed but the commit failed: ${detail}`, EXIT.FAIL, `run: git -C ${root} commit -m ${JSON.stringify(
|
|
569
|
+
return fail(`the gates passed but the commit failed: ${detail}`, EXIT.FAIL, `run: git -C ${root} commit -m ${JSON.stringify(message)}`);
|
|
559
570
|
}
|
|
560
571
|
|
|
561
572
|
const row = await planDone(main, id, { sha, test, dryRun: false });
|
|
@@ -596,7 +607,7 @@ async function finishSlice(root, id, flags) {
|
|
|
596
607
|
];
|
|
597
608
|
if (row.evidenceRecorded) lines.push(`evidence ${sha}${test ? ` test: ${test}` : ''}`);
|
|
598
609
|
else if (row.evidenceNote) lines.push(`note ${row.evidenceNote}`);
|
|
599
|
-
emit(lines.join('\n')
|
|
610
|
+
emit(lines.join('\n'));
|
|
600
611
|
return EXIT.OK;
|
|
601
612
|
}
|
|
602
613
|
|
|
@@ -645,7 +656,7 @@ async function abandonSlice(root, id, flags) {
|
|
|
645
656
|
`state ${preview.from} -> ${preview.to}`,
|
|
646
657
|
`apply ${apply}`,
|
|
647
658
|
];
|
|
648
|
-
emit(lines.join('\n')
|
|
659
|
+
emit(lines.join('\n'));
|
|
649
660
|
return EXIT.OK;
|
|
650
661
|
}
|
|
651
662
|
|
|
@@ -707,6 +718,6 @@ async function abandonSlice(root, id, flags) {
|
|
|
707
718
|
return EXIT.OK;
|
|
708
719
|
}
|
|
709
720
|
const lines = [`repo ${root}`, `slice ${id} ${doc}`, ...removed, `state ${row.from} -> ${row.to}`, `lease ${leaseLine}`];
|
|
710
|
-
emit(lines.join('\n')
|
|
721
|
+
emit(lines.join('\n'));
|
|
711
722
|
return EXIT.OK;
|
|
712
723
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// The command spec: one row per path this binary dispatches (review 0003 Q60).
|
|
2
|
+
//
|
|
3
|
+
// The list of commands existed in four places that disagreed - the dispatch map, a
|
|
4
|
+
// hand-written `--help` template, the completions table and the README - so a command
|
|
5
|
+
// could work and be undiscoverable, or be documented and absent. A row here is the one
|
|
6
|
+
// place a command is declared; `src/commands/index.mjs` derives its dispatchable paths
|
|
7
|
+
// and group defaults from it, and `test/spec.test.mjs` fails when the registry and the
|
|
8
|
+
// spec stop agreeing in either direction.
|
|
9
|
+
//
|
|
10
|
+
// The fields that only some consumers need (`tier`, `writes`, `args`, `flags`) arrive
|
|
11
|
+
// with the slices that read them: help (Q61), validation (Q62), MCP (Q66). A field no
|
|
12
|
+
// code reads is a field that drifts, and a wrong `writes` claim is worse than none.
|
|
13
|
+
//
|
|
14
|
+
// `defaultFor` marks the subcommand a bare group resolves to. It sits on the row that
|
|
15
|
+
// IS the default rather than on a separate map, so the reason for a default lives beside
|
|
16
|
+
// the command it names - `skills` alone indexes because the read is the safe default,
|
|
17
|
+
// `slice` alone starts because an id-less `start` fails before it writes anything.
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} SpecRow
|
|
21
|
+
* @property {string} path the dispatch key: `status`, or `group subcommand`
|
|
22
|
+
* @property {string} summary one phrase, printed by help and by the generated docs
|
|
23
|
+
* @property {string} [defaultFor] the group whose bare form resolves to this row
|
|
24
|
+
* @property {string} [mcp] the MCP tool name that answers this row, when one does
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** @type {SpecRow[]} */
|
|
28
|
+
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"], },
|
|
31
|
+
{ 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
|
+
// A group like `agent-hooks`: `install` writes the agent file into each harness's own
|
|
34
|
+
// directory and `status` reports what a harness carries. The default is the read.
|
|
35
|
+
{ 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 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 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', defaultFor: 'bench', flags: ["yes","all","arm","compare","cross-group","harness","labels","model","regrade","replace","reps","task"], },
|
|
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"], },
|
|
46
|
+
{ path: 'check', mcp: { name: 'check', flags: [] }, summary: 'report stale or missing generated regions and every check finding', flags: ["check","diff","dry-run"], },
|
|
47
|
+
{ path: 'ci', summary: "refresh simt-dev's own .github workflow and Dependabot files", flags: ["check","diff"], },
|
|
48
|
+
{ 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: 'doctor', mcp: { name: 'doctor', flags: [] }, summary: 'report what is wrong with this installation', writes: 'repo', flags: ["yes","diff","fix"], },
|
|
53
|
+
{ 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"], },
|
|
62
|
+
{ 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 toolchain pins', writes: 'repo', flags: ["yes","accept-conflicts","backup","diff","surfaces"], },
|
|
67
|
+
{ path: 'lease', summary: 'claim files for one session, or list the claims', flags: ["harness","session","ttl"], },
|
|
68
|
+
{ path: 'lessons capture', summary: 'scan the ledger and history for new lesson candidates', flags: ["diff","expires","paths","task"], },
|
|
69
|
+
{ 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"], },
|
|
74
|
+
// Plan 0003 R45. The default is the read: `pack` alone lints the working directory,
|
|
75
|
+
// 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"], },
|
|
81
|
+
{ 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: [], },
|
|
85
|
+
{ 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"], },
|
|
90
|
+
// ADR-084. `shared` is the one group whose default writes nothing, and it is `plan`
|
|
91
|
+
// for the same reason `skills`'s is `index`: the read answers "what would this do, and
|
|
92
|
+
// where", and `install` - the subcommand that touches a reader's home directory - is
|
|
93
|
+
// the one that needs `--yes` before it does anything.
|
|
94
|
+
{ 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"], },
|
|
105
|
+
// Plan 0003 R53. Both halves write, so a bare `slice` has no read to default to.
|
|
106
|
+
// `start` is the default because with the id it is missing it fails with a usage error
|
|
107
|
+
// before anything is created - the one default here that cannot mutate.
|
|
108
|
+
{ path: 'slice start', summary: 'give a slice its own worktree, branch and brief', defaultFor: 'slice', flags: ["session","test","type"], },
|
|
109
|
+
{ path: 'status', summary: 'gate state from the last check', flags: ["line"], },
|
|
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: [], },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Which tier a command belongs to, decided by its group: the commands a reader reaches
|
|
121
|
+
* for while working are core and appear in `--help`; the rest are advanced and appear
|
|
122
|
+
* under `help --all`. A tier is a presentation fact, not a permission, so a command
|
|
123
|
+
* cannot be missing from both pages - `test/help.test.mjs` checks that every path is on
|
|
124
|
+
* one of them (review 0003 Q61).
|
|
125
|
+
*/
|
|
126
|
+
const CORE_GROUPS = new Set(['brief', 'check', 'gate', 'graph', 'hooks', 'impact', 'init', 'map', 'plan', 'rules', 'skills', 'slice', 'status', 'sync', 'tests', 'why']);
|
|
127
|
+
|
|
128
|
+
/** The tier a path is listed under: `core` or `advanced`. */
|
|
129
|
+
export function tierOf(path) {
|
|
130
|
+
return CORE_GROUPS.has(path.split(' ')[0]) ? 'core' : 'advanced';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The tools that answer a question no command asks.
|
|
135
|
+
*
|
|
136
|
+
* `federation_query` reads the federation config and consults a sibling repository
|
|
137
|
+
* directly (src/federation/query.mjs). There is no CLI verb for it, so giving it a spec
|
|
138
|
+
* row would advertise a command this binary cannot dispatch; leaving it undeclared is
|
|
139
|
+
* how a tool list and a command list drift apart. It is listed here with the reason
|
|
140
|
+
* instead, and test/mcp-spec.test.mjs accepts a tool without a row only when this table
|
|
141
|
+
* says why.
|
|
142
|
+
*/
|
|
143
|
+
export const MCP_ONLY = [
|
|
144
|
+
{ tool: 'federation_query', why: 'a transport-only read: no command in this binary asks a sibling repository a question' },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/** The dispatchable paths, sorted - the answer to "what can this binary do". */
|
|
148
|
+
export const SPEC_PATHS = SPEC.map((row) => row.path).sort();
|
|
149
|
+
|
|
150
|
+
/** group -> the subcommand its bare form resolves to. */
|
|
151
|
+
export const GROUP_DEFAULTS = new Map(SPEC.filter((row) => row.defaultFor !== undefined).map((row) => [row.defaultFor, row.path.split(' ')[1]]));
|
package/src/commands/status.mjs
CHANGED
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
// only mode this command has is `--line`, and running something else by
|
|
11
11
|
// default would invent a second answer to what the status is.
|
|
12
12
|
|
|
13
|
-
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
13
|
+
import { EXIT, emit, emitJson, fail, isJson, present } from '../output/index.mjs';
|
|
14
14
|
import { CLI } from '../name.mjs';
|
|
15
15
|
import { currentHead, readCheckCache, renderStatusLine } from '../status.mjs';
|
|
16
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
16
17
|
|
|
17
18
|
export async function statusCommand(args, flags) {
|
|
18
|
-
const root = flags
|
|
19
|
+
const root = commandRoot(flags, args);
|
|
19
20
|
if (flags.line !== true) {
|
|
20
21
|
return fail('status needs --line', EXIT.USAGE, `run: ${CLI} status --line --root ${root}`);
|
|
21
22
|
}
|
|
@@ -25,11 +26,7 @@ export async function statusCommand(args, flags) {
|
|
|
25
26
|
// Informational, not a failure: a repository that never ran `check` — or
|
|
26
27
|
// one a writer just invalidated — has no state to report, and a prompt
|
|
27
28
|
// that errored until the first check would train its reader to ignore it.
|
|
28
|
-
|
|
29
|
-
emitJson({ ok: true, command: 'status', root, line: null, cached: false, stale: false });
|
|
30
|
-
return EXIT.OK;
|
|
31
|
-
}
|
|
32
|
-
emit('simt no cache - run check');
|
|
29
|
+
present({ ok: true, command: 'status', root, line: null, cached: false, stale: false }, () => 'simt no cache - run check', flags);
|
|
33
30
|
return EXIT.OK;
|
|
34
31
|
}
|
|
35
32
|
|