simt-dev 0.2.0__py3-none-any.whl
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.
- python/simt_dev/__init__.py +32 -0
- python/simt_dev/cli.py +225 -0
- simt_dev/__init__.py +32 -0
- simt_dev/cli.py +225 -0
- simt_dev/vendored/CHANGELOG.md +166 -0
- simt_dev/vendored/bin/simt-dev.mjs +28 -0
- simt_dev/vendored/docs/decisions/ADR-001-push-requires-approval.md +42 -0
- simt_dev/vendored/docs/decisions/ADR-002-refactor-blast-radius.md +45 -0
- simt_dev/vendored/docs/decisions/ADR-003-test-breadth.md +46 -0
- simt_dev/vendored/docs/decisions/ADR-004-hook-owns-format.md +45 -0
- simt_dev/vendored/docs/decisions/ADR-005-file-length.md +42 -0
- simt_dev/vendored/docs/decisions/ADR-006-coverage-bar.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-007-section-skeleton.md +54 -0
- simt_dev/vendored/docs/decisions/ADR-008-verdict-vocabulary.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-009-skill-layout.md +48 -0
- simt_dev/vendored/docs/decisions/ADR-010-runner-invocation.md +56 -0
- simt_dev/vendored/docs/decisions/ADR-011-windows-support.md +52 -0
- simt_dev/vendored/docs/decisions/ADR-012-drop-windows-support.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +114 -0
- simt_dev/vendored/docs/decisions/ADR-014-workspace-is-configuration.md +211 -0
- simt_dev/vendored/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +117 -0
- simt_dev/vendored/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +180 -0
- simt_dev/vendored/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +89 -0
- simt_dev/vendored/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +183 -0
- simt_dev/vendored/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +110 -0
- simt_dev/vendored/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-021-one-surface-per-repo.md +94 -0
- simt_dev/vendored/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +98 -0
- simt_dev/vendored/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +106 -0
- simt_dev/vendored/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +111 -0
- simt_dev/vendored/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +153 -0
- simt_dev/vendored/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +117 -0
- simt_dev/vendored/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +130 -0
- simt_dev/vendored/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +131 -0
- simt_dev/vendored/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +120 -0
- simt_dev/vendored/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +114 -0
- simt_dev/vendored/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +129 -0
- simt_dev/vendored/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +141 -0
- simt_dev/vendored/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +82 -0
- simt_dev/vendored/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +91 -0
- simt_dev/vendored/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +119 -0
- simt_dev/vendored/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +103 -0
- simt_dev/vendored/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +95 -0
- simt_dev/vendored/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +101 -0
- simt_dev/vendored/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +158 -0
- simt_dev/vendored/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +92 -0
- simt_dev/vendored/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +94 -0
- simt_dev/vendored/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +90 -0
- simt_dev/vendored/docs/decisions/ADR-044-compatibility-is-not-evidence.md +116 -0
- simt_dev/vendored/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +119 -0
- simt_dev/vendored/docs/decisions/ADR-046-one-rule-one-copy.md +140 -0
- simt_dev/vendored/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +141 -0
- simt_dev/vendored/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +150 -0
- simt_dev/vendored/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +153 -0
- simt_dev/vendored/docs/decisions/ADR-050-a-byte-count-is-not-a-region-and-updated-is-not-a-change-of-rules.md +102 -0
- simt_dev/vendored/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +134 -0
- simt_dev/vendored/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +101 -0
- simt_dev/vendored/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +49 -0
- simt_dev/vendored/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +98 -0
- simt_dev/vendored/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +160 -0
- simt_dev/vendored/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +115 -0
- simt_dev/vendored/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +148 -0
- simt_dev/vendored/docs/decisions/README.md +530 -0
- simt_dev/vendored/graph/skills.graph.json +1622 -0
- simt_dev/vendored/package.json +43 -0
- simt_dev/vendored/rules/core.md +76 -0
- simt_dev/vendored/rules/mdc/intake-questions.mdc +50 -0
- simt_dev/vendored/rules/mdc/preview-postflight.mdc +61 -0
- simt_dev/vendored/rules/mdc/slice-commit.mdc +52 -0
- simt_dev/vendored/rules/mdc/test-gate.mdc +54 -0
- simt_dev/vendored/rules/stacks/android.md +63 -0
- simt_dev/vendored/rules/stacks/cloudflare-workers.md +65 -0
- simt_dev/vendored/rules/stacks/connectiq.md +62 -0
- simt_dev/vendored/rules/stacks/container.md +31 -0
- simt_dev/vendored/rules/stacks/jvm.md +54 -0
- simt_dev/vendored/rules/stacks/node.md +45 -0
- simt_dev/vendored/rules/stacks/python.md +48 -0
- simt_dev/vendored/rules/stacks/react-native.md +58 -0
- simt_dev/vendored/rules/stacks/react-web.md +61 -0
- simt_dev/vendored/rules/stacks/typescript.md +21 -0
- simt_dev/vendored/rules/stacks/uv.md +32 -0
- simt_dev/vendored/src/argv.mjs +116 -0
- simt_dev/vendored/src/ci/dependabot.mjs +268 -0
- simt_dev/vendored/src/ci/workflow.mjs +360 -0
- simt_dev/vendored/src/commands/ci.mjs +285 -0
- simt_dev/vendored/src/commands/compare.mjs +195 -0
- simt_dev/vendored/src/commands/graph.mjs +73 -0
- simt_dev/vendored/src/commands/help.mjs +108 -0
- simt_dev/vendored/src/commands/hooks.mjs +471 -0
- simt_dev/vendored/src/commands/index.mjs +241 -0
- simt_dev/vendored/src/commands/init.mjs +632 -0
- simt_dev/vendored/src/commands/mcp.mjs +63 -0
- simt_dev/vendored/src/commands/rules.mjs +374 -0
- simt_dev/vendored/src/commands/skills.mjs +647 -0
- simt_dev/vendored/src/commands/skillsinstall.mjs +515 -0
- simt_dev/vendored/src/commands/sync.mjs +331 -0
- simt_dev/vendored/src/commands/upgrade.mjs +202 -0
- simt_dev/vendored/src/compare/discover.mjs +212 -0
- simt_dev/vendored/src/compare/hooksstate.mjs +159 -0
- simt_dev/vendored/src/compare/row.mjs +299 -0
- simt_dev/vendored/src/decisions/index.mjs +536 -0
- simt_dev/vendored/src/graph/load.mjs +541 -0
- simt_dev/vendored/src/hooks/capability.mjs +129 -0
- simt_dev/vendored/src/hooks/dispatcher.mjs +268 -0
- simt_dev/vendored/src/hooks/gates.mjs +144 -0
- simt_dev/vendored/src/hooks/resolve.mjs +194 -0
- simt_dev/vendored/src/hooks/shim.mjs +140 -0
- simt_dev/vendored/src/hooks/where.mjs +118 -0
- simt_dev/vendored/src/identity.mjs +218 -0
- simt_dev/vendored/src/locate.mjs +75 -0
- simt_dev/vendored/src/mcp/server.mjs +167 -0
- simt_dev/vendored/src/mcp/tools.mjs +410 -0
- simt_dev/vendored/src/name.mjs +95 -0
- simt_dev/vendored/src/output/index.mjs +96 -0
- simt_dev/vendored/src/projection/baselines.mjs +209 -0
- simt_dev/vendored/src/projection/conflicts.mjs +205 -0
- simt_dev/vendored/src/projection/manifest.mjs +478 -0
- simt_dev/vendored/src/projection/markers.mjs +349 -0
- simt_dev/vendored/src/projection/mdc.mjs +327 -0
- simt_dev/vendored/src/projection/project.mjs +504 -0
- simt_dev/vendored/src/projection/rules.mjs +640 -0
- simt_dev/vendored/src/projection/surfaces.mjs +398 -0
- simt_dev/vendored/src/projection/targets.mjs +221 -0
- simt_dev/vendored/src/resolve/fingerprint.mjs +553 -0
- simt_dev/vendored/src/resolve/near.mjs +61 -0
- simt_dev/vendored/src/resolve/resolve.mjs +457 -0
- simt_dev/vendored/src/resolve/source.mjs +105 -0
- simt_dev/vendored/src/toolchain.mjs +202 -0
- simt_dev/vendored/src/tsconfig.mjs +314 -0
- simt_dev/vendored/src/upgrade/plan.mjs +203 -0
- simt_dev/vendored/src/workspace.mjs +289 -0
- simt_dev-0.2.0.dist-info/METADATA +150 -0
- simt_dev-0.2.0.dist-info/RECORD +138 -0
- simt_dev-0.2.0.dist-info/WHEEL +4 -0
- simt_dev-0.2.0.dist-info/entry_points.txt +2 -0
- simt_dev-0.2.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// Command registry and dispatch.
|
|
2
|
+
//
|
|
3
|
+
// Every command named in docs/plans/0002-simt-dev-cli.md section 5 is registered
|
|
4
|
+
// here from day one. A command that is declared but not yet implemented exits
|
|
5
|
+
// UNSUPPORTED with a pointer to its implementation step; it never silently
|
|
6
|
+
// succeeds. An agent that calls `simt-dev compare` on an unreleased build must get a
|
|
7
|
+
// failure, not an empty result it will read as "no drift".
|
|
8
|
+
|
|
9
|
+
import { EXIT, emit, fail, isJson } from '../output/index.mjs';
|
|
10
|
+
import { help } from './help.mjs';
|
|
11
|
+
import { lintCommand } from './graph.mjs';
|
|
12
|
+
import {
|
|
13
|
+
indexCommand as skillsIndex,
|
|
14
|
+
findCommand as skillsFind,
|
|
15
|
+
briefCommand as skillsBrief,
|
|
16
|
+
showCommand as skillsShow,
|
|
17
|
+
whyCommand as skillsWhy,
|
|
18
|
+
} from './skills.mjs';
|
|
19
|
+
import { syncCommand, checkCommand } from './sync.mjs';
|
|
20
|
+
import { initCommand } from './init.mjs';
|
|
21
|
+
import { ciCommand } from './ci.mjs';
|
|
22
|
+
import { hooksCommand } from './hooks.mjs';
|
|
23
|
+
import { compareCommand } from './compare.mjs';
|
|
24
|
+
import { upgradeCommand } from './upgrade.mjs';
|
|
25
|
+
import { mcpCommand } from './mcp.mjs';
|
|
26
|
+
import { installCommand } from './skillsinstall.mjs';
|
|
27
|
+
import { listCommand as rulesList, showCommand as rulesShow, whyCommand as rulesWhy } from './rules.mjs';
|
|
28
|
+
import { parse } from '../argv.mjs';
|
|
29
|
+
import { fingerprint } from '../resolve/fingerprint.mjs';
|
|
30
|
+
import { resolveWorkspace } from '../workspace.mjs';
|
|
31
|
+
|
|
32
|
+
/** command path -> implementation sequence step (plan 0002 section 11). */
|
|
33
|
+
// Every command named in plan 0002 section 5 is now implemented, so this map is empty.
|
|
34
|
+
// It stays, rather than being deleted, because the next command should be added here
|
|
35
|
+
// first: a declared-but-unimplemented command must exit UNSUPPORTED with a pointer to
|
|
36
|
+
// its step, never silently succeed with empty output an agent will read as a result.
|
|
37
|
+
const STEPS = new Map([]);
|
|
38
|
+
|
|
39
|
+
/** groups whose subcommands may be omitted, and the default for each. */
|
|
40
|
+
const GROUPS = new Map([
|
|
41
|
+
['skills', 'index'],
|
|
42
|
+
['graph', 'lint'],
|
|
43
|
+
['rules', 'list'],
|
|
44
|
+
['hooks', 'status'],
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export async function dispatch(argv) {
|
|
48
|
+
const { flags, positional } = parseOrExit(argv);
|
|
49
|
+
|
|
50
|
+
// Help is shown only when explicitly asked for. An unknown command must fail with
|
|
51
|
+
// a usage code, not print 40 lines of prose and exit 0 — an agent that mistyped
|
|
52
|
+
// needs to be told so, not handed a wall of text it may misread as output.
|
|
53
|
+
if (flags.help || flags.version || positional[0] === 'help') {
|
|
54
|
+
return help(argv);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const [first, second] = positional;
|
|
58
|
+
const path = commandPath(first, second);
|
|
59
|
+
// Arguments after the command path. A group consumed two tokens (`graph lint`),
|
|
60
|
+
// a bare command one, so the offset depends on the shape that resolved.
|
|
61
|
+
const offset = GROUPS.has(first) && second !== undefined ? 2 : 1;
|
|
62
|
+
|
|
63
|
+
if (path === null) {
|
|
64
|
+
return fail(`unknown command \`${[first, second].filter(Boolean).join(' ')}\``, EXIT.USAGE);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const step = STEPS.get(path);
|
|
68
|
+
if (step !== undefined) {
|
|
69
|
+
// Two things a reader must be able to act on, and neither may name a path inside
|
|
70
|
+
// simt-dev: they run this tool, not its source tree. The step number is the
|
|
71
|
+
// stable handle; the file that documents it is an implementation detail.
|
|
72
|
+
return fail(
|
|
73
|
+
`\`simt-dev ${path}\` is declared but not implemented`,
|
|
74
|
+
EXIT.UNSUPPORTED,
|
|
75
|
+
`implementation step ${step} is not built yet`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const args = positional.slice(offset);
|
|
79
|
+
const IMPLEMENTED = {
|
|
80
|
+
doctor: runDoctor,
|
|
81
|
+
'graph lint': lintCommand,
|
|
82
|
+
'skills index': skillsIndex,
|
|
83
|
+
'skills find': skillsFind,
|
|
84
|
+
'skills brief': skillsBrief,
|
|
85
|
+
'skills show': skillsShow,
|
|
86
|
+
'skills why': skillsWhy,
|
|
87
|
+
'skills install': installCommand,
|
|
88
|
+
'rules list': rulesList,
|
|
89
|
+
'rules show': rulesShow,
|
|
90
|
+
'rules why': rulesWhy,
|
|
91
|
+
sync: syncCommand,
|
|
92
|
+
check: checkCommand,
|
|
93
|
+
init: initCommand,
|
|
94
|
+
ci: ciCommand,
|
|
95
|
+
'hooks install': (args, flags) => hooksCommand('install', args, flags),
|
|
96
|
+
'hooks status': (args, flags) => hooksCommand('status', args, flags),
|
|
97
|
+
'hooks run': (args, flags) => hooksCommand('run', args, flags),
|
|
98
|
+
compare: compareCommand,
|
|
99
|
+
upgrade: upgradeCommand,
|
|
100
|
+
mcp: mcpCommand,
|
|
101
|
+
};
|
|
102
|
+
const run = IMPLEMENTED[path];
|
|
103
|
+
if (run) return run(args, flags);
|
|
104
|
+
return fail(`unknown command \`${path}\``, EXIT.USAGE);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Normalise argv into a registry key, or null when unrecognised.
|
|
109
|
+
* A group with an omitted subcommand resolves to that group's default, so
|
|
110
|
+
* `simt-dev skills` is `simt-dev skills index`.
|
|
111
|
+
*/
|
|
112
|
+
function commandPath(first, second) {
|
|
113
|
+
if (first === undefined) return null;
|
|
114
|
+
if (!GROUPS.has(first)) return first;
|
|
115
|
+
|
|
116
|
+
const group = GROUPS.get(first);
|
|
117
|
+
if (second === undefined) return `${first} ${group}`;
|
|
118
|
+
// Reject a second token that is not a subcommand of this group, rather than
|
|
119
|
+
// silently routing `simt-dev skills nonsense` to the default subcommand.
|
|
120
|
+
const key = `${first} ${second}`;
|
|
121
|
+
if (!STEPS.has(key) && !KNOWN_SUBS.has(key)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return key;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Subcommands implemented now, so they must not be rejected as unknown.
|
|
129
|
+
* Implemented paths live here; declared-but-unimplemented ones live in STEPS.
|
|
130
|
+
*/
|
|
131
|
+
const KNOWN_SUBS = new Set([
|
|
132
|
+
'doctor',
|
|
133
|
+
'graph lint',
|
|
134
|
+
'skills index',
|
|
135
|
+
'skills find',
|
|
136
|
+
'skills brief',
|
|
137
|
+
'skills show',
|
|
138
|
+
'skills why',
|
|
139
|
+
'skills install',
|
|
140
|
+
'rules list',
|
|
141
|
+
'rules show',
|
|
142
|
+
'rules why',
|
|
143
|
+
'hooks install',
|
|
144
|
+
'hooks status',
|
|
145
|
+
'hooks run',
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
function parseOrExit(argv) {
|
|
149
|
+
const parsed = parse(argv);
|
|
150
|
+
if (!parsed.ok) return { flags: {}, positional: [] };
|
|
151
|
+
return parsed.value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Step 3 of the implementation sequence: classify a repo from marker files alone.
|
|
156
|
+
* Deliberately shipped early — it needs no registry, no rules and no projection,
|
|
157
|
+
* and it makes the tool useful before any content exists.
|
|
158
|
+
*/
|
|
159
|
+
async function runDoctor(positional, flags) {
|
|
160
|
+
const root = flags.root ?? positional[0] ?? process.cwd();
|
|
161
|
+
const result = await fingerprint(root);
|
|
162
|
+
// Which source answered the workspace question, and where it looked. A tool
|
|
163
|
+
// that silently fell back to the wrong directory would make every downstream
|
|
164
|
+
// answer confidently wrong, so the answer is reported rather than inferred
|
|
165
|
+
// (ADR-014).
|
|
166
|
+
const ws = await resolveWorkspace(flags);
|
|
167
|
+
|
|
168
|
+
// Under `--json` the document is the whole report, so it has to carry both
|
|
169
|
+
// failures the text path reports AND the workspace answer the text path prints.
|
|
170
|
+
// This branch used to return before either check, with `ok` hardcoded true: a
|
|
171
|
+
// caller asking whether their config is readable was told yes, and given exit
|
|
172
|
+
// 0, while the same command in a terminal exited 3 and named the file. A
|
|
173
|
+
// scripted consumer has only this document to go on, so `ok` is derived from
|
|
174
|
+
// the exit code rather than written as a constant (S10).
|
|
175
|
+
const failure = result.error
|
|
176
|
+
? { error: `${root}: ${result.error}`, remedy: null }
|
|
177
|
+
: ws.error
|
|
178
|
+
? {
|
|
179
|
+
error: `workspace config ${ws.detail}: ${ws.error}`,
|
|
180
|
+
remedy: 'fix the JSON, or remove the file',
|
|
181
|
+
}
|
|
182
|
+
: null;
|
|
183
|
+
const code = failure ? EXIT.CONFIG : EXIT.OK;
|
|
184
|
+
|
|
185
|
+
if (isJson(flags)) {
|
|
186
|
+
emit(
|
|
187
|
+
`${JSON.stringify(
|
|
188
|
+
{
|
|
189
|
+
ok: code === EXIT.OK,
|
|
190
|
+
command: 'doctor',
|
|
191
|
+
root,
|
|
192
|
+
...result,
|
|
193
|
+
// The workspace answer is a fact about which directory the tool would
|
|
194
|
+
// act on next (ADR-014). It is printed in the text report; leaving it
|
|
195
|
+
// out of the JSON meant a machine-readable caller could not see it at
|
|
196
|
+
// all, which is the one-question-two-answers defect again.
|
|
197
|
+
workspace: {
|
|
198
|
+
root: ws.root ?? null,
|
|
199
|
+
source: ws.source,
|
|
200
|
+
detail: ws.detail,
|
|
201
|
+
error: ws.error ?? null,
|
|
202
|
+
},
|
|
203
|
+
...(failure ?? {}),
|
|
204
|
+
},
|
|
205
|
+
null,
|
|
206
|
+
2,
|
|
207
|
+
)}\n`,
|
|
208
|
+
);
|
|
209
|
+
return code;
|
|
210
|
+
}
|
|
211
|
+
if (result.error) {
|
|
212
|
+
return fail(`${root}: ${result.error}`, EXIT.CONFIG);
|
|
213
|
+
}
|
|
214
|
+
emit(
|
|
215
|
+
[
|
|
216
|
+
`root ${root}`,
|
|
217
|
+
`stack ${result.stack.length > 0 ? result.stack.join(', ') : '(undetected)'}`,
|
|
218
|
+
`evidence ${result.evidence.length > 0 ? result.evidence.join(' ') : '(none)'}`,
|
|
219
|
+
// Facts are what a profile selects on when it wants to survive the
|
|
220
|
+
// framework being replaced, so they are reported with the path that proved
|
|
221
|
+
// each one rather than as a bare list (ADR-013).
|
|
222
|
+
`facts ${
|
|
223
|
+
result.facts.length > 0
|
|
224
|
+
? result.facts.map((f) => `${f}(${result.factEvidence[f] ?? '?'})`).join(' ')
|
|
225
|
+
: '(none)'
|
|
226
|
+
}`,
|
|
227
|
+
`manifest ${result.manifest ? (result.manifest.error ?? result.manifest.id ?? 'present') : '(absent)'}`,
|
|
228
|
+
`workspace ${ws.root ?? '(none)'} (${ws.source}: ${ws.detail})`,
|
|
229
|
+
].join('\n'),
|
|
230
|
+
{ maxBytes: flags.maxBytes ?? 0 },
|
|
231
|
+
);
|
|
232
|
+
// An unconfigured workspace is a fact, not a failure: a single repo with no
|
|
233
|
+
// workspace around it is a normal, supported case. A config file that EXISTS and
|
|
234
|
+
// does not parse is a real mistake, and that does fail - with the parse error,
|
|
235
|
+
// because naming the file without saying what is wrong with it sends the reader
|
|
236
|
+
// to the file and gives them nothing to look for.
|
|
237
|
+
if (ws.error) {
|
|
238
|
+
return fail(`workspace config ${ws.detail}: ${ws.error}`, EXIT.CONFIG, 'fix the JSON, or remove the file');
|
|
239
|
+
}
|
|
240
|
+
return EXIT.OK;
|
|
241
|
+
}
|