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.
Files changed (187) hide show
  1. package/CHANGELOG.md +162 -841
  2. package/README.md +21 -135
  3. package/agents/simt-agent.md +84 -0
  4. package/bin/simt.mjs +19 -55
  5. package/docs/decisions/ADR-009-skill-layout.md +1 -1
  6. package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
  7. package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
  8. package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
  9. package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
  10. package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
  11. package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
  12. 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
  13. 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
  14. package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
  15. package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
  16. package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
  17. 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
  18. 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
  19. package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
  20. package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
  21. 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
  22. package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
  23. package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
  24. package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
  25. package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
  26. package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
  27. package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
  28. package/package.json +13 -3
  29. package/rules/core.md +9 -2
  30. package/rules/mdc/slice-commit.mdc +57 -4
  31. package/rules/mdc/test-gate.mdc +2 -0
  32. package/rules/stacks/android.md +3 -3
  33. package/rules/stacks/connectiq.md +2 -2
  34. package/rules/stacks/dart.md +1 -1
  35. package/rules/stacks/deno.md +1 -1
  36. package/rules/stacks/dotnet.md +1 -1
  37. package/rules/stacks/elixir.md +1 -1
  38. package/rules/stacks/go.md +1 -1
  39. package/rules/stacks/jvm.md +3 -2
  40. package/rules/stacks/node.md +2 -2
  41. package/rules/stacks/python.md +7 -6
  42. package/rules/stacks/ruby.md +1 -1
  43. package/rules/stacks/rust.md +1 -1
  44. package/rules/stacks/swift.md +1 -1
  45. package/rules/stacks/typescript.md +2 -2
  46. package/rules/stacks/uv.md +3 -2
  47. package/skills/simt-commands/SKILL.md +70 -0
  48. package/src/advice/advice.mjs +7 -3
  49. package/src/agenthooks/decide.mjs +141 -9
  50. package/src/agenthooks/opencode.mjs +72 -0
  51. package/src/agenthooks/prompt.mjs +10 -3
  52. package/src/agenthooks/render.mjs +186 -19
  53. package/src/agenthooks/shim.mjs +55 -0
  54. package/src/agents/render.mjs +277 -0
  55. package/src/api.mjs +29 -0
  56. package/src/argv.mjs +37 -3
  57. package/src/bench/ablate.mjs +31 -14
  58. package/src/bench/adapters.json +16 -0
  59. package/src/bench/corpus.mjs +150 -0
  60. package/src/bench/grade.mjs +23 -49
  61. package/src/bench/mine.mjs +247 -36
  62. package/src/bench/probes.mjs +104 -68
  63. package/src/bench/process.mjs +128 -0
  64. package/src/bench/report.mjs +260 -84
  65. package/src/bench/run.mjs +251 -150
  66. package/src/bench/variants.mjs +112 -40
  67. package/src/brief/assemble.mjs +44 -3
  68. package/src/ci/workflow.mjs +46 -14
  69. package/src/cli.mjs +45 -0
  70. package/src/commands/agent.mjs +205 -0
  71. package/src/commands/agenthook.mjs +113 -20
  72. package/src/commands/bench.mjs +192 -18
  73. package/src/commands/brief.mjs +5 -4
  74. package/src/commands/ci.mjs +2 -1
  75. package/src/commands/compare.mjs +50 -49
  76. package/src/commands/completions.mjs +2 -6
  77. package/src/commands/decide.mjs +10 -9
  78. package/src/commands/distill.mjs +4 -3
  79. package/src/commands/doctor.mjs +11 -7
  80. package/src/commands/evidence.mjs +19 -12
  81. package/src/commands/explain.mjs +0 -1
  82. package/src/commands/gate.mjs +98 -28
  83. package/src/commands/graph.mjs +20 -8
  84. package/src/commands/harness.mjs +3 -2
  85. package/src/commands/help.mjs +160 -31
  86. package/src/commands/hooks.mjs +163 -50
  87. package/src/commands/ignore.mjs +2 -1
  88. package/src/commands/impact.mjs +8 -9
  89. package/src/commands/index.mjs +278 -180
  90. package/src/commands/init.mjs +171 -38
  91. package/src/commands/lease.mjs +206 -39
  92. package/src/commands/lessons.mjs +72 -15
  93. package/src/commands/local.mjs +3 -3
  94. package/src/commands/map.mjs +47 -27
  95. package/src/commands/mcp.mjs +0 -1
  96. package/src/commands/pack.mjs +9 -6
  97. package/src/commands/plan.mjs +6 -5
  98. package/src/commands/plugin.mjs +3 -3
  99. package/src/commands/prbody.mjs +3 -3
  100. package/src/commands/repoindex.mjs +5 -3
  101. package/src/commands/review.mjs +2 -2
  102. package/src/commands/rules.mjs +124 -33
  103. package/src/commands/shared.mjs +2 -3
  104. package/src/commands/skills.mjs +82 -45
  105. package/src/commands/skillscompile.mjs +12 -12
  106. package/src/commands/skillsinstall.mjs +14 -2
  107. package/src/commands/slice.mjs +396 -26
  108. package/src/commands/spec.mjs +151 -0
  109. package/src/commands/status.mjs +4 -7
  110. package/src/commands/sync.mjs +50 -33
  111. package/src/commands/tests.mjs +11 -11
  112. package/src/commands/tokens.mjs +2 -2
  113. package/src/commands/undo.mjs +29 -31
  114. package/src/commands/upgrade.mjs +40 -4
  115. package/src/commands/why.mjs +19 -12
  116. package/src/compare/hooksstate.mjs +23 -1
  117. package/src/compare/learn.mjs +82 -57
  118. package/src/compare/parallel.mjs +50 -0
  119. package/src/compare/row.mjs +26 -3
  120. package/src/completions.mjs +5 -0
  121. package/src/context.mjs +64 -0
  122. package/src/decisions/dir.mjs +84 -0
  123. package/src/decisions/index-doc.mjs +46 -0
  124. package/src/decisions/index.mjs +109 -11
  125. package/src/diff.mjs +1 -1
  126. package/src/distill/distill.mjs +17 -2
  127. package/src/distill/engine.mjs +5 -1
  128. package/src/ecosystem/node-runner.mjs +144 -0
  129. package/src/explain.mjs +18 -6
  130. package/src/graph/load.mjs +42 -20
  131. package/src/hooks/capability.mjs +17 -4
  132. package/src/hooks/dispatcher.mjs +513 -36
  133. package/src/hooks/gates.mjs +0 -43
  134. package/src/hooks/resolve.mjs +11 -10
  135. package/src/hooks/shim.mjs +17 -9
  136. package/src/identity.mjs +96 -15
  137. package/src/index/extract.mjs +192 -11
  138. package/src/index/persist.mjs +307 -47
  139. package/src/index/store.mjs +263 -60
  140. package/src/index/tests.mjs +6 -35
  141. package/src/journal.mjs +372 -84
  142. package/src/kernel/card.mjs +37 -12
  143. package/src/kernel/checks.mjs +44 -9
  144. package/src/kernel/variants.mjs +27 -16
  145. package/src/lease.mjs +97 -15
  146. package/src/ledger/index.mjs +120 -25
  147. package/src/local/client.mjs +4 -6
  148. package/src/locate.mjs +33 -9
  149. package/src/manifest/schema.mjs +213 -0
  150. package/src/mcp/resources.mjs +12 -13
  151. package/src/mcp/server.mjs +151 -38
  152. package/src/mcp/tools.mjs +90 -16
  153. package/src/measure/session.mjs +3 -2
  154. package/src/memory/decide.mjs +98 -43
  155. package/src/memory/lessons.mjs +168 -33
  156. package/src/name.mjs +9 -0
  157. package/src/node-version.mjs +37 -0
  158. package/src/output/index.mjs +111 -3
  159. package/src/packs/manage.mjs +17 -3
  160. package/src/packs.mjs +20 -2
  161. package/src/plan/index.mjs +31 -8
  162. package/src/plan/template.mjs +27 -5
  163. package/src/plugins.mjs +6 -2
  164. package/src/prbody.mjs +7 -5
  165. package/src/projection/baselines.mjs +1 -1
  166. package/src/projection/disclosure.mjs +2 -2
  167. package/src/projection/loaders.mjs +41 -1
  168. package/src/projection/lock.mjs +31 -14
  169. package/src/projection/manifest.mjs +89 -53
  170. package/src/projection/markers.mjs +43 -3
  171. package/src/projection/mdc.mjs +34 -7
  172. package/src/projection/project.mjs +162 -18
  173. package/src/projection/rules.mjs +58 -6
  174. package/src/projection/surfaces.mjs +9 -1
  175. package/src/repo-root.mjs +88 -0
  176. package/src/resolve/body.mjs +4 -0
  177. package/src/resolve/fingerprint.mjs +24 -15
  178. package/src/resolve/local.mjs +58 -0
  179. package/src/review/critic.mjs +6 -3
  180. package/src/secrets.mjs +165 -22
  181. package/src/state.mjs +76 -0
  182. package/src/status.mjs +9 -4
  183. package/src/toolwords.mjs +8 -0
  184. package/src/tsconfig.mjs +39 -99
  185. package/src/workspace.mjs +55 -10
  186. package/templates/plan/tracker.json +1 -1
  187. package/docs/decisions/README.md +0 -591
package/src/tsconfig.mjs CHANGED
@@ -1,73 +1,36 @@
1
- // `tsconfig.base.json`: the strictness floor every TypeScript repo in the estate agrees on.
1
+ // `tsconfig.base.json`: the strictness floor a TypeScript repo writes for itself.
2
2
  //
3
- // This is ADR-026's mechanism — a config whose grammar has no comments is a whole file the
4
- // repo owns from the first commit — pointed at the file ADR-025 ruled out by name. Two
5
- // consequences shape everything here, and both are easy to get wrong:
3
+ // ADR-026's mechanism — a config whose grammar has no comments is a whole file the
4
+ // repo owns from the first commit — pointed at the file ADR-025 ruled out by name.
5
+ // `init` used to create it (review 0003 Q28 stopped that): a floor the tool writes
6
+ // into a fresh repo is a file nothing extends, because the repo's own `tsconfig.json`
7
+ // is the repo's to write — and a base that nothing points at is a floor with no
8
+ // effect, which `check` then reported as drift on every fresh adoption.
6
9
  //
7
- // 1. A base file is a FLOOR, not a policy. `extends` lets a child override any key, so a
8
- // base can only repair a repo that is *missing* a flag. It can never repair one that
9
- // spells a flag differently — the child wins and the base is inert for that key. Every
10
- // key below is therefore one the estate already agrees on, and the one place the estate
11
- // disagrees is a check instead (see `checkTsconfig`).
12
- // 2. The bytes cannot explain themselves. JSON has no comments, so this file ships with no
13
- // header saying who wrote it or what a reader must do next, which is normally the one
14
- // thing a shipped file owes its reader. `init` prints the `extends` line it requires
15
- // and `check` reports a repo that has not adopted it, so the obligation is met by the
16
- // tool's output rather than the file's.
10
+ // What is left here is the reporting side of ADR-026, and it reports exactly the two
11
+ // states a base file can be in that no file can fix:
17
12
  //
18
- // The whole file is owned by `init` alone. It is not in `src/ci/` and not in
19
- // `src/projection/baselines.mjs` because it is neither a `.github` file that `simt ci`
20
- // refreshes nor a marker region: `simt check` reports its absence and never its contents.
13
+ // 1. `inert` — the base exists and `tsconfig.json` does not extend it, so the floor
14
+ // is in the repository and not in effect. `extends` lets a child override any key,
15
+ // so a base can only repair a repo that is *missing* a flag; it can never repair
16
+ // one that does not point at it.
17
+ // 2. `inconsistent` — `moduleResolution` is spelled the way the parser accepts but
18
+ // the documentation does not. The parser is case-insensitive, which is the whole
19
+ // reason the estate drifted: both spellings work, so nothing ever said so.
20
+ //
21
+ // Both are non-blocking findings (review 0003 Q28): the base file is repo-owned, and
22
+ // a gate that fails a repository over a policy decision is a gate whose remedy is a
23
+ // choice, not a command. An existing base file stays exactly as the repo wrote it:
24
+ // this tool does not own its bytes, so a repo that edited them is not drift.
21
25
 
22
26
  import { readFile } from 'node:fs/promises';
23
27
  import { join } from 'node:path';
24
28
 
25
29
  import { fingerprint } from './resolve/fingerprint.mjs';
26
- import { CLI } from './name.mjs';
27
30
 
28
31
  export const TSCONFIG_BASE_FILE = 'tsconfig.base.json';
29
32
  export const TSCONFIG_FILE = 'tsconfig.json';
30
33
 
31
- /**
32
- * The strictness floor.
33
- *
34
- * Every key here is already set, and set the same way, by every `tsconfig.json` in the
35
- * estate that could afford it. One repository is the outlier and it is not one on purpose: it
36
- * is missing the seven flags below that the three social repos share, which is the gap
37
- * plan section 3.1 row 5 named. Nothing in this object is a value anybody has argued about;
38
- * it is the intersection of what four repositories independently wrote.
39
- *
40
- * Absent, and each for a reason that would be invisible to the next reader:
41
- *
42
- * - `noEmit`. node.md:36 wants it for "any project that bundles elsewhere", and it would
43
- * be harmless to inherit here because a child can set `false`. It is left out because a
44
- * base that hands `noEmit` to a repo nobody looked at is a build that stops emitting,
45
- * and the two repositories that bundle elsewhere did not ask for it. A floor nobody
46
- * opted into is not a floor.
47
- * - `moduleResolution`. All four repositories set it; three write `Bundler` and one writes
48
- * `bundler`. Shipping either would change nothing, because the child overrides the base
49
- * and the disagreement is precisely that the child sets it. `check` reports the spelling
50
- * instead.
51
- * - `target`, `lib`, `module`. Genuinely per-repo: `lib` is DOM+DOM.Iterable in two
52
- * repositories and DOM+WebWorker in the other two, and a base that chose would silence
53
- * one of them. These describe the platform, not the estate.
54
- * - `include`, `exclude`, `paths`, `types`, `jsx`. What a project is made of.
55
- */
56
- export const STRICTNESS_FLOOR = {
57
- strict: true,
58
- noUncheckedIndexedAccess: true,
59
- exactOptionalPropertyTypes: true,
60
- noImplicitOverride: true,
61
- useUnknownInCatchVariables: true,
62
- noFallthroughCasesInSwitch: true,
63
- noImplicitReturns: true,
64
- isolatedModules: true,
65
- verbatimModuleSyntax: true,
66
- allowJs: false,
67
- resolveJsonModule: true,
68
- skipLibCheck: true,
69
- };
70
-
71
34
  /**
72
35
  * `moduleResolution` as TypeScript documents it.
73
36
  *
@@ -87,19 +50,6 @@ export const DOCUMENTED_RESOLUTIONS = new Set([
87
50
  'bundler',
88
51
  ]);
89
52
 
90
- /**
91
- * The base file, as bytes.
92
- *
93
- * Key order is the order of `STRICTNESS_FLOOR` and not alphabetical, because the object
94
- * literal is the readable form and reordering it here would mean the file and the reason
95
- * disagree. `JSON.stringify` preserves insertion order, so the two cannot drift.
96
- *
97
- * @returns {string} JSON, LF-terminated
98
- */
99
- export function renderTsconfigBase() {
100
- return `${JSON.stringify({ compilerOptions: STRICTNESS_FLOOR }, null, 2)}\n`;
101
- }
102
-
103
53
  /**
104
54
  * Parse a file that TypeScript allows to carry comments.
105
55
  *
@@ -170,21 +120,19 @@ export function readJsonc(text) {
170
120
  }
171
121
 
172
122
  /**
173
- * What this repo's base file should be, and what should happen to it.
123
+ * What this repo's base file is, and what `init` will do about it.
174
124
  *
175
- * There is no `refresh`. ADR-026 gave simt-dev's own `.github` files a refresh path because
176
- * this repository is the template's only author; a base file has the same argument, and it
177
- * is deliberately not extended to it. `simt check` in this repository reports the
178
- * baseline files as unmanaged by design, so a refresh path here would be a way to make this
179
- * repository's own gate green by rewriting the file it is judging — which is the same
180
- * mistake as a gate that edits its own expectations.
125
+ * There is no `refresh`, and since review 0003 Q28 there is no `create` either: the
126
+ * file is the repo's to write, and a floor the tool created was one nothing extended.
127
+ * `init`'s whole contribution is now to say so in the plan — a file that appears after
128
+ * the run without being in the plan is a silent write, and "init will not write this"
129
+ * is a plan line, not an absence of one.
181
130
  *
182
- * The skip case is detection, not politeness: a repository with no TypeScript has nothing to
183
- * extend the base, and a `tsconfig.base.json` in a JavaScript repository is a file that
184
- * claims to be an authority and is read by nothing.
131
+ * `unchanged` means the file exists and is left exactly as it is: this tool does not
132
+ * own its bytes (ADR-026), so a repo that edited them is not drift.
185
133
  *
186
134
  * @param {string} root repo root
187
- * @returns {Promise<{file:string, path:string, action:'create'|'unchanged'|'skip',
135
+ * @returns {Promise<{file:string, path:string, action:'unchanged'|'skip',
188
136
  * exists:boolean, bytes:number, current:boolean, typescript:boolean,
189
137
  * reason:string|null}>}
190
138
  */
@@ -212,16 +160,19 @@ export async function planTsconfigBase(root) {
212
160
  reason: null,
213
161
  };
214
162
  }
163
+ // Absent is a skip in every case: init no longer creates the file (review 0003 Q28),
164
+ // so the plan states what it will not write and why, rather than leaving a floor
165
+ // behind that nothing extends.
215
166
  return {
216
167
  file: TSCONFIG_BASE_FILE,
217
168
  path,
218
- action: typescript ? 'create' : 'skip',
169
+ action: 'skip',
219
170
  exists,
220
171
  bytes: 0,
221
- current: false,
172
+ current: true,
222
173
  typescript,
223
174
  reason: typescript
224
- ? null
175
+ ? 'init no longer creates the base file; a floor is a policy the repo writes for itself'
225
176
  : 'no TypeScript in this repository, so nothing would extend the base',
226
177
  };
227
178
  }
@@ -249,23 +200,12 @@ export async function checkTsconfig(root) {
249
200
  baseExists = false;
250
201
  }
251
202
 
252
- const { stack } = await fingerprint(root);
253
- const typescript = stack.includes('typescript');
254
- if (typescript && !baseExists) {
255
- findings.push({
256
- file: TSCONFIG_BASE_FILE,
257
- status: 'absent',
258
- detail:
259
- 'this repo is TypeScript and has no base file, so every strictness flag is whatever ' +
260
- 'one person happened to write',
261
- remedy: `${CLI} init --yes`,
262
- });
263
- }
264
-
265
203
  // Nothing below reads the base file's contents, and that is deliberate. This tool does not
266
204
  // own those bytes (ADR-026), so a repo that edited them is not drift — and a finding that
267
205
  // compared them would report somebody's work as an error. Only the question of whether
268
- // `tsconfig.json` points at the base is this tool's to ask.
206
+ // `tsconfig.json` points at the base is this tool's to ask. A missing base file raises
207
+ // no finding at all (review 0003 Q28): writing one is the repo's decision, and a gate
208
+ // whose remedy is a policy choice is a gate nothing can satisfy.
269
209
  let own = null;
270
210
  try {
271
211
  own = readJsonc(await readFile(join(root, TSCONFIG_FILE), 'utf8'));
package/src/workspace.mjs CHANGED
@@ -38,7 +38,8 @@ export const PACKAGE_ROOT = resolve(HERE, '..');
38
38
  // other LOCKED on-disk names, and a second copy here is a name that can drift from the
39
39
  // list of names that are allowed to move. Imported as well as re-exported, because a
40
40
  // bare `export { X } from` does not bind X here and every use below would fail.
41
- import { CONFIG_DIR } from './name.mjs';
41
+ import { CONFIG_DIR, MANIFEST_FILE } from './name.mjs';
42
+ import { manifestPath, readManifest } from './projection/manifest.mjs';
42
43
  export { CONFIG_DIR };
43
44
  export const CONFIG_FILE = 'config.json';
44
45
 
@@ -133,14 +134,25 @@ async function looksLikeWorkspace(dir) {
133
134
  /**
134
135
  * Resolve the workspace.
135
136
  *
136
- * Precedence, highest first: `--workspace`, `SIMT_DEV_WORKSPACE`, the user config file, then the
137
- * sibling heuristic of this package - and that last one only if it actually contains a git
138
- * repository.
137
+ * Precedence, highest first (ADR-119): `--workspace`, the repository's own manifest
138
+ * declaration, `SIMT_DEV_WORKSPACE` - a value that names a directory is honoured only
139
+ * where the manifest declares a workspace, because the variable is a pointer INTO a
140
+ * workspace the repo opted into, not one imposed on a repo that never declared one;
141
+ * a value that declines is honoured from anywhere, because a decline points at
142
+ * nothing - the manifest's value, the user config file, then the sibling heuristic
143
+ * of this package, which is accepted only if it actually contains a git repository.
139
144
  *
145
+ * A relative manifest value resolves against the manifest's directory, not the
146
+ * process's, so `workspace: ".."` means the same thing from any subdirectory.
147
+ *
148
+ * @param {{repoRoot?: string}} [options] `repoRoot` names the repository whose manifest
149
+ * declares the workspace; it defaults to `process.cwd()`, which is the repository
150
+ * for the way every command is run.
140
151
  * @returns {Promise<{root: string|null, source: string, detail: string, searched: string[], error: string|null}>}
141
152
  */
142
153
  export async function resolveWorkspace(flags = {}, options = {}) {
143
154
  const env = options.env ?? process.env;
155
+ const repoRoot = options.repoRoot ?? process.cwd();
144
156
  const searched = configPaths(options);
145
157
 
146
158
  // The config file is read FIRST, before precedence is applied, and a parse failure
@@ -169,16 +181,49 @@ export async function resolveWorkspace(flags = {}, options = {}) {
169
181
  );
170
182
  }
171
183
 
184
+ // The repository's own declaration (ADR-119). It is read before the environment
185
+ // variable because it is what the variable is allowed to point into: a repo that
186
+ // declared no workspace is a single repo with no workspace, and an environment
187
+ // variable cannot turn it into a member of a workspace nobody reviewed.
188
+ const manifest = await readManifest(repoRoot);
189
+ if (manifest.error) {
190
+ return {
191
+ root: null,
192
+ source: 'repo',
193
+ detail: await manifestPath(repoRoot),
194
+ searched,
195
+ error: manifest.error,
196
+ };
197
+ }
198
+ let repoValue = null;
199
+ if (manifest.exists && typeof manifest.manifest.workspace === 'string' && manifest.manifest.workspace.trim() !== '') {
200
+ const off = disabled(manifest.manifest.workspace, 'repo', `${MANIFEST_FILE} workspace`, searched);
201
+ if (off) return off; // the repo declined; the environment variable cannot enforce one
202
+ repoValue = manifest.manifest.workspace;
203
+ }
204
+
172
205
  if (env.SIMT_DEV_WORKSPACE) {
173
- return (
174
- disabled(env.SIMT_DEV_WORKSPACE, 'env', 'SIMT_DEV_WORKSPACE', searched) ?? {
206
+ const off = disabled(env.SIMT_DEV_WORKSPACE, 'env', 'SIMT_DEV_WORKSPACE', searched);
207
+ if (off) return off; // a decline points at nothing, so it needs no declaration to work
208
+ if (repoValue !== null) {
209
+ return {
175
210
  root: absolute(env.SIMT_DEV_WORKSPACE),
176
211
  source: 'env',
177
212
  detail: 'SIMT_DEV_WORKSPACE',
178
213
  searched,
179
214
  error: null,
180
- }
181
- );
215
+ };
216
+ }
217
+ }
218
+
219
+ if (repoValue !== null) {
220
+ return {
221
+ root: absolute(repoValue, repoRoot),
222
+ source: 'repo',
223
+ detail: `${MANIFEST_FILE} workspace`,
224
+ searched,
225
+ error: null,
226
+ };
182
227
  }
183
228
 
184
229
  if (user.exists && typeof user.config.workspace === 'string' && user.config.workspace.length > 0) {
@@ -245,11 +290,11 @@ export const WORKSPACE_REMEDY = (() => {
245
290
  return `--workspace <path>, the SIMT_DEV_WORKSPACE environment variable, or ${where}`;
246
291
  })();
247
292
 
248
- function absolute(p) {
293
+ function absolute(p, base = process.cwd()) {
249
294
  const expanded = p.startsWith('~/')
250
295
  ? join(homedir(), p.slice(2))
251
296
  : p;
252
- return isAbsolute(expanded) ? expanded : resolve(expanded);
297
+ return isAbsolute(expanded) ? expanded : resolve(base, expanded);
253
298
  }
254
299
 
255
300
  /**
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "states": [
19
19
  { "id": "queued", "write": "Queued", "read": ["queued"] },
20
- { "id": "in-progress", "write": "In progress", "read": ["in progress", "in-progress"] },
20
+ { "id": "in-progress", "write": "In progress", "read": ["in progress", "in-progress"], "role": "started" },
21
21
  { "id": "complete", "write": "Complete", "read": ["complete", "completed", "done"], "satisfies": true },
22
22
  { "id": "accepted", "write": "Accepted", "read": ["accepted"] }
23
23
  ],