simt-dev 0.6.2 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -1
- package/README.md +46 -17
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -6
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md +94 -0
- package/docs/decisions/ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md +95 -0
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +49 -0
- package/docs/decisions/ADR-103-co-projected-duplicates-merge-down.md +74 -0
- package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md +76 -0
- package/docs/decisions/ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md +44 -0
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/README.md +53 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- package/rules/mdc/preview-postflight.mdc +5 -6
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +10 -11
- package/rules/stacks/cloudflare-workers.md +1 -1
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +4 -3
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/react-native.md +3 -5
- package/rules/stacks/react-web.md +9 -9
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +238 -15
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/policy.mjs +81 -5
- package/src/agenthooks/prompt.mjs +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shell.mjs +523 -0
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +62 -9
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +133 -17
- package/src/commands/bench.mjs +2 -1
- package/src/commands/ci.mjs +28 -3
- package/src/commands/compare.mjs +8 -7
- package/src/commands/completions.mjs +27 -0
- package/src/commands/decide.mjs +20 -6
- package/src/commands/doctor.mjs +367 -0
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/explain.mjs +52 -0
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +96 -26
- package/src/commands/hooks.mjs +258 -67
- package/src/commands/ignore.mjs +152 -0
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +69 -102
- package/src/commands/init.mjs +239 -37
- package/src/commands/lease.mjs +304 -0
- package/src/commands/lessons.mjs +77 -9
- package/src/commands/map.mjs +33 -18
- package/src/commands/mcp.mjs +2 -2
- package/src/commands/pack.mjs +9 -0
- package/src/commands/plan.mjs +13 -1
- package/src/commands/plugin.mjs +17 -2
- package/src/commands/prbody.mjs +45 -0
- package/src/commands/repoindex.mjs +4 -0
- package/src/commands/rules.mjs +219 -105
- package/src/commands/shared.mjs +36 -5
- package/src/commands/skills.mjs +329 -206
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +112 -30
- package/src/commands/slice.mjs +712 -0
- package/src/commands/status.mjs +62 -0
- package/src/commands/sync.mjs +146 -36
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +114 -0
- package/src/commands/upgrade.mjs +103 -9
- package/src/commands/why.mjs +10 -4
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -1
- package/src/completions.mjs +196 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +254 -0
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +270 -0
- package/src/federation/config.mjs +44 -0
- package/src/graph/load.mjs +52 -24
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +441 -9
- package/src/hooks/gates.mjs +14 -3
- package/src/hooks/resolve.mjs +23 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- package/src/ignore.mjs +133 -0
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +372 -55
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +545 -0
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +195 -0
- package/src/ledger/index.mjs +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +86 -13
- package/src/mcp/tools.mjs +340 -264
- package/src/memory/decide.mjs +98 -38
- package/src/memory/lessons.mjs +180 -33
- package/src/name.mjs +9 -0
- package/src/output/index.mjs +82 -12
- package/src/package-version.mjs +49 -1
- package/src/packs/manage.mjs +13 -4
- package/src/plan/index.mjs +37 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +33 -8
- package/src/prbody.mjs +253 -0
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +21 -14
- package/src/projection/manifest.mjs +82 -21
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +54 -3
- package/src/projection/shared.mjs +4 -1
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +374 -0
- package/src/state.mjs +76 -0
- package/src/status.mjs +157 -0
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- package/templates/plan/tracker.json +1 -1
package/src/commands/index.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { initCommand } from './init.mjs';
|
|
|
31
31
|
import { ciCommand } from './ci.mjs';
|
|
32
32
|
import { hooksCommand } from './hooks.mjs';
|
|
33
33
|
import { agentHookCommand, agentHooksCommand } from './agenthook.mjs';
|
|
34
|
+
import { agentsCommand } from './agent.mjs';
|
|
34
35
|
import { compareCommand } from './compare.mjs';
|
|
35
36
|
import { upgradeCommand } from './upgrade.mjs';
|
|
36
37
|
import { mcpCommand } from './mcp.mjs';
|
|
@@ -38,7 +39,7 @@ import { pluginCommand } from './plugin.mjs';
|
|
|
38
39
|
import { packCommand } from './pack.mjs';
|
|
39
40
|
import { installCommand } from './skillsinstall.mjs';
|
|
40
41
|
import { compileCommand } from './skillscompile.mjs';
|
|
41
|
-
import { listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
42
|
+
import { decisionsCommand as rulesDecisions, listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
42
43
|
import { tokensCommand } from './tokens.mjs';
|
|
43
44
|
import { harnessCommand, harnessLoadersCommand } from './harness.mjs';
|
|
44
45
|
import {
|
|
@@ -51,7 +52,11 @@ import {
|
|
|
51
52
|
benchCriticCommand,
|
|
52
53
|
} from './bench.mjs';
|
|
53
54
|
import { planCommand } from './plan.mjs';
|
|
54
|
-
import {
|
|
55
|
+
import { sliceCommand } from './slice.mjs';
|
|
56
|
+
import { leaseCommand } from './lease.mjs';
|
|
57
|
+
import { sharedPlanCommand, sharedInstallCommand } from './shared.mjs';
|
|
58
|
+
import { runDoctor } from './doctor.mjs';
|
|
59
|
+
import { statusCommand } from './status.mjs';
|
|
55
60
|
import { evidenceCommand } from './evidence.mjs';
|
|
56
61
|
import { indexCommand } from './repoindex.mjs';
|
|
57
62
|
import { mapCommand } from './map.mjs';
|
|
@@ -59,15 +64,18 @@ import { testsCommand } from './tests.mjs';
|
|
|
59
64
|
import { whyCommand } from './why.mjs';
|
|
60
65
|
import { gateCommand } from './gate.mjs';
|
|
61
66
|
import { impactCommand } from './impact.mjs';
|
|
62
|
-
import { lessonsListCommand, lessonsReviewCommand } from './lessons.mjs';
|
|
67
|
+
import { lessonsCaptureCommand, lessonsListCommand, lessonsReviewCommand } from './lessons.mjs';
|
|
63
68
|
import { decideCommand } from './decide.mjs';
|
|
69
|
+
import { explainCommand } from './explain.mjs';
|
|
70
|
+
import { undoCommand } from './undo.mjs';
|
|
71
|
+
import { prBodyCommand } from './prbody.mjs';
|
|
72
|
+
import { ignoreCommand } from './ignore.mjs';
|
|
73
|
+
import { completionsCommand } from './completions.mjs';
|
|
64
74
|
import { reviewCommand } from './review.mjs';
|
|
65
75
|
import { localDoctorCommand } from './local.mjs';
|
|
66
76
|
import { distillCommand } from './distill.mjs';
|
|
67
|
-
import { parse } from '../argv.mjs';
|
|
77
|
+
import { parse, rawJsonFlag } from '../argv.mjs';
|
|
68
78
|
import { CLI } from '../name.mjs';
|
|
69
|
-
import { fingerprint } from '../resolve/fingerprint.mjs';
|
|
70
|
-
import { resolveWorkspace } from '../workspace.mjs';
|
|
71
79
|
|
|
72
80
|
/** command path -> implementation sequence step (plan 0002 section 11). */
|
|
73
81
|
// Every command named in plan 0002 section 5 is now implemented, so this map is empty.
|
|
@@ -83,7 +91,15 @@ const GROUPS = new Map([
|
|
|
83
91
|
['rules', 'list'],
|
|
84
92
|
['hooks', 'status'],
|
|
85
93
|
['plan', 'status'],
|
|
94
|
+
// Plan 0003 R53. Both halves write, so a bare `slice` has no read to default to.
|
|
95
|
+
// `start` is the default because with the id it is missing it fails with a usage
|
|
96
|
+
// error before anything is created — the one default here that cannot mutate.
|
|
97
|
+
['slice', 'start'],
|
|
86
98
|
['agent-hooks', 'status'],
|
|
99
|
+
// A group like `agent-hooks`: `install` writes the agent file into each harness's
|
|
100
|
+
// own directory, `status` reports what each harness carries. The default is the
|
|
101
|
+
// read, the way `agent-hooks`'s is.
|
|
102
|
+
['agents', 'status'],
|
|
87
103
|
['harness', 'detect'],
|
|
88
104
|
['bench', 'mine'],
|
|
89
105
|
// ADR-084. `shared` is the one group whose default writes nothing, and it is `plan` for
|
|
@@ -114,6 +130,9 @@ const GROUPS = new Map([
|
|
|
114
130
|
export const IMPLEMENTED = {
|
|
115
131
|
brief: briefCommand,
|
|
116
132
|
doctor: runDoctor,
|
|
133
|
+
// Plan 0003 R48. A bare command: the only mode is `--line`, so there is no
|
|
134
|
+
// group and no default subcommand to resolve.
|
|
135
|
+
status: statusCommand,
|
|
117
136
|
'graph lint': lintCommand,
|
|
118
137
|
'skills index': skillsIndex,
|
|
119
138
|
'skills find': skillsFind,
|
|
@@ -123,6 +142,7 @@ export const IMPLEMENTED = {
|
|
|
123
142
|
'skills eval': skillsEval,
|
|
124
143
|
'skills install': installCommand,
|
|
125
144
|
'skills compile': compileCommand,
|
|
145
|
+
'rules decisions': rulesDecisions,
|
|
126
146
|
'rules list': rulesList,
|
|
127
147
|
'rules show': rulesShow,
|
|
128
148
|
'rules why': rulesWhy,
|
|
@@ -131,6 +151,13 @@ export const IMPLEMENTED = {
|
|
|
131
151
|
'plan status': (args, flags) => planCommand('status', args, flags),
|
|
132
152
|
'plan next': (args, flags) => planCommand('next', args, flags),
|
|
133
153
|
'plan done': (args, flags) => planCommand('done', args, flags),
|
|
154
|
+
// Plan 0003 R53. A group: `start` isolates the work on a branch with its brief,
|
|
155
|
+
// `finish` runs the gates, commits and records the evidence, and `abandon`
|
|
156
|
+
// (review 0003 Q23) takes the slice back down. All three take the slice id
|
|
157
|
+
// as a positional.
|
|
158
|
+
'slice start': (args, flags) => sliceCommand('start', args, flags),
|
|
159
|
+
'slice finish': (args, flags) => sliceCommand('finish', args, flags),
|
|
160
|
+
'slice abandon': (args, flags) => sliceCommand('abandon', args, flags),
|
|
134
161
|
'shared plan': sharedPlanCommand,
|
|
135
162
|
'shared install': sharedInstallCommand,
|
|
136
163
|
evidence: evidenceCommand,
|
|
@@ -140,9 +167,26 @@ export const IMPLEMENTED = {
|
|
|
140
167
|
why: (args, flags) => whyCommand(args, flags, COMMANDS),
|
|
141
168
|
gate: gateCommand,
|
|
142
169
|
impact: (args, flags) => impactCommand(args, flags, COMMANDS),
|
|
170
|
+
'lessons capture': lessonsCaptureCommand,
|
|
143
171
|
'lessons list': lessonsListCommand,
|
|
144
172
|
'lessons review': lessonsReviewCommand,
|
|
145
173
|
decide: decideCommand,
|
|
174
|
+
// Plan 0003 R46. A bare command: the code is a positional, so there is no
|
|
175
|
+
// group and no default subcommand to resolve.
|
|
176
|
+
explain: explainCommand,
|
|
177
|
+
// Plan 0003 R49. A bare command: restoring the last journaled set needs no
|
|
178
|
+
// subcommand, so there is no group and no default to resolve.
|
|
179
|
+
undo: undoCommand,
|
|
180
|
+
// Plan 0003 R55. A bare command: the body is derived from the tree, the
|
|
181
|
+
// ledger and the last check, so there is no subcommand to resolve and no
|
|
182
|
+
// write to confirm.
|
|
183
|
+
'pr-body': prBodyCommand,
|
|
184
|
+
// Plan 0003 R52. A bare command: the one list is `.simtignore`, so there is no
|
|
185
|
+
// group and no default subcommand to resolve.
|
|
186
|
+
ignore: ignoreCommand,
|
|
187
|
+
// Plan 0003 R54. A bare command: paths claim, no paths list, so the argument itself
|
|
188
|
+
// is the mode and there is no group to resolve.
|
|
189
|
+
lease: leaseCommand,
|
|
146
190
|
distill: distillCommand,
|
|
147
191
|
// R25 machinery needs the runnable command names for Affects command tokens,
|
|
148
192
|
// same wiring `why` and `impact` get through their lambdas.
|
|
@@ -157,6 +201,10 @@ export const IMPLEMENTED = {
|
|
|
157
201
|
'agent-hook': agentHookCommand,
|
|
158
202
|
'agent-hooks install': (args, flags) => agentHooksCommand('install', args, flags),
|
|
159
203
|
'agent-hooks status': (args, flags) => agentHooksCommand('status', args, flags),
|
|
204
|
+
// ADR-111. The one agent this tool ships, written into each harness's own agents
|
|
205
|
+
// directory. A group like `agent-hooks`: `install` writes, `status` reads.
|
|
206
|
+
'agents install': (args, flags) => agentsCommand('install', args, flags),
|
|
207
|
+
'agents status': (args, flags) => agentsCommand('status', args, flags),
|
|
160
208
|
compare: compareCommand,
|
|
161
209
|
tokens: tokensCommand,
|
|
162
210
|
'harness detect': harnessCommand,
|
|
@@ -177,6 +225,9 @@ export const IMPLEMENTED = {
|
|
|
177
225
|
// plan 0003 R43. A bare command: the kind is a positional, so there is no group and
|
|
178
226
|
// no default subcommand to resolve.
|
|
179
227
|
plugin: pluginCommand,
|
|
228
|
+
// Plan 0003 R51. A bare command: the shell is a positional. `COMMANDS` rides in
|
|
229
|
+
// from here so the engine keeps no second copy of the registry it completes.
|
|
230
|
+
completions: (args, flags) => completionsCommand(args, flags, COMMANDS),
|
|
180
231
|
// Plan 0003 R45. A group, not a bare command: `new` and `publish` write (or
|
|
181
232
|
// publish) and `lint` reads, and the dispatcher routes all three through one
|
|
182
233
|
// subcommand switch the way `hooks` does.
|
|
@@ -192,7 +243,7 @@ export const IMPLEMENTED = {
|
|
|
192
243
|
export const COMMANDS = Object.keys(IMPLEMENTED).sort();
|
|
193
244
|
|
|
194
245
|
export async function dispatch(argv) {
|
|
195
|
-
const { flags, positional } = parseOrExit(argv);
|
|
246
|
+
const { flags, positional, parseError } = parseOrExit(argv);
|
|
196
247
|
|
|
197
248
|
// The JSON shape is decided once, here, and every command inherits it. Threading a
|
|
198
249
|
// `--pretty` through twenty-five call sites would mean twenty-five places to forget,
|
|
@@ -205,6 +256,10 @@ export async function dispatch(argv) {
|
|
|
205
256
|
jsonIndentDefault(flags.pretty === true);
|
|
206
257
|
jsonFailMode(flags.json === true);
|
|
207
258
|
|
|
259
|
+
// A parse failure is already the whole answer: report it once, in the asked-for
|
|
260
|
+
// shape, rather than falling through to `unknown command` on the empty path.
|
|
261
|
+
if (parseError !== undefined) return fail(parseError.message, parseError.code);
|
|
262
|
+
|
|
208
263
|
// Help is shown only when explicitly asked for. An unknown command must fail with
|
|
209
264
|
// a usage code, not print 40 lines of prose and exit 0 — an agent that mistyped
|
|
210
265
|
// needs to be told so, not handed a wall of text it may misread as output.
|
|
@@ -267,102 +322,14 @@ function commandPath(first, second) {
|
|
|
267
322
|
}
|
|
268
323
|
|
|
269
324
|
function parseOrExit(argv) {
|
|
325
|
+
// Unreachable from `bin` (it renders the failure itself and exits), but `dispatch`
|
|
326
|
+
// is also called directly: carry the failure as data so the dispatcher reports it
|
|
327
|
+
// once, in the asked-for shape, instead of printing here and again below.
|
|
270
328
|
const parsed = parse(argv);
|
|
271
|
-
if (!parsed.ok)
|
|
329
|
+
if (!parsed.ok) {
|
|
330
|
+
const json = rawJsonFlag(argv);
|
|
331
|
+
return { flags: { json }, positional: [], parseError: parsed.error };
|
|
332
|
+
}
|
|
272
333
|
return parsed.value;
|
|
273
334
|
}
|
|
274
335
|
|
|
275
|
-
/**
|
|
276
|
-
* Step 3 of the implementation sequence: classify a repo from marker files alone.
|
|
277
|
-
* Deliberately shipped early — it needs no registry, no rules and no projection,
|
|
278
|
-
* and it makes the tool useful before any content exists.
|
|
279
|
-
*/
|
|
280
|
-
async function runDoctor(positional, flags) {
|
|
281
|
-
const root = flags.root ?? positional[0] ?? process.cwd();
|
|
282
|
-
const result = await fingerprint(root);
|
|
283
|
-
// Which source answered the workspace question, and where it looked. A tool
|
|
284
|
-
// that silently fell back to the wrong directory would make every downstream
|
|
285
|
-
// answer confidently wrong, so the answer is reported rather than inferred
|
|
286
|
-
// (ADR-014).
|
|
287
|
-
const ws = await resolveWorkspace(flags);
|
|
288
|
-
// The shared file this repository hands rules to (ADR-084). Asked here and nowhere else
|
|
289
|
-
// because it is the one thing about a repository that is true of the reader's machine
|
|
290
|
-
// rather than of the tree: `check` must not read `$HOME`, so `doctor` is the only command
|
|
291
|
-
// that can notice a region that dropped rules into a file nobody has. Reported, never
|
|
292
|
-
// blocking — a stale copy costs a reader some of their rules, which is worth knowing and
|
|
293
|
-
// is not this repository's gate failing.
|
|
294
|
-
const shared = await sharedWarning(root, result.manifest ?? null, flags);
|
|
295
|
-
|
|
296
|
-
// Under `--json` the document is the whole report, so it has to carry both
|
|
297
|
-
// failures the text path reports AND the workspace answer the text path prints.
|
|
298
|
-
// This branch used to return before either check, with `ok` hardcoded true: a
|
|
299
|
-
// caller asking whether their config is readable was told yes, and given exit
|
|
300
|
-
// 0, while the same command in a terminal exited 3 and named the file. A
|
|
301
|
-
// scripted consumer has only this document to go on, so `ok` is derived from
|
|
302
|
-
// the exit code rather than written as a constant (S10).
|
|
303
|
-
const failure = result.error
|
|
304
|
-
? { error: `${root}: ${result.error}`, remedy: null }
|
|
305
|
-
: ws.error
|
|
306
|
-
? {
|
|
307
|
-
error: `workspace config ${ws.detail}: ${ws.error}`,
|
|
308
|
-
remedy: 'fix the JSON, or remove the file',
|
|
309
|
-
}
|
|
310
|
-
: null;
|
|
311
|
-
const code = failure ? EXIT.CONFIG : EXIT.OK;
|
|
312
|
-
|
|
313
|
-
if (isJson(flags)) {
|
|
314
|
-
emitJson({
|
|
315
|
-
ok: code === EXIT.OK,
|
|
316
|
-
command: 'doctor',
|
|
317
|
-
root,
|
|
318
|
-
...result,
|
|
319
|
-
// The workspace answer is a fact about which directory the tool would
|
|
320
|
-
// act on next (ADR-014). It is printed in the text report; leaving it
|
|
321
|
-
// out of the JSON meant a machine-readable caller could not see it at
|
|
322
|
-
// all, which is the one-question-two-answers defect again.
|
|
323
|
-
workspace: {
|
|
324
|
-
root: ws.root ?? null,
|
|
325
|
-
source: ws.source,
|
|
326
|
-
detail: ws.detail,
|
|
327
|
-
error: ws.error ?? null,
|
|
328
|
-
},
|
|
329
|
-
shared,
|
|
330
|
-
...(failure ?? {}),
|
|
331
|
-
});
|
|
332
|
-
return code;
|
|
333
|
-
}
|
|
334
|
-
if (result.error) {
|
|
335
|
-
return fail(`${root}: ${result.error}`, EXIT.CONFIG);
|
|
336
|
-
}
|
|
337
|
-
emit(
|
|
338
|
-
[
|
|
339
|
-
`root ${root}`,
|
|
340
|
-
`stack ${result.stack.length > 0 ? result.stack.join(', ') : '(undetected)'}`,
|
|
341
|
-
`evidence ${result.evidence.length > 0 ? result.evidence.join(' ') : '(none)'}`,
|
|
342
|
-
// Facts are what a profile selects on when it wants to survive the
|
|
343
|
-
// framework being replaced, so they are reported with the path that proved
|
|
344
|
-
// each one rather than as a bare list (ADR-013).
|
|
345
|
-
`facts ${
|
|
346
|
-
result.facts.length > 0
|
|
347
|
-
? result.facts.map((f) => `${f}(${result.factEvidence[f] ?? '?'})`).join(' ')
|
|
348
|
-
: '(none)'
|
|
349
|
-
}`,
|
|
350
|
-
`manifest ${result.manifest ? (result.manifest.error ?? result.manifest.id ?? 'present') : '(absent)'}`,
|
|
351
|
-
`workspace ${ws.root ?? '(none)'} (${ws.source}: ${ws.detail})`,
|
|
352
|
-
// Printed only when this repository handed rules to a workspace copy: a line in
|
|
353
|
-
// every `doctor` is a line in every reader's terminal, and the reader who has
|
|
354
|
-
// nothing to install is not served by one (ADR-084).
|
|
355
|
-
...(shared.declared ? [`shared ${shared.state} ${shared.detail}`] : []),
|
|
356
|
-
].join('\n'),
|
|
357
|
-
{ maxBytes: flags.maxBytes ?? 0 },
|
|
358
|
-
);
|
|
359
|
-
// An unconfigured workspace is a fact, not a failure: a single repo with no
|
|
360
|
-
// workspace around it is a normal, supported case. A config file that EXISTS and
|
|
361
|
-
// does not parse is a real mistake, and that does fail - with the parse error,
|
|
362
|
-
// because naming the file without saying what is wrong with it sends the reader
|
|
363
|
-
// to the file and gives them nothing to look for.
|
|
364
|
-
if (ws.error) {
|
|
365
|
-
return fail(`workspace config ${ws.detail}: ${ws.error}`, EXIT.CONFIG, 'fix the JSON, or remove the file');
|
|
366
|
-
}
|
|
367
|
-
return EXIT.OK;
|
|
368
|
-
}
|