simt-dev 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
package/src/commands/lessons.mjs
CHANGED
|
@@ -17,12 +17,13 @@ import {
|
|
|
17
17
|
promoteLesson,
|
|
18
18
|
readLessonCandidates,
|
|
19
19
|
} from '../memory/lessons.mjs';
|
|
20
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
20
21
|
|
|
21
22
|
export async function lessonsListCommand(args, flags) {
|
|
22
23
|
if (args.length > 0) {
|
|
23
24
|
return fail(`unknown lessons argument ${args[0]}`, EXIT.USAGE, `run: ${CLI} lessons`);
|
|
24
25
|
}
|
|
25
|
-
const root = resolve(flags
|
|
26
|
+
const root = resolve(commandRoot(flags));
|
|
26
27
|
// Pure: listing must not capture. Capture used to run here, so `list` wrote
|
|
27
28
|
// to the store it was reading and a read-only question had write side
|
|
28
29
|
// effects. Capturing is `lessons capture` now (review 0003 Q36).
|
|
@@ -48,7 +49,7 @@ export async function lessonsListCommand(args, flags) {
|
|
|
48
49
|
};
|
|
49
50
|
|
|
50
51
|
if (isJson(flags)) {
|
|
51
|
-
emitJson(payload
|
|
52
|
+
emitJson(payload);
|
|
52
53
|
return errors.length === 0 ? EXIT.OK : EXIT.FAIL;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -81,7 +82,7 @@ export async function lessonsListCommand(args, flags) {
|
|
|
81
82
|
|
|
82
83
|
for (const err of errors) lines.push(`error ${err}`);
|
|
83
84
|
|
|
84
|
-
emit(lines.join('\n')
|
|
85
|
+
emit(lines.join('\n'));
|
|
85
86
|
return errors.length === 0 ? EXIT.OK : EXIT.FAIL;
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -96,7 +97,7 @@ export async function lessonsCaptureCommand(args, flags) {
|
|
|
96
97
|
if (args.length > 0) {
|
|
97
98
|
return fail(`unknown lessons capture argument ${args[0]}`, EXIT.USAGE, `run: ${CLI} lessons capture`);
|
|
98
99
|
}
|
|
99
|
-
const root = resolve(flags
|
|
100
|
+
const root = resolve(commandRoot(flags));
|
|
100
101
|
const fromLedger = await captureFromLedger(root);
|
|
101
102
|
const reverts = await captureRevertedCommits(root);
|
|
102
103
|
|
|
@@ -109,7 +110,7 @@ export async function lessonsCaptureCommand(args, flags) {
|
|
|
109
110
|
captured: [...fromLedger, ...reverts],
|
|
110
111
|
};
|
|
111
112
|
if (isJson(flags)) {
|
|
112
|
-
emitJson(payload
|
|
113
|
+
emitJson(payload);
|
|
113
114
|
return EXIT.OK;
|
|
114
115
|
}
|
|
115
116
|
const lines = [
|
|
@@ -120,7 +121,7 @@ export async function lessonsCaptureCommand(args, flags) {
|
|
|
120
121
|
for (const c of payload.captured) {
|
|
121
122
|
lines.push(` ${c.id} ${c.kind} ${c.summary}`);
|
|
122
123
|
}
|
|
123
|
-
emit(lines.join('\n')
|
|
124
|
+
emit(lines.join('\n'));
|
|
124
125
|
return EXIT.OK;
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -128,7 +129,7 @@ export async function lessonsReviewCommand(args, flags) {
|
|
|
128
129
|
if (args.length > 1) {
|
|
129
130
|
return fail(`unknown lessons review argument ${args[1]}`, EXIT.USAGE, `run: ${CLI} lessons review [<id>] [--yes]`);
|
|
130
131
|
}
|
|
131
|
-
const root = resolve(flags
|
|
132
|
+
const root = resolve(commandRoot(flags));
|
|
132
133
|
const mode = writeMode(flags);
|
|
133
134
|
const parsedPaths = parsePathList(flags.paths);
|
|
134
135
|
const paths = parsedPaths.length > 0 ? parsedPaths : undefined;
|
|
@@ -173,7 +174,7 @@ export async function lessonsReviewCommand(args, flags) {
|
|
|
173
174
|
};
|
|
174
175
|
|
|
175
176
|
if (isJson(flags)) {
|
|
176
|
-
emitJson(payload
|
|
177
|
+
emitJson(payload);
|
|
177
178
|
return EXIT.OK;
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -185,7 +186,7 @@ export async function lessonsReviewCommand(args, flags) {
|
|
|
185
186
|
result.markdown.trimEnd(),
|
|
186
187
|
];
|
|
187
188
|
|
|
188
|
-
emit(lines.join('\n')
|
|
189
|
+
emit(lines.join('\n'));
|
|
189
190
|
// After the report: the report says what, the diff shows it (R50).
|
|
190
191
|
emitDiffs(diffs, flags);
|
|
191
192
|
if (!mode.apply) {
|
package/src/commands/local.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { resolve } from 'node:path';
|
|
|
8
8
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
9
9
|
import { CLI, MANIFEST_FILE } from '../name.mjs';
|
|
10
10
|
import { probeLocal } from '../local/client.mjs';
|
|
11
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
11
12
|
|
|
12
13
|
function formatRoleLine(role, report) {
|
|
13
14
|
if (!report) return `${role.padEnd(8)} (unset)`;
|
|
@@ -55,7 +56,7 @@ function remedyFor(result) {
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export async function localDoctorCommand(args, flags) {
|
|
58
|
-
const root = resolve(flags
|
|
59
|
+
const root = resolve(commandRoot(flags, args));
|
|
59
60
|
const allowRemote = flags.allowRemote === true;
|
|
60
61
|
const result = await probeLocal(root, { allowRemote });
|
|
61
62
|
|
|
@@ -86,7 +87,6 @@ export async function localDoctorCommand(args, flags) {
|
|
|
86
87
|
findings: result.findings,
|
|
87
88
|
...(error ? { error, remedy, exitCode: code } : {}),
|
|
88
89
|
},
|
|
89
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
90
90
|
);
|
|
91
91
|
return code;
|
|
92
92
|
}
|
|
@@ -99,7 +99,7 @@ export async function localDoctorCommand(args, flags) {
|
|
|
99
99
|
`sockets ${result.socketsOpened}`,
|
|
100
100
|
];
|
|
101
101
|
|
|
102
|
-
emit(lines.join('\n')
|
|
102
|
+
emit(lines.join('\n'));
|
|
103
103
|
|
|
104
104
|
if (!result.ok) {
|
|
105
105
|
return fail(error, code, remedy);
|
package/src/commands/map.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// `build` on are ones a person typed at a terminal, where a few hundred
|
|
6
6
|
// milliseconds is not a bar to meet.
|
|
7
7
|
|
|
8
|
-
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
8
|
+
import { EXIT, emit, emitJson, fail, isJson, present } from '../output/index.mjs';
|
|
9
9
|
import { CLI } from '../name.mjs';
|
|
10
10
|
import { isIndexStale, readEdges, readEmbeddings, readFiles, readFts, readSymbols, reindex, searchFtsBm25 } from '../index/store.mjs';
|
|
11
11
|
import {
|
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
rankFiles,
|
|
17
17
|
renderMap,
|
|
18
18
|
} from '../index/map.mjs';
|
|
19
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
19
20
|
|
|
20
21
|
const LEVELS = new Set(['outline', 'names', 'signatures']);
|
|
21
22
|
|
|
22
23
|
export async function mapCommand(args, flags) {
|
|
23
24
|
if (args.length > 0) return fail(`unknown map argument ${args[0]}`, EXIT.USAGE, `run: ${CLI} map`);
|
|
24
|
-
const root = flags
|
|
25
|
+
const root = commandRoot(flags);
|
|
25
26
|
if (flags.recall === true) return recall(root, flags);
|
|
26
27
|
const level = flags.level ?? 'outline';
|
|
27
28
|
if (!LEVELS.has(level)) {
|
|
@@ -31,9 +32,9 @@ export async function mapCommand(args, flags) {
|
|
|
31
32
|
if (!Number.isInteger(budget) || budget < 0) {
|
|
32
33
|
return fail(`budget must be a non-negative integer`, EXIT.USAGE, `run: ${CLI} map --budget 2048`);
|
|
33
34
|
}
|
|
34
|
-
const loaded = await loadGraph(root);
|
|
35
|
-
if (loaded.error) return loaded.error;
|
|
36
35
|
const task = typeof flags.task === 'string' ? flags.task : '';
|
|
36
|
+
const loaded = await loadGraph(root, { task, embed: flags.embed === true });
|
|
37
|
+
if (loaded.error) return loaded.error;
|
|
37
38
|
const bm25 = task.trim().length > 0 ? await searchFtsBm25(root, task, { fts: loaded.fts }) : [];
|
|
38
39
|
const queryVector =
|
|
39
40
|
flags.embed === true && loaded.embeddings.length > 0
|
|
@@ -44,12 +45,19 @@ export async function mapCommand(args, flags) {
|
|
|
44
45
|
: null;
|
|
45
46
|
const ranked = rankFiles({ ...loaded, bm25, queryVector, task });
|
|
46
47
|
const rendered = renderMap(ranked, loaded.symbols, { level, budget });
|
|
48
|
+
// What the ranking is an answer to. `graph-only` says no file or symbol mentioned the
|
|
49
|
+
// task and the list is the graph hub; reading that as a hit is how `map --task
|
|
50
|
+
// zebracorn` looked useful (review 0003 Q52).
|
|
51
|
+
const matched = ranked.filter((row) => (row.score ?? 0) > 0).length;
|
|
52
|
+
const basis = task.trim().length === 0 ? 'no-task' : matched > 0 ? 'matched' : 'graph-only';
|
|
47
53
|
const payload = {
|
|
48
54
|
ok: true,
|
|
49
55
|
command: 'map',
|
|
50
56
|
root,
|
|
51
57
|
level,
|
|
52
58
|
budget,
|
|
59
|
+
matched,
|
|
60
|
+
basis,
|
|
53
61
|
shown: rendered.shown,
|
|
54
62
|
total: rendered.total,
|
|
55
63
|
truncated: rendered.truncated,
|
|
@@ -57,17 +65,18 @@ export async function mapCommand(args, flags) {
|
|
|
57
65
|
text: rendered.text,
|
|
58
66
|
};
|
|
59
67
|
if (isJson(flags)) {
|
|
60
|
-
emitJson(payload
|
|
68
|
+
emitJson(payload);
|
|
61
69
|
return EXIT.OK;
|
|
62
70
|
}
|
|
63
71
|
const lines = [
|
|
64
72
|
`level ${level}`,
|
|
73
|
+
`basis ${basis}${basis === 'graph-only' ? ' (no file or symbol mentions this task; the list is the graph hub)' : ''}`,
|
|
65
74
|
`shown ${rendered.shown}`,
|
|
66
75
|
`total ${rendered.total}`,
|
|
67
76
|
`budget ${budget}`,
|
|
68
77
|
];
|
|
69
78
|
if (rendered.text) lines.push(rendered.text);
|
|
70
|
-
emit(lines.join('\n')
|
|
79
|
+
emit(lines.join('\n'));
|
|
71
80
|
return EXIT.OK;
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -82,11 +91,7 @@ async function recall(root, flags) {
|
|
|
82
91
|
return fail(`could not measure recall (${error.message})`, EXIT.UNSUPPORTED, `run ${CLI} index in a git repository`);
|
|
83
92
|
}
|
|
84
93
|
const payload = { ok: true, command: 'map', root, recall: result };
|
|
85
|
-
|
|
86
|
-
emitJson(payload, { pretty: flags.pretty === true, maxBytes: flags.maxBytes ?? 0 });
|
|
87
|
-
return EXIT.OK;
|
|
88
|
-
}
|
|
89
|
-
emit(formatRecall(result), { maxBytes: flags.maxBytes ?? 0 });
|
|
94
|
+
present(payload, () => formatRecall(result), flags);
|
|
90
95
|
return EXIT.OK;
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -104,8 +109,12 @@ async function recall(root, flags) {
|
|
|
104
109
|
* first prompt in a fresh clone wait, and a prompt that takes a minute to appear is
|
|
105
110
|
* how a hook gets uninstalled. The next prompt gets the packet, and a command a
|
|
106
111
|
* person typed at a terminal always builds what it needs.
|
|
112
|
+
*
|
|
113
|
+
* FTS bodies and stored vectors load only when the caller names them (review 0003
|
|
114
|
+
* Q76): BM25 runs in SQLite, so the graph read pays for files, symbols and edges
|
|
115
|
+
* and nothing more.
|
|
107
116
|
*/
|
|
108
|
-
export async function loadGraph(root, { build = true } = {}) {
|
|
117
|
+
export async function loadGraph(root, { build = true, task = '', embed = false } = {}) {
|
|
109
118
|
if (build) {
|
|
110
119
|
try {
|
|
111
120
|
await reindex(root);
|
|
@@ -119,12 +128,13 @@ export async function loadGraph(root, { build = true } = {}) {
|
|
|
119
128
|
};
|
|
120
129
|
}
|
|
121
130
|
}
|
|
131
|
+
const wantFts = String(task ?? '').trim().length > 0;
|
|
122
132
|
const [files, symbols, edges, fts, embeddings] = await Promise.all([
|
|
123
133
|
readFiles(root),
|
|
124
134
|
readSymbols(root),
|
|
125
135
|
readEdges(root),
|
|
126
|
-
readFts(root),
|
|
127
|
-
readEmbeddings(root),
|
|
136
|
+
wantFts ? readFts(root) : Promise.resolve([]),
|
|
137
|
+
embed === true ? readEmbeddings(root) : Promise.resolve([]),
|
|
128
138
|
]);
|
|
129
139
|
// A present index that cannot be read is named, not served as empty
|
|
130
140
|
// (review 0003 Q44): `cold` is "nothing here", `broken` is "something is
|
package/src/commands/mcp.mjs
CHANGED
|
@@ -9,17 +9,14 @@
|
|
|
9
9
|
// misconfigured the command needs to be able to ask what it exposes without hanging, and
|
|
10
10
|
// a command that only speaks when spoken to cannot answer that.
|
|
11
11
|
|
|
12
|
-
import { readFile } from 'node:fs/promises';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
-
import { dirname, join } from 'node:path';
|
|
15
|
-
|
|
16
12
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
17
13
|
import { TOOLS } from '../mcp/tools.mjs';
|
|
18
14
|
import { PROTOCOL_VERSION, SUPPORTED_VERSIONS, serve } from '../mcp/server.mjs';
|
|
19
15
|
import { CLI } from '../name.mjs';
|
|
16
|
+
import { packageVersion } from '../package-version.mjs';
|
|
20
17
|
|
|
21
18
|
export async function mcpCommand(positional, flags) {
|
|
22
|
-
const
|
|
19
|
+
const version = packageVersion();
|
|
23
20
|
|
|
24
21
|
if (flags.list === true) {
|
|
25
22
|
if (isJson(flags)) {
|
|
@@ -53,7 +50,6 @@ export async function mcpCommand(positional, flags) {
|
|
|
53
50
|
'',
|
|
54
51
|
`serve them with: ${positional[0] ?? `${CLI} mcp`}`,
|
|
55
52
|
].join('\n'),
|
|
56
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
57
53
|
);
|
|
58
54
|
return EXIT.OK;
|
|
59
55
|
}
|
|
@@ -63,16 +59,4 @@ export async function mcpCommand(positional, flags) {
|
|
|
63
59
|
} catch (error) {
|
|
64
60
|
return fail(`mcp transport failed: ${error.message}`, EXIT.FAIL, `run: ${CLI} mcp --list to check the surface loads`);
|
|
65
61
|
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async function packageVersion() {
|
|
69
|
-
try {
|
|
70
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
71
|
-
const pkg = JSON.parse(await readFile(join(here, '..', '..', 'package.json'), 'utf8'));
|
|
72
|
-
return { version: pkg.version };
|
|
73
|
-
} catch {
|
|
74
|
-
// A missing version must not stop the server: the client already knows which
|
|
75
|
-
// build it launched, and a hard failure here would look like a protocol bug.
|
|
76
|
-
return { version: 'unknown' };
|
|
77
|
-
}
|
|
78
62
|
}
|
package/src/commands/pack.mjs
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../packs/manage.mjs';
|
|
29
29
|
import { readManifest } from '../projection/manifest.mjs';
|
|
30
30
|
import { diffField, emitDiffs } from '../diff.mjs';
|
|
31
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
31
32
|
|
|
32
33
|
export async function packCommand(sub, args, flags) {
|
|
33
34
|
if (sub === 'new') return packNew(args, flags);
|
|
@@ -40,9 +41,13 @@ export async function packCommand(sub, args, flags) {
|
|
|
40
41
|
* Where a lint or publish resolves its pack: the positional directory, the
|
|
41
42
|
* `--root`, or the working directory. `hooks` reads the same three in the same
|
|
42
43
|
* order, so the two commands cannot disagree about which directory was meant.
|
|
44
|
+
*
|
|
45
|
+
* The positional wins, as this always said it did. The code read `--root` first,
|
|
46
|
+
* so `pack lint .simt/packs/a --root ../b` judged `b` while the prose and the
|
|
47
|
+
* command's own usage promised `a` (review 0003 Q58).
|
|
43
48
|
*/
|
|
44
49
|
function packDir(args, flags) {
|
|
45
|
-
return resolve(
|
|
50
|
+
return resolve(args[0] ?? commandRoot(flags));
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
@@ -98,7 +103,7 @@ async function packNew(args, flags) {
|
|
|
98
103
|
return fail(sourceProblem, EXIT.USAGE, 'a pack source names where the bytes come from: `npm:<package>` or `git+https://…`');
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
const dir = join(resolve(flags
|
|
106
|
+
const dir = join(resolve(commandRoot(flags)), name);
|
|
102
107
|
const files = scaffoldPack({ name, source });
|
|
103
108
|
const { apply, dryRun } = writeMode(flags);
|
|
104
109
|
// A scaffold always creates into a directory that must not exist, so every
|
|
@@ -129,7 +134,6 @@ async function packNew(args, flags) {
|
|
|
129
134
|
'',
|
|
130
135
|
dryRun ? 'DRY-RUN no files written' : `PLAN nothing written; apply with: ${CLI} pack new ${name} --source ${source} --yes`,
|
|
131
136
|
].join('\n'),
|
|
132
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
133
137
|
);
|
|
134
138
|
// After the report: the report says what, the diff shows it (R50).
|
|
135
139
|
emitDiffs(diffs, flags);
|
|
@@ -149,7 +153,6 @@ async function packNew(args, flags) {
|
|
|
149
153
|
'',
|
|
150
154
|
`WROTE ${files.length} scaffold file(s); write the rule, then: ${CLI} pack lint ${dir}`,
|
|
151
155
|
].join('\n'),
|
|
152
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
153
156
|
);
|
|
154
157
|
// After the report: the report says what, the diff shows it (R50).
|
|
155
158
|
emitDiffs(diffs, flags);
|
|
@@ -187,7 +190,7 @@ async function packLint(args, flags) {
|
|
|
187
190
|
lines.push(` remedy: ${f.remedy}`);
|
|
188
191
|
}
|
|
189
192
|
lines.push('', ok ? 'PASSED the pack is well-formed' : `FAILED ${findings.length} finding(s)`);
|
|
190
|
-
emit(lines.join('\n')
|
|
193
|
+
emit(lines.join('\n'));
|
|
191
194
|
return ok ? EXIT.OK : EXIT.FAIL;
|
|
192
195
|
}
|
|
193
196
|
|
|
@@ -247,7 +250,7 @@ async function packPublish(args, flags) {
|
|
|
247
250
|
} else {
|
|
248
251
|
lines.push('', `FAILED ${result.findings.length} finding(s)`);
|
|
249
252
|
}
|
|
250
|
-
emit(lines.join('\n')
|
|
253
|
+
emit(lines.join('\n'));
|
|
251
254
|
if (result.ok && apply && result.published) {
|
|
252
255
|
note(`note never committed or pushed by this tool (ADR-001); the registry now has the bytes, the repos still need vendoring`);
|
|
253
256
|
}
|
package/src/commands/plan.mjs
CHANGED
|
@@ -3,12 +3,13 @@ import { EXIT, emit, emitJson, fail, isJson, writeMode } from '../output/index.m
|
|
|
3
3
|
import { formatCounts, planDone, planNext, planStatus } from '../plan/index.mjs';
|
|
4
4
|
import { diffField, emitDiffs } from '../diff.mjs';
|
|
5
5
|
import { CLI } from '../name.mjs';
|
|
6
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
6
7
|
|
|
7
8
|
// Preview unless --yes, and --dry-run wins even then. A tracker row marked done
|
|
8
9
|
// for a commit that was never written is a claim the next reader will believe.
|
|
9
10
|
|
|
10
11
|
export async function planCommand(sub, args, flags) {
|
|
11
|
-
const root = flags
|
|
12
|
+
const root = commandRoot(flags);
|
|
12
13
|
if (sub === 'status') return runStatus(root, flags);
|
|
13
14
|
if (sub === 'next') return runNext(root, args[0] ?? null, flags);
|
|
14
15
|
if (sub === 'done') {
|
|
@@ -35,7 +36,7 @@ async function runStatus(root, flags) {
|
|
|
35
36
|
}
|
|
36
37
|
lines.push('', `total ${formatCounts(result.totals, result.stateOrder) || '-'}`);
|
|
37
38
|
lines.push(`ready ${result.ready}`);
|
|
38
|
-
emit(lines.join('\n')
|
|
39
|
+
emit(lines.join('\n'));
|
|
39
40
|
return EXIT.OK;
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -55,7 +56,7 @@ async function runNext(root, id, flags) {
|
|
|
55
56
|
lines.push(` ${row.id} ${row.doc} depends on: ${row.dependsOn || '(none)'}${unknown}`);
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
|
-
emit(lines.join('\n')
|
|
59
|
+
emit(lines.join('\n'));
|
|
59
60
|
return EXIT.OK;
|
|
60
61
|
}
|
|
61
62
|
const next = result.next;
|
|
@@ -68,7 +69,7 @@ async function runNext(root, id, flags) {
|
|
|
68
69
|
'',
|
|
69
70
|
next.section || '(no section for this id)',
|
|
70
71
|
];
|
|
71
|
-
emit(head.join('\n')
|
|
72
|
+
emit(head.join('\n'));
|
|
72
73
|
return EXIT.OK;
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -104,7 +105,7 @@ async function runDone(root, id, sha, flags) {
|
|
|
104
105
|
}
|
|
105
106
|
if (result.evidenceRecorded) lines.push(`evidence ${result.sha}${result.test ? ` test: ${result.test}` : ''}`);
|
|
106
107
|
else if (result.evidenceNote) lines.push(`note ${result.evidenceNote}`);
|
|
107
|
-
emit(lines.join('\n')
|
|
108
|
+
emit(lines.join('\n'));
|
|
108
109
|
// After the report: the report says what, the diff shows it (R50).
|
|
109
110
|
emitDiffs(diffs, flags);
|
|
110
111
|
return EXIT.OK;
|
package/src/commands/plugin.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { EXIT, emit, emitJson, fail, isJson, writeMode } from '../output/index.m
|
|
|
10
10
|
import { CLI } from '../name.mjs';
|
|
11
11
|
import { buildPackage, PLUGIN_KINDS } from '../plugins.mjs';
|
|
12
12
|
import { diffField, emitDiffs } from '../diff.mjs';
|
|
13
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
13
14
|
|
|
14
15
|
export async function pluginCommand(args, flags) {
|
|
15
16
|
const kind = args[0];
|
|
@@ -27,7 +28,7 @@ export async function pluginCommand(args, flags) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const { apply } = writeMode(flags);
|
|
30
|
-
const root = flags
|
|
31
|
+
const root = commandRoot(flags);
|
|
31
32
|
const plan = await buildPackage(kind, { root, out, apply });
|
|
32
33
|
|
|
33
34
|
// The `--diff` pairs: the generated files, created or refreshed (R50). The
|
|
@@ -68,7 +69,6 @@ export async function pluginCommand(args, flags) {
|
|
|
68
69
|
? `WROTE ${plan.files.length} generated file(s) plus the runtime`
|
|
69
70
|
: `PLAN nothing written; apply with: ${CLI} plugin ${kind} --out ${out} --yes`,
|
|
70
71
|
].join('\n'),
|
|
71
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
72
72
|
);
|
|
73
73
|
// After the report: the report says what, the diff shows it (R50).
|
|
74
74
|
emitDiffs(diffs, flags);
|
package/src/commands/prbody.mjs
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
13
13
|
import { CLI } from '../name.mjs';
|
|
14
14
|
import { buildPrBody, renderPrBody } from '../prbody.mjs';
|
|
15
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
15
16
|
|
|
16
17
|
export async function prBodyCommand(args, flags) {
|
|
17
|
-
const root = flags
|
|
18
|
+
const root = commandRoot(flags);
|
|
18
19
|
if (args.length > 0) {
|
|
19
20
|
return fail(`pr-body takes no positional arguments`, EXIT.USAGE, `run: ${CLI} pr-body [--base <ref>]`);
|
|
20
21
|
}
|
|
@@ -36,10 +37,9 @@ export async function prBodyCommand(args, flags) {
|
|
|
36
37
|
verdict: report.verdict,
|
|
37
38
|
unreviewed: report.unreviewed,
|
|
38
39
|
},
|
|
39
|
-
{ pretty: flags.pretty === true, maxBytes: flags.maxBytes ?? 0 },
|
|
40
40
|
);
|
|
41
41
|
return EXIT.OK;
|
|
42
42
|
}
|
|
43
|
-
emit(renderPrBody(report)
|
|
43
|
+
emit(renderPrBody(report));
|
|
44
44
|
return EXIT.OK;
|
|
45
45
|
}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// content: it is gitignored, and a second run with the same blobs writes the same
|
|
3
3
|
// rows (ADR-089).
|
|
4
4
|
|
|
5
|
-
import { execFile } from 'node:child_process';
|
|
6
|
-
|
|
7
5
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
8
6
|
import { CLI } from '../name.mjs';
|
|
9
7
|
import { JSONL_FILE, SQLITE_FILE, reindex } from '../index/store.mjs';
|
|
8
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
9
|
+
import { git } from '../sys/git.mjs';
|
|
10
10
|
|
|
11
11
|
export async function indexCommand(args, flags) {
|
|
12
12
|
if (args.length > 0) return fail(`unknown index argument ${args[0]}`, EXIT.USAGE, `run: ${CLI} index`);
|
|
13
|
-
const root = flags
|
|
13
|
+
const root = commandRoot(flags);
|
|
14
14
|
let result;
|
|
15
15
|
try {
|
|
16
16
|
result = await reindex(root, {
|
|
@@ -31,7 +31,7 @@ export async function indexCommand(args, flags) {
|
|
|
31
31
|
const ignored = await isIgnored(root, file);
|
|
32
32
|
const payload = { ok: true, command: 'index', root, file, ignored, ...result };
|
|
33
33
|
if (isJson(flags)) {
|
|
34
|
-
emitJson(payload
|
|
34
|
+
emitJson(payload);
|
|
35
35
|
return EXIT.OK;
|
|
36
36
|
}
|
|
37
37
|
const lines = [
|
|
@@ -46,14 +46,15 @@ export async function indexCommand(args, flags) {
|
|
|
46
46
|
];
|
|
47
47
|
if (result.parsers === 'missing') lines.push('note index.parsers did not load, so the built-in extractors ran');
|
|
48
48
|
if (!ignored) lines.push(`note ${file} is not gitignored, and the cache is not meant to be committed`);
|
|
49
|
-
emit(lines.join('\n')
|
|
49
|
+
emit(lines.join('\n'));
|
|
50
50
|
return EXIT.OK;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function isIgnored(root, file) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
async function isIgnored(root, file) {
|
|
54
|
+
try {
|
|
55
|
+
await git(root, ['check-ignore', '-q', '--', file]);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
59
60
|
}
|
package/src/commands/review.mjs
CHANGED
|
@@ -23,7 +23,7 @@ export async function reviewCommand(args, flags, commands = []) {
|
|
|
23
23
|
if (typeof result === 'number') return result;
|
|
24
24
|
|
|
25
25
|
if (isJson(flags)) {
|
|
26
|
-
emitJson(result
|
|
26
|
+
emitJson(result);
|
|
27
27
|
return EXIT.OK;
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -60,6 +60,6 @@ export async function reviewCommand(args, flags, commands = []) {
|
|
|
60
60
|
}
|
|
61
61
|
if (result.metric) lines.push(`metric ${result.metric.note}`);
|
|
62
62
|
|
|
63
|
-
emit(lines.join('\n')
|
|
63
|
+
emit(lines.join('\n'));
|
|
64
64
|
return EXIT.OK;
|
|
65
65
|
}
|
package/src/commands/rules.mjs
CHANGED
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
// reviewer needs and exactly the fact an empty output would hide.
|
|
16
16
|
|
|
17
17
|
import { readdir } from 'node:fs/promises';
|
|
18
|
+
import { repoContext } from '../context.mjs';
|
|
18
19
|
import { join } from 'node:path';
|
|
20
|
+
import { renderDecisionsIndex } from '../decisions/index-doc.mjs';
|
|
21
|
+
import { applyPlan, change } from '../write/plan.mjs';
|
|
22
|
+
import { readBefore } from '../diff.mjs';
|
|
19
23
|
import { EXIT, emit, emitJson, fail, isJson } from '../output/index.mjs';
|
|
20
24
|
import { loadRules, select } from '../projection/project.mjs';
|
|
21
25
|
import { loadMdcRules } from '../projection/mdc.mjs';
|
|
@@ -49,8 +53,8 @@ function sorted(rules) {
|
|
|
49
53
|
return [...rules].sort((a, b) => a.id.localeCompare(b.id));
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
async function load(root) {
|
|
53
|
-
const rules = await
|
|
56
|
+
async function load(root, ctx = repoContext(root)) {
|
|
57
|
+
const rules = await ctx.rules();
|
|
54
58
|
const broken = rules.filter((r) => r.error);
|
|
55
59
|
return { rules: [...rules, ...(await loadPerSurfaceRules())], broken };
|
|
56
60
|
}
|
|
@@ -153,12 +157,12 @@ export async function listCommand(args, flags) {
|
|
|
153
157
|
if (locals.length > 0) lines.push('', `local profiles come from ${locals.map((r) => r.source).join(', ')}`);
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
emit(lines.join('\n')
|
|
160
|
+
emit(lines.join('\n'));
|
|
157
161
|
return broken.length === 0 ? EXIT.OK : EXIT.FAIL;
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
async function selectionFor(rules, root) {
|
|
161
|
-
const { stack, facts } = await fingerprint(
|
|
164
|
+
async function selectionFor(rules, root, ctx = repoContext(root)) {
|
|
165
|
+
const { stack, facts } = await ctx.fingerprint();
|
|
162
166
|
const { selected, notes } = select(rules, stack, facts);
|
|
163
167
|
return { selected: new Set(selected.map((r) => r.id)), notes };
|
|
164
168
|
}
|
|
@@ -210,9 +214,11 @@ async function loadDecisionsFor(root, all) {
|
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
/** The catalogue both surfaces answer from: every rule, its decisions, and the selection. */
|
|
213
|
-
export async function rulesCatalogue(root, { selectFor = null } = {}) {
|
|
214
|
-
const { rules, broken } = await load(root);
|
|
215
|
-
|
|
217
|
+
export async function rulesCatalogue(root, { selectFor = null, ctx = null } = {}) {
|
|
218
|
+
const { rules, broken } = await load(root, ctx ?? repoContext(root));
|
|
219
|
+
// `selectFor` is a different repository: the caller asked for the catalogue here and
|
|
220
|
+
// the selection there, so it gets its own context rather than this repository answer.
|
|
221
|
+
const selection = selectFor ? await selectionFor(rules, selectFor, repoContext(selectFor)) : null;
|
|
216
222
|
return {
|
|
217
223
|
rules: sorted(rules).map((r) => ({
|
|
218
224
|
id: r.id,
|
|
@@ -236,8 +242,8 @@ export async function rulesCatalogue(root, { selectFor = null } = {}) {
|
|
|
236
242
|
* The provenance facts for one rule or one decision record, in the shape the
|
|
237
243
|
* JSON documents carry. Text renders from the same facts; nothing re-derives.
|
|
238
244
|
*/
|
|
239
|
-
export async function rulesWhyFacts(root, what, { all = false } = {}) {
|
|
240
|
-
const { rules } = await load(root);
|
|
245
|
+
export async function rulesWhyFacts(root, what, { all = false, ctx = null } = {}) {
|
|
246
|
+
const { rules } = await load(root, ctx ?? repoContext(root));
|
|
241
247
|
if (/^ADR-\d{3,}$/i.test(what)) {
|
|
242
248
|
const id = what.toUpperCase();
|
|
243
249
|
const { ok, decisions, errors, origins } = await loadDecisionsFor(root, all);
|
|
@@ -312,7 +318,7 @@ export async function showCommand(args, flags) {
|
|
|
312
318
|
|
|
313
319
|
const adrs = citations(rule.body);
|
|
314
320
|
if (isJson(flags)) {
|
|
315
|
-
emitJson({ ok: !rule.error, ...rule, adrs }
|
|
321
|
+
emitJson({ ok: !rule.error, ...rule, adrs });
|
|
316
322
|
return rule.error ? EXIT.FAIL : EXIT.OK;
|
|
317
323
|
}
|
|
318
324
|
|
|
@@ -329,7 +335,6 @@ export async function showCommand(args, flags) {
|
|
|
329
335
|
'',
|
|
330
336
|
rule.body,
|
|
331
337
|
].join('\n'),
|
|
332
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
333
338
|
);
|
|
334
339
|
return rule.error ? EXIT.FAIL : EXIT.OK;
|
|
335
340
|
}
|
|
@@ -370,7 +375,7 @@ function ruleDirection(facts, flags) {
|
|
|
370
375
|
' this rule cites no decision, so nothing settles it and nothing can overturn it.',
|
|
371
376
|
' Either cite the ADR that produced it, or accept that it is this repo\'s own policy.',
|
|
372
377
|
);
|
|
373
|
-
emit(lines.join('\n')
|
|
378
|
+
emit(lines.join('\n'));
|
|
374
379
|
return EXIT.OK;
|
|
375
380
|
}
|
|
376
381
|
|
|
@@ -395,7 +400,7 @@ function ruleDirection(facts, flags) {
|
|
|
395
400
|
lines.push('', `shares a decision with ${facts.others.join(', ')}`);
|
|
396
401
|
}
|
|
397
402
|
|
|
398
|
-
emit(lines.join('\n')
|
|
403
|
+
emit(lines.join('\n'));
|
|
399
404
|
return EXIT.OK;
|
|
400
405
|
}
|
|
401
406
|
|
|
@@ -486,7 +491,7 @@ function adrDirection(facts, flags) {
|
|
|
486
491
|
);
|
|
487
492
|
}
|
|
488
493
|
|
|
489
|
-
emit(lines.join('\n')
|
|
494
|
+
emit(lines.join('\n'));
|
|
490
495
|
// A superseded record is not a failure; it is the supersession rule working.
|
|
491
496
|
// The caller asked a question and got a true answer.
|
|
492
497
|
return EXIT.OK;
|
|
@@ -519,6 +524,22 @@ export async function decisionsCommand(args, flags) {
|
|
|
519
524
|
file: d.file,
|
|
520
525
|
}));
|
|
521
526
|
|
|
527
|
+
// The index is output, not authorship (review 0003 Q91): the table is rendered from
|
|
528
|
+
// the same records this command lists, so a new ADR cannot leave it behind. The
|
|
529
|
+
// write is journaled like every other repo-file write, so `undo` restores the old
|
|
530
|
+
// table - the index is not internal state even though it is generated.
|
|
531
|
+
if (flags.yes === true) {
|
|
532
|
+
const rootDir = root ?? process.cwd();
|
|
533
|
+
const index = join(rootDir, 'docs', 'decisions-index.md');
|
|
534
|
+
await applyPlan(
|
|
535
|
+
rootDir,
|
|
536
|
+
[change({ path: index, before: await readBefore(index), after: renderDecisionsIndex(rows), owner: 'rules decisions', reason: 'the index follows the decision files' })],
|
|
537
|
+
{ mode: { apply: true }, label: 'rules decisions' },
|
|
538
|
+
);
|
|
539
|
+
emit(`wrote docs/decisions-index.md (${rows.length} decisions)`);
|
|
540
|
+
return EXIT.OK;
|
|
541
|
+
}
|
|
542
|
+
|
|
522
543
|
if (isJson(flags)) {
|
|
523
544
|
emitJson({ ok: true, command: 'rules decisions', root, decisions: rows });
|
|
524
545
|
return EXIT.OK;
|
|
@@ -534,6 +555,6 @@ export async function decisionsCommand(args, flags) {
|
|
|
534
555
|
const head = r.status.length > 80 ? `${r.status.slice(0, 80)}...` : r.status;
|
|
535
556
|
lines.push(`${r.id.padEnd(8)} ${r.origin.padEnd(7)} ${r.title} — ${head}`);
|
|
536
557
|
}
|
|
537
|
-
emit(lines.join('\n')
|
|
558
|
+
emit(lines.join('\n'));
|
|
538
559
|
return EXIT.OK;
|
|
539
560
|
}
|