simt-dev 0.6.3 → 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 +162 -841
- package/README.md +21 -135
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-014-workspace-is-configuration.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-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- 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/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 +13 -3
- package/rules/core.md +9 -2
- package/rules/mdc/slice-commit.mdc +57 -4
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +3 -3
- package/rules/stacks/connectiq.md +2 -2
- 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 +3 -2
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- 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 +141 -9
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/prompt.mjs +10 -3
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/api.mjs +29 -0
- package/src/argv.mjs +37 -3
- package/src/bench/ablate.mjs +31 -14
- 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 +247 -36
- package/src/bench/probes.mjs +104 -68
- package/src/bench/process.mjs +128 -0
- package/src/bench/report.mjs +260 -84
- package/src/bench/run.mjs +251 -150
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +44 -3
- package/src/ci/workflow.mjs +46 -14
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +205 -0
- package/src/commands/agenthook.mjs +113 -20
- package/src/commands/bench.mjs +192 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +2 -1
- package/src/commands/compare.mjs +50 -49
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +10 -9
- package/src/commands/distill.mjs +4 -3
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +19 -12
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +98 -28
- package/src/commands/graph.mjs +20 -8
- package/src/commands/harness.mjs +3 -2
- package/src/commands/help.mjs +160 -31
- package/src/commands/hooks.mjs +163 -50
- package/src/commands/ignore.mjs +2 -1
- package/src/commands/impact.mjs +8 -9
- package/src/commands/index.mjs +278 -180
- package/src/commands/init.mjs +171 -38
- package/src/commands/lease.mjs +206 -39
- package/src/commands/lessons.mjs +72 -15
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +47 -27
- 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 +3 -3
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +5 -3
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +124 -33
- package/src/commands/shared.mjs +2 -3
- package/src/commands/skills.mjs +82 -45
- package/src/commands/skillscompile.mjs +12 -12
- package/src/commands/skillsinstall.mjs +14 -2
- package/src/commands/slice.mjs +396 -26
- package/src/commands/spec.mjs +151 -0
- package/src/commands/status.mjs +4 -7
- package/src/commands/sync.mjs +50 -33
- package/src/commands/tests.mjs +11 -11
- package/src/commands/tokens.mjs +2 -2
- package/src/commands/undo.mjs +29 -31
- package/src/commands/upgrade.mjs +40 -4
- package/src/commands/why.mjs +19 -12
- 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 -3
- package/src/completions.mjs +5 -0
- package/src/context.mjs +64 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +17 -2
- package/src/distill/engine.mjs +5 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +42 -20
- package/src/hooks/capability.mjs +17 -4
- package/src/hooks/dispatcher.mjs +513 -36
- package/src/hooks/gates.mjs +0 -43
- package/src/hooks/resolve.mjs +11 -10
- package/src/hooks/shim.mjs +17 -9
- package/src/identity.mjs +96 -15
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +307 -47
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +372 -84
- 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 +97 -15
- package/src/ledger/index.mjs +120 -25
- 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 +12 -13
- package/src/mcp/server.mjs +151 -38
- package/src/mcp/tools.mjs +90 -16
- package/src/measure/session.mjs +3 -2
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- 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/plan/index.mjs +31 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +6 -2
- package/src/prbody.mjs +7 -5
- 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 +31 -14
- package/src/projection/manifest.mjs +89 -53
- 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 +58 -6
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +88 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +24 -15
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +6 -3
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +8 -0
- package/src/tsconfig.mjs +39 -99
- package/src/workspace.mjs +55 -10
- package/templates/plan/tracker.json +1 -1
- package/docs/decisions/README.md +0 -591
package/src/commands/init.mjs
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { execFile } from 'node:child_process';
|
|
19
19
|
import { promisify } from 'node:util';
|
|
20
20
|
import { mkdir, readFile, symlink } from 'node:fs/promises';
|
|
21
|
-
import { commitJournal, journaledWriteFile } from '../journal.mjs';
|
|
21
|
+
import { commitJournal, ignoreJournal, journaledWriteFile } from '../journal.mjs';
|
|
22
22
|
import { invalidateCheckCache } from '../status.mjs';
|
|
23
23
|
import { dirname, join } from 'node:path';
|
|
24
24
|
|
|
@@ -31,10 +31,12 @@ import { readCapability } from '../hooks/capability.mjs';
|
|
|
31
31
|
import { readHooksState } from '../compare/hooksstate.mjs';
|
|
32
32
|
import { planWorkflow, renderWorkflow, WORKFLOWS_DIR } from '../ci/workflow.mjs';
|
|
33
33
|
import { planDependabot, renderDependabot } from '../ci/dependabot.mjs';
|
|
34
|
-
import { planTsconfigBase
|
|
34
|
+
import { planTsconfigBase } from '../tsconfig.mjs';
|
|
35
35
|
import {
|
|
36
36
|
IDENTITY_FILE,
|
|
37
37
|
INCLUDE_KEY,
|
|
38
|
+
gitignoreWithIdentity,
|
|
39
|
+
identityIgnored,
|
|
38
40
|
ignoreIdentity,
|
|
39
41
|
includeOwnIdentity,
|
|
40
42
|
includesOwnIdentity,
|
|
@@ -44,10 +46,16 @@ import {
|
|
|
44
46
|
import { pointerState, pointerTargets } from '../projection/project.mjs';
|
|
45
47
|
import { surfacesLine } from '../projection/surfaces.mjs';
|
|
46
48
|
import { plan, provenanceDigest, PRIMARY_TARGET, LOCAL_PROFILE_DIR } from '../projection/project.mjs';
|
|
49
|
+
import { planAgents, writeAgentPlan, writeSkillFile } from '../agents/render.mjs';
|
|
50
|
+
import { computeLock } from '../projection/lock.mjs';
|
|
47
51
|
import { parse as parseRegion } from '../projection/markers.mjs';
|
|
52
|
+
import { reindex, SQLITE_FILE } from '../index/store.mjs';
|
|
48
53
|
import { CLI } from '../name.mjs';
|
|
49
54
|
import { MANIFEST_FILE } from '../name.mjs';
|
|
50
55
|
import { packageVersion, hasNpm } from '../package-version.mjs';
|
|
56
|
+
import { resolveNodeRunner } from '../ecosystem/node-runner.mjs';
|
|
57
|
+
import { PACKAGE_NAME } from '../name.mjs';
|
|
58
|
+
import { commandRoot } from '../repo-root.mjs';
|
|
51
59
|
|
|
52
60
|
const run = promisify(execFile);
|
|
53
61
|
|
|
@@ -55,7 +63,7 @@ const BACKUP_TAG = 'backup-before-simt-dev';
|
|
|
55
63
|
const ADOPT_BRANCH = 'simt-dev/adopt';
|
|
56
64
|
|
|
57
65
|
export async function initCommand(positional, flags) {
|
|
58
|
-
const root = flags
|
|
66
|
+
const root = commandRoot(flags, positional);
|
|
59
67
|
|
|
60
68
|
const detected = await fingerprint(root);
|
|
61
69
|
if (detected.error) {
|
|
@@ -68,6 +76,20 @@ export async function initCommand(positional, flags) {
|
|
|
68
76
|
// a repository without node_modules obtains the binary in CI.
|
|
69
77
|
const version = packageVersion();
|
|
70
78
|
const npm = await hasNpm(root);
|
|
79
|
+
// Q31: the CI install step and the plan row that names it read the same
|
|
80
|
+
// resolver as the card and the gates — lockfile first, `packageManager` next.
|
|
81
|
+
// `manager` is the Node manager id or null (no package.json: binary channel).
|
|
82
|
+
let manager = null;
|
|
83
|
+
if (npm) {
|
|
84
|
+
let packageManager = null;
|
|
85
|
+
try {
|
|
86
|
+
const pkg = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
87
|
+
if (typeof pkg.packageManager === 'string') packageManager = pkg.packageManager;
|
|
88
|
+
} catch {
|
|
89
|
+
// Unreadable here is reported by the manifest path; the default stays npm.
|
|
90
|
+
}
|
|
91
|
+
manager = resolveNodeRunner({ facts: detected.facts, packageManager }).id;
|
|
92
|
+
}
|
|
71
93
|
const existing = await readManifest(root);
|
|
72
94
|
const { manifest: current, exists, error } = existing;
|
|
73
95
|
if (error) {
|
|
@@ -87,9 +109,15 @@ export async function initCommand(positional, flags) {
|
|
|
87
109
|
const manifest = buildManifest({ root, stack: detected.stack, sha, version, existing: current, capability, facts: detected.facts });
|
|
88
110
|
const findings = await scanRepo(root);
|
|
89
111
|
const summary = summarise(findings);
|
|
112
|
+
// Every repo-file write is journaled, so `undo` can restore this run (R49).
|
|
113
|
+
// Created before `plan()` because the skill file must be on disk when
|
|
114
|
+
// `installedSkills` reads the directory for the region's disclosure section.
|
|
115
|
+
const journal = [];
|
|
116
|
+
await writeSkillFile(root, journal);
|
|
90
117
|
const projected = await plan(root, flags);
|
|
91
118
|
const region = projected.targets[0];
|
|
92
119
|
const mdcProblems = projected.mdcProblems ?? [];
|
|
120
|
+
const crossRefs = projected.crossRefs ?? [];
|
|
93
121
|
|
|
94
122
|
// Which editors this repo's rules reach. `--surfaces` wins for this run and is
|
|
95
123
|
// recorded in the manifest so the choice survives; with nothing declared the
|
|
@@ -105,7 +133,30 @@ export async function initCommand(positional, flags) {
|
|
|
105
133
|
// not a line to save.
|
|
106
134
|
else pointerPlan.push({ ...p, state: st.state });
|
|
107
135
|
}
|
|
108
|
-
|
|
136
|
+
// Record the choice only when it was made: a flag for this run, or a key the
|
|
137
|
+
// manifest already carries with a real value. A fresh adoption with neither gets
|
|
138
|
+
// no surfaces of its own - "absent" means the global standard applies, and writing
|
|
139
|
+
// today's default into every fresh manifest would freeze it for every release after
|
|
140
|
+
// (review 0003 Q30). With a declared array, `projected.surfaces` is that same array
|
|
141
|
+
// resolved, so the assignment records rather than rewrites.
|
|
142
|
+
if (flags.surfaces !== undefined || Array.isArray(existing?.surfaces)) {
|
|
143
|
+
manifest.surfaces = projected.surfaces;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The lock's input digest includes the manifest this run writes. On a fresh adoption
|
|
147
|
+
// there is no manifest on disk yet, so the lock `plan` computed is the one for a repo
|
|
148
|
+
// that has none - and the adoption would be red on `check --frozen` the moment the
|
|
149
|
+
// manifest lands. Recompute it with the manifest about to write, so the bytes in the
|
|
150
|
+
// plan, the diff and the write are the same (Q29). A re-init preserves every lock
|
|
151
|
+
// input, so its `plan` bytes are already right and are left alone. After the
|
|
152
|
+
// surfaces line: that is the last mutation of the manifest, and the digest must
|
|
153
|
+
// cover the bytes that land on disk, not an earlier draft of them.
|
|
154
|
+
const lockTarget = projected.targets.find((t) => t.kind === 'lock');
|
|
155
|
+
if (lockTarget && !exists) {
|
|
156
|
+
const lock = await computeLock(root, flags, manifest);
|
|
157
|
+
lockTarget.text = `${JSON.stringify(lock, null, 2)}\n`;
|
|
158
|
+
lockTarget.bytes = Buffer.byteLength(lockTarget.text);
|
|
159
|
+
}
|
|
109
160
|
|
|
110
161
|
const adoption = exists || Boolean(region?.exists);
|
|
111
162
|
// One decider for `--yes --dry-run`: the inline copy used to agree with it today,
|
|
@@ -120,10 +171,25 @@ export async function initCommand(positional, flags) {
|
|
|
120
171
|
const ci = await planWorkflow(root);
|
|
121
172
|
const dependabot = await planDependabot(root);
|
|
122
173
|
const tsconfigBase = await planTsconfigBase(root);
|
|
174
|
+
// ADR-111. The one agent this tool ships, planned with the rest so the operator sees
|
|
175
|
+
// it in the same preview that lists everything else. Written in the apply path with
|
|
176
|
+
// everything else, journaled the same way.
|
|
177
|
+
const agentPlan = await planAgents(root);
|
|
123
178
|
// Per-developer, and planned with the rest so the operator sees it in the same
|
|
124
179
|
// preview that lists everything else. `exists` short-circuits the write: a
|
|
125
180
|
// repository that already has one keeps it exactly as it is.
|
|
126
181
|
const identity = await resolveIdentity(root);
|
|
182
|
+
// The .gitignore edit and the include repoint are writes too, so they get plan
|
|
183
|
+
// lines of their own: a line the run does not print is a write the reader
|
|
184
|
+
// approves from the diff afterwards (review 0003 Q30). The before-bytes are read
|
|
185
|
+
// once here and reused by the --diff pair below, so the preview's before side is
|
|
186
|
+
// the same bytes the plan's verb was decided from.
|
|
187
|
+
let gitignoreBefore = null;
|
|
188
|
+
let gitignoreAction = null;
|
|
189
|
+
if (!identity.exists && identity.from !== null && !(await identityIgnored(root))) {
|
|
190
|
+
gitignoreBefore = await readBefore(join(root, '.gitignore'));
|
|
191
|
+
gitignoreAction = gitignoreBefore === null ? 'create' : 'update';
|
|
192
|
+
}
|
|
127
193
|
const identityPlan = {
|
|
128
194
|
file: IDENTITY_FILE,
|
|
129
195
|
action: identity.exists ? 'unchanged' : identity.from === null ? 'skip' : 'created',
|
|
@@ -139,6 +205,9 @@ export async function initCommand(positional, flags) {
|
|
|
139
205
|
// pointing at another repository's identity file works here and breaks for the
|
|
140
206
|
// next person who clones without that sibling checkout.
|
|
141
207
|
include: await includesOwnIdentity(root),
|
|
208
|
+
// The .gitignore edit, planned like every other write: null when the entry is
|
|
209
|
+
// already there or no file is being created, 'create'/'update' otherwise.
|
|
210
|
+
gitignore: gitignoreAction,
|
|
142
211
|
};
|
|
143
212
|
|
|
144
213
|
const payload = {
|
|
@@ -182,8 +251,18 @@ export async function initCommand(positional, flags) {
|
|
|
182
251
|
bytes: tsconfigBase.bytes,
|
|
183
252
|
reason: tsconfigBase.reason,
|
|
184
253
|
},
|
|
254
|
+
// ADR-111. The agent files the apply path will write, one per harness that documents
|
|
255
|
+
// an agents directory. `text` is stripped from the emitted payload the way the other
|
|
256
|
+
// plans strip their rendered bytes; the apply path writes from `agentPlan`, which
|
|
257
|
+
// keeps it.
|
|
258
|
+
agents: agentPlan.map(({ text, ...rest }) => rest),
|
|
185
259
|
adoption,
|
|
186
260
|
manifest: { file: MANIFEST, action: manifestAction, path: manifestFile },
|
|
261
|
+
// Local state, not a repository file: gitignored, rebuildable from `git ls-files -s`,
|
|
262
|
+
// and not journaled. The plan names it because a file that appears after the run
|
|
263
|
+
// without being in the plan is the same silent write as any other. `build` means
|
|
264
|
+
// "built when --yes is applied"; the apply path replaces this with the result.
|
|
265
|
+
index: { file: SQLITE_FILE, action: 'build' },
|
|
187
266
|
region: region && {
|
|
188
267
|
file: region.file,
|
|
189
268
|
action: region.action,
|
|
@@ -245,6 +324,10 @@ export async function initCommand(positional, flags) {
|
|
|
245
324
|
);
|
|
246
325
|
}
|
|
247
326
|
|
|
327
|
+
// A dangling cross-reference does not stop the adoption: the region renders, and one
|
|
328
|
+
// unreachable reference is a defect to report, not a reason to withhold the rest.
|
|
329
|
+
for (const p of crossRefs) note(`PROBLEM ${p}`);
|
|
330
|
+
|
|
248
331
|
// Step 3: block on a real conflict before doing anything else. A duplicate is
|
|
249
332
|
// reported but never blocks - restating a ruling is a hygiene problem, while
|
|
250
333
|
// instructing an agent to do the forbidden thing is a correctness problem.
|
|
@@ -254,7 +337,7 @@ export async function initCommand(positional, flags) {
|
|
|
254
337
|
if (isJson(flags)) {
|
|
255
338
|
emitJson(payload);
|
|
256
339
|
} else {
|
|
257
|
-
emit(render(payload, manifest, { npm, version }));
|
|
340
|
+
emit(render(payload, manifest, { npm, manager, version }));
|
|
258
341
|
note('');
|
|
259
342
|
note(
|
|
260
343
|
`blocked ${summary.conflicts} conflict${summary.conflicts === 1 ? '' : 's'}; nothing written`,
|
|
@@ -275,7 +358,7 @@ export async function initCommand(positional, flags) {
|
|
|
275
358
|
payload.ok = false;
|
|
276
359
|
payload.blockedBy = 'malformed-region';
|
|
277
360
|
if (isJson(flags)) emitJson(payload);
|
|
278
|
-
else emit(render(payload, manifest, { npm, version }));
|
|
361
|
+
else emit(render(payload, manifest, { npm, manager, version }));
|
|
279
362
|
return fail(
|
|
280
363
|
`${malformed.length} target(s) have a malformed generated region (${malformed[0].file}: ${malformed[0].reason})`,
|
|
281
364
|
EXIT.FAIL,
|
|
@@ -304,11 +387,18 @@ export async function initCommand(positional, flags) {
|
|
|
304
387
|
after: renderIdentityFile(identity),
|
|
305
388
|
});
|
|
306
389
|
}
|
|
390
|
+
if (payload.identity.gitignore !== null) {
|
|
391
|
+
diffs.push({
|
|
392
|
+
path: '.gitignore',
|
|
393
|
+
before: gitignoreBefore,
|
|
394
|
+
after: gitignoreWithIdentity(gitignoreBefore ?? ''),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
307
397
|
if (payload.ci.action === 'create') {
|
|
308
398
|
diffs.push({
|
|
309
399
|
path: payload.ci.file,
|
|
310
400
|
before: await readBefore(join(root, payload.ci.file)),
|
|
311
|
-
after: renderWorkflow({ own: payload.ci.own, npm, version }),
|
|
401
|
+
after: renderWorkflow({ own: payload.ci.own, npm, manager, version }),
|
|
312
402
|
});
|
|
313
403
|
}
|
|
314
404
|
if (payload.dependabot.action === 'create') {
|
|
@@ -321,13 +411,6 @@ export async function initCommand(positional, flags) {
|
|
|
321
411
|
}),
|
|
322
412
|
});
|
|
323
413
|
}
|
|
324
|
-
if (payload.tsconfigBase.action === 'create') {
|
|
325
|
-
diffs.push({
|
|
326
|
-
path: payload.tsconfigBase.file,
|
|
327
|
-
before: await readBefore(join(root, payload.tsconfigBase.file)),
|
|
328
|
-
after: renderTsconfigBase(),
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
414
|
for (const target of projected.targets) {
|
|
332
415
|
if (target.action === 'unchanged' || target.action === 'malformed') continue;
|
|
333
416
|
diffs.push({
|
|
@@ -343,7 +426,7 @@ export async function initCommand(positional, flags) {
|
|
|
343
426
|
if (isJson(flags)) {
|
|
344
427
|
if (!apply) emitJson(payload);
|
|
345
428
|
} else {
|
|
346
|
-
emit(render(payload, manifest, { npm, version }));
|
|
429
|
+
emit(render(payload, manifest, { npm, manager, version }));
|
|
347
430
|
// After the report: the report says what, the diff shows it (R50).
|
|
348
431
|
emitDiffs(diffs, flags);
|
|
349
432
|
}
|
|
@@ -394,7 +477,6 @@ export async function initCommand(positional, flags) {
|
|
|
394
477
|
|
|
395
478
|
// Every repo-file write below is journaled, so `undo` can restore this run
|
|
396
479
|
// (R49). Pointer symlinks carry no bytes and are not journaled.
|
|
397
|
-
const journal = [];
|
|
398
480
|
await writeManifest(root, manifest, journal);
|
|
399
481
|
note(`wrote ${MANIFEST_FILE} (${manifestAction})`);
|
|
400
482
|
|
|
@@ -410,7 +492,14 @@ export async function initCommand(positional, flags) {
|
|
|
410
492
|
// a change.
|
|
411
493
|
if (payload.identity.action === 'created') {
|
|
412
494
|
await journaledWriteFile(root, journal, join(root, IDENTITY_FILE), renderIdentityFile(identity));
|
|
413
|
-
|
|
495
|
+
// Both .gitignore edits are journaled, and the journal line lands first: the two
|
|
496
|
+
// entries fold to one (first before, last after), so undo restores the pre-run
|
|
497
|
+
// bytes — or removes the file — in one step. If the journal line landed after,
|
|
498
|
+
// unrecorded, the file would no longer match the recorded after-bytes and undo
|
|
499
|
+
// would refuse (review 0003 Q30). The `ignoreJournal` inside `commitJournal`
|
|
500
|
+
// below is then a no-op.
|
|
501
|
+
await ignoreJournal(root, journal);
|
|
502
|
+
await ignoreIdentity(root, journal);
|
|
414
503
|
note(`wrote ${IDENTITY_FILE} (${identity.from === 'last-commit' ? 'from the last commit in this repository' : 'from the identity configured on this machine'}); gitignored, because it is yours and not the repository's`);
|
|
415
504
|
} else if (payload.identity.action === 'unchanged') {
|
|
416
505
|
note(`kept ${IDENTITY_FILE} (${payload.identity.reason})`);
|
|
@@ -418,7 +507,7 @@ export async function initCommand(positional, flags) {
|
|
|
418
507
|
note(`skipped ${IDENTITY_FILE} ${payload.identity.reason}`);
|
|
419
508
|
}
|
|
420
509
|
if (!payload.identity.include) {
|
|
421
|
-
await includeOwnIdentity(root);
|
|
510
|
+
await includeOwnIdentity(root, journal);
|
|
422
511
|
note(`set ${INCLUDE_KEY} = ../${IDENTITY_FILE} this repository's own identity file`);
|
|
423
512
|
}
|
|
424
513
|
|
|
@@ -433,7 +522,7 @@ export async function initCommand(positional, flags) {
|
|
|
433
522
|
// guaranteed to be misled.
|
|
434
523
|
if (payload.ci.action === 'create') {
|
|
435
524
|
await mkdir(dirname(join(root, payload.ci.file)), { recursive: true });
|
|
436
|
-
await journaledWriteFile(root, journal, join(root, payload.ci.file), renderWorkflow({ own: payload.ci.own, npm, version }));
|
|
525
|
+
await journaledWriteFile(root, journal, join(root, payload.ci.file), renderWorkflow({ own: payload.ci.own, npm, manager, version }));
|
|
437
526
|
note(`wrote ${payload.ci.file} (create; yours to edit from here, simt-dev will not rewrite it)`);
|
|
438
527
|
}
|
|
439
528
|
if (payload.dependabot.action === 'create') {
|
|
@@ -455,13 +544,6 @@ export async function initCommand(positional, flags) {
|
|
|
455
544
|
`wrote ${payload.dependabot.file} (create; watches ${payload.dependabot.ecosystems.join(', ') || 'github actions'} — yours to edit, simt-dev will not rewrite it)`,
|
|
456
545
|
);
|
|
457
546
|
}
|
|
458
|
-
if (payload.tsconfigBase.action === 'create') {
|
|
459
|
-
await journaledWriteFile(root, journal, join(root, payload.tsconfigBase.file), renderTsconfigBase());
|
|
460
|
-
note(
|
|
461
|
-
`wrote ${payload.tsconfigBase.file} (create; ${TSCONFIG_BASE_FILE} is JSON, so it cannot say this itself — add "extends": "./${TSCONFIG_BASE_FILE}" to your tsconfig.json or nothing reads it)`,
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
547
|
// Every planned target is written, not just the AGENTS.md region. An adoption that
|
|
466
548
|
// scaffolds the manifest and one region but leaves the `.mdc` rules for a later
|
|
467
549
|
// `sync` produces a repo whose `check` fails on files the user was never told
|
|
@@ -507,12 +589,34 @@ export async function initCommand(positional, flags) {
|
|
|
507
589
|
for (const p of pointerBlocked) {
|
|
508
590
|
note(`BLOCKED ${p.file}: ${p.detail} (${p.bytes} bytes); left as it is`);
|
|
509
591
|
}
|
|
592
|
+
// ADR-111. The agent files, written with everything else and journaled the same way,
|
|
593
|
+
// so `undo` restores them with the rest of the adoption. Written from `agentPlan`,
|
|
594
|
+
// which keeps the rendered bytes the display payload strips.
|
|
595
|
+
const agentWritten = await writeAgentPlan(root, journal, agentPlan);
|
|
596
|
+
for (const f of agentWritten) note(`wrote ${f} (agent)`);
|
|
510
597
|
await commitJournal(root, 'init', journal);
|
|
511
598
|
// The tree changed, so the last `check` no longer describes it: the line
|
|
512
599
|
// goes back to "no cache" rather than reporting the tree as it was (R48).
|
|
513
600
|
await invalidateCheckCache(root);
|
|
514
601
|
payload.written = true;
|
|
515
602
|
|
|
603
|
+
// The index is a cache of git blobs (ADR-089): gitignored, rebuildable, and not
|
|
604
|
+
// journaled. Building it here means the first `simt map` or `simt brief` in a fresh
|
|
605
|
+
// adoption answers from real files instead of walking the tree on the reader's first
|
|
606
|
+
// prompt. A failure is a note, not a failed adoption: the next read rebuilds what
|
|
607
|
+
// this one could not.
|
|
608
|
+
try {
|
|
609
|
+
const built = await reindex(root);
|
|
610
|
+
payload.index = { file: SQLITE_FILE, action: 'built', files: built.files, symbols: built.symbols, ms: built.ms };
|
|
611
|
+
note(
|
|
612
|
+
`index ${built.files} files, ${built.symbols} symbols in ${built.ms} ms; gitignored cache, ` +
|
|
613
|
+
`refreshed on read by ${CLI} map, brief and slice`,
|
|
614
|
+
);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
payload.index = { file: SQLITE_FILE, action: 'failed', reason: error.message };
|
|
617
|
+
note(`index not built (${error.message}); ${CLI} index rebuilds it`);
|
|
618
|
+
}
|
|
619
|
+
|
|
516
620
|
if (isJson(flags)) emitJson(payload);
|
|
517
621
|
|
|
518
622
|
note('');
|
|
@@ -617,7 +721,7 @@ async function git(cwd, args) {
|
|
|
617
721
|
}
|
|
618
722
|
|
|
619
723
|
/** Every line is a decision with a reason. Nothing is silent. */
|
|
620
|
-
function render(payload, manifest, { npm = true, version } = {}) {
|
|
724
|
+
function render(payload, manifest, { npm = true, manager = null, version } = {}) {
|
|
621
725
|
const lines = [
|
|
622
726
|
`repo ${payload.root}`,
|
|
623
727
|
`stack ${payload.stack.length > 0 ? payload.stack.join(', ') : '(undetected)'}`,
|
|
@@ -649,6 +753,18 @@ function render(payload, manifest, { npm = true, version } = {}) {
|
|
|
649
753
|
? id.reason
|
|
650
754
|
: `${id.name} <${id.email}> from ${id.from === 'last-commit' ? "this repository's last commit" : 'the identity configured on this machine'}`;
|
|
651
755
|
lines.push(` ${verb.padEnd(9)} ${id.file}${detail ? `\n ${detail}` : ''}`);
|
|
756
|
+
// The two side writes of the same action, each with its own line: a dry run that
|
|
757
|
+
// says `created git.config` and stops is a plan missing two of its three writes
|
|
758
|
+
// (review 0003 Q30). The include line mirrors the apply note byte for byte, so
|
|
759
|
+
// preview and run cannot tell the reader two different stories.
|
|
760
|
+
if (id.gitignore !== null) {
|
|
761
|
+
lines.push(
|
|
762
|
+
` ${id.gitignore.padEnd(9)} .gitignore ${IDENTITY_FILE} is per-developer, and committing it publishes one identity to everyone`,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
if (id.include === false) {
|
|
766
|
+
lines.push(` set ${INCLUDE_KEY} = ../${IDENTITY_FILE} this repository's own identity file`);
|
|
767
|
+
}
|
|
652
768
|
}
|
|
653
769
|
// Every gate the stack suggested and this repo cannot run is named, with the reason.
|
|
654
770
|
// A silently shorter gate list reads as "this repo needs no formatting check", when
|
|
@@ -687,7 +803,9 @@ function render(payload, manifest, { npm = true, version } = {}) {
|
|
|
687
803
|
? 'hand-written content outside the markers is never modified'
|
|
688
804
|
: target.kind === 'baseline'
|
|
689
805
|
? 'a region placed first, so your own settings below it still win (ADR-025)'
|
|
690
|
-
:
|
|
806
|
+
: target.kind === 'lock'
|
|
807
|
+
? 'the two digests a frozen check compares (ADR-086)'
|
|
808
|
+
: 'generated file: frontmatter kept, body replaced (ADR-016)';
|
|
691
809
|
lines.push(
|
|
692
810
|
` ${target.action.padEnd(9)} ${target.file} ${target.previousBytes} -> ${target.bytes} bytes ${caption}`,
|
|
693
811
|
);
|
|
@@ -705,17 +823,30 @@ function render(payload, manifest, { npm = true, version } = {}) {
|
|
|
705
823
|
lines.push(` remedy: move it aside, or delete it and rerun init`);
|
|
706
824
|
}
|
|
707
825
|
|
|
826
|
+
// ADR-111. The agent files, one per harness that documents an agents directory.
|
|
827
|
+
// Printed as their own kind: the file is owned whole by the tool, so the action is
|
|
828
|
+
// created, unchanged or updated and there is no byte delta to show.
|
|
829
|
+
for (const a of payload.agents ?? []) {
|
|
830
|
+
lines.push(` ${a.action.padEnd(9)} ${a.path} agent (ADR-111)`);
|
|
831
|
+
}
|
|
832
|
+
|
|
708
833
|
// The CI gate is listed as its own kind because it is the one plan entry whose
|
|
709
834
|
// absence is a decision rather than a default. Skipped states its reason inline so
|
|
710
835
|
// a reader can tell "this repo does not use GitHub Actions" from "this repo was
|
|
711
836
|
// not offered the gate".
|
|
712
837
|
if (payload.ci.action === 'create') {
|
|
713
|
-
lines.push(` ${'create'.padEnd(9)} ${payload.ci.file} ${renderWorkflow({ own: payload.ci.own, npm, version }).length} bytes`);
|
|
838
|
+
lines.push(` ${'create'.padEnd(9)} ${payload.ci.file} ${renderWorkflow({ own: payload.ci.own, npm, manager, version }).length} bytes`);
|
|
714
839
|
lines.push(
|
|
715
840
|
payload.ci.own
|
|
716
841
|
? ` this repository is simt-dev's own; ${CLI} ci --yes refreshes this copy when the gate changes`
|
|
717
842
|
: ` runs ${CLI} check where a pull request is merged; yours to edit after this`,
|
|
718
843
|
);
|
|
844
|
+
// Q31: the workflow installs simt-dev as a devDependency, so the plan states
|
|
845
|
+
// the install the reader must run — `<pm> add -D`, never a bare npm guess.
|
|
846
|
+
if (!payload.ci.own && manager) {
|
|
847
|
+
const addDev = { npm: `npm i -D ${PACKAGE_NAME}`, pnpm: `pnpm add -D ${PACKAGE_NAME}`, yarn: `yarn add -D ${PACKAGE_NAME}`, bun: `bun add -d ${PACKAGE_NAME}` }[manager];
|
|
848
|
+
if (addDev) lines.push(` ${'needs'.padEnd(9)} ${addDev} (CI installs the pinned devDependency before it can run ${CLI} check)`);
|
|
849
|
+
}
|
|
719
850
|
} else if (payload.ci.action === 'refresh') {
|
|
720
851
|
// init does not write it. `simt ci` owns the template's own copy, and a
|
|
721
852
|
// second writer would be a second answer to who owns it.
|
|
@@ -746,19 +877,21 @@ function render(payload, manifest, { npm = true, version } = {}) {
|
|
|
746
877
|
lines.push(` create ${WORKFLOWS_DIR}/ first if this repo is on GitHub Actions, then rerun init`);
|
|
747
878
|
}
|
|
748
879
|
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
880
|
+
// One state, and it is the point: init no longer creates the file (review 0003 Q28),
|
|
881
|
+
// so the plan line says what it will not write and why, instead of a floor appearing
|
|
882
|
+
// after the run without being in the plan. An existing base file is repo-owned and
|
|
883
|
+
// left exactly as it is, which needs no line because nothing changes.
|
|
752
884
|
const tb = payload.tsconfigBase;
|
|
753
|
-
if (tb && tb.action === '
|
|
754
|
-
lines.push(` ${'create'.padEnd(9)} ${tb.file} ${renderTsconfigBase().length} bytes`);
|
|
755
|
-
lines.push(
|
|
756
|
-
` the strictness flags these repositories already agree on; add "extends": "./${tb.file}" to your tsconfig.json to use it`,
|
|
757
|
-
);
|
|
758
|
-
} else if (tb && tb.action === 'skip') {
|
|
885
|
+
if (tb && tb.action === 'skip') {
|
|
759
886
|
lines.push(` ${'skip'.padEnd(9)} ${tb.file} ${tb.reason}`);
|
|
760
887
|
}
|
|
761
888
|
|
|
889
|
+
// The index is local state, not a repository file: gitignored and rebuildable, so the
|
|
890
|
+
// plan line describes what apply does rather than a file written into the tree.
|
|
891
|
+
lines.push(
|
|
892
|
+
` ${'build'.padEnd(9)} ${payload.index.file} gitignored cache of the git index; built on apply, refreshed on read by ${CLI} map, brief and slice (ADR-089)`,
|
|
893
|
+
);
|
|
894
|
+
|
|
762
895
|
const { findings, summary } = payload;
|
|
763
896
|
if (summary.conflicts > 0) {
|
|
764
897
|
lines.push('', `CONFLICTS ${summary.conflicts} - blocking without --accept-conflicts`);
|