arkgate 3.0.1 → 3.0.3

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 CHANGED
@@ -4,7 +4,44 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
4
4
 
5
5
  ## Unreleased
6
6
 
7
- No changes are scheduled after 3.0.1.
7
+ No changes are scheduled after 3.0.3.
8
+
9
+ ## 3.0.3 — 2026-07-13
10
+
11
+ ### Added
12
+
13
+ - **Post-green path (Q01):** when design residual remains under edge-clean ENFORCE, doctor
14
+ JSON exposes `postGreenPath` / `primaryNextAction` / `healthyFinishedForbidden` for a single
15
+ “clarify for AI / Shape” door (`clarify-for-ai`). Skill routing maps messy/design-weak work
16
+ to that path — no skill shopping.
17
+ - **Smell outcomes (Q02):** each stable `designSmells[]` id carries plain-language `outcome`
18
+ for newbies; technical `message` retained; doctor human prints outcome first.
19
+ - **Golden pattern (Q03):** optional `.ark/golden-pattern.json` (`name` + `norm`, optional
20
+ `newCodeHome` / `examplePath`). Surfaced on `ark_place`, `ark_prepare_write`, and
21
+ `doctor.goldenPattern` as **advisory for NEW code only**. Absent is OK; never ENFORCE;
22
+ never clears design-weak; malformed fails closed.
23
+ - **Pilot loop (Q04):** `plan.pilotLoop` / `doctor.pilotLoop` select **one** next extraction
24
+ card (`nextPilot`: pilot target, move, success, kill-switch). One pilot → re-doctor;
25
+ residual outside the pilot may remain; never multi-pilot batch; never mechanical-safe.
26
+ - **AI-velocity eval (Q05):** `npm run eval:ai-velocity` compares the same fixed feature
27
+ scenario on design-weak vs golden-path arms. Metric `placementTurns` (agent-equivalent);
28
+ golden must be strictly better. Fixture-measured (no live LLM); method string lives next
29
+ to the number in `eval/ai-velocity-report.json`.
30
+ - **Skills:** `/ark-place` honors golden; explore/autopilot document pilot loop; `/ark-explain`
31
+ opens `ark-report.html` in the default browser after generating the showcase report.
32
+
33
+ ### Documentation
34
+
35
+ - package-surface, agent-guide, brownfield pilot-loop section, eval README, Phase Q ROADMAP
36
+ evidence for Q01–Q05. Release note: `docs/releases/3.0.3.md`.
37
+
38
+ ## 3.0.2 — 2026-07-13
39
+
40
+ ### Documentation
41
+
42
+ - Sync consumer-facing agent docs with 3.0.1 design-depth skills: `designFitness` /
43
+ `patternBets`, extraction cards, dual-plan B honesty in agent-guide, AI gates, and the
44
+ autopilot demo. Release note status for 3.0.1 marked published.
8
45
 
9
46
  ## 3.0.1 — 2026-07-13
10
47
 
package/README.md CHANGED
@@ -38,6 +38,7 @@ If you remember nothing else:
38
38
  | Gate failed after an edit | `/ark-fix` |
39
39
  | “Where does this new file go?” | `/ark-place` |
40
40
  | Contract globs / layers wrong | `/ark-contract` |
41
+ | **Messy / spaghetti code** (even if the gate is green) | **`/ark-explore`** then **`/ark-autopilot`** — [below](#less-spaghetti-after-the-gate-is-green) |
41
42
  | New ArkGate version | `/ark-upgrade` |
42
43
 
43
44
  **Everything else is optional.** You do not need to learn “modes”, 11 skills, or the runtime
@@ -92,9 +93,36 @@ mode you configure:
92
93
  |-------|--------|-----------|
93
94
  | **Suggest** | New/thin project | Finish `start` + autopilot |
94
95
  | **Adapt** | Not fully protected yet | Keep autopilot / adopt until clean |
95
- | **Enforce** | Contract coverage is honest and checked edges are clean | Keep the host-appropriate write path; require the CI status if it must block merges |
96
+ | **Enforce** | Contract coverage is honest and checked **edges** are clean | Keep write path + CI. If the tree is still a mess [Shape flow](#less-spaghetti-after-the-gate-is-green) |
97
+ | **Enforce · design-weak** | Edges clean, but doctor still sees design smells (`designSmells` / `patternBets`) | **`/ark-explore`** (shape-focus) → **`/ark-autopilot`** for dual-plan **B** — not “done” |
96
98
 
97
- You **arrive** at Enforce. You never “turn on Enforce”.
99
+ You **arrive** at Enforce. You never “turn on Enforce”.
100
+ **Enforce does not mean the design is elegant** — only that the contract’s import edges are honest.
101
+
102
+ ---
103
+
104
+ ## Less spaghetti after the gate is green
105
+
106
+ A green check can still leave god modules, SQL in routes, and three patterns at once.
107
+ That residual is **Shape** work — plan **B**, never auto-applied as mechanical-safe.
108
+
109
+ ```text
110
+ 1. /ark-explore ← map + dual-plan B + extraction cards (no apply)
111
+ 2. /ark-autopilot ← apply A (edges); B only with your ok, one pilot at a time
112
+ 3. npx arkgate-check --doctor
113
+ npx arkgate-check --plan --json ← designWeak + patternBets when residual remains
114
+ ```
115
+
116
+ | If… | Skill |
117
+ |-----|--------|
118
+ | Need the map / “what’s the golden pattern?” | `/ark-explore` |
119
+ | Ready to execute the dual plan | `/ark-autopilot` |
120
+ | Contract still lies (false-green, wrong globs) | `/ark-adopt` first, then explore |
121
+ | One cluster only | `/ark-fix` (+ extraction card) |
122
+ | Only care about governed% / gates installed | `/ark-coverage` (not the Shape map) |
123
+
124
+ Phases: **Align** (honest contract) → **Stabilize** (real baseline) → **Shape** (golden pattern + pilot).
125
+ Details: [docs/brownfield-adoption.md](docs/brownfield-adoption.md) §6 · skills install: `npx arkgate-check --install-agent-gates`.
98
126
 
99
127
  ---
100
128
 
package/bin/ark-mcp.mjs CHANGED
@@ -60,6 +60,7 @@ import { validateWithAutoPatch, resolveImportFileAbs } from './lib/auto-patch.mj
60
60
  import { composePrepareWrite } from './lib/prepare-write.mjs';
61
61
  import { loadArkConfigContract } from './lib/config-contract.mjs';
62
62
  import { ARK_ANALYSIS_RESULT_SCHEMA, createAdapterResult } from './lib/adapter-contract.mjs';
63
+ import { loadGoldenPattern, attachGoldenToPlacement } from './lib/golden-pattern.mjs';
63
64
 
64
65
  const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
65
66
 
@@ -748,7 +749,9 @@ async function main() {
748
749
  name: 'ark_place',
749
750
  description:
750
751
  'Place a file in the architecture: pass filePath (preferred) and/or description. ' +
751
- 'Returns layer, mayImport / mustNotImport, forbiddenGlobals. Call BEFORE writing a new file. ' +
752
+ 'Returns layer, mayImport / mustNotImport, forbiddenGlobals, and optional goldenPattern ' +
753
+ '(advisory for NEW code when .ark/golden-pattern.json exists — never clears design-weak). ' +
754
+ 'Call BEFORE writing a new file. ' +
752
755
  'If only description is given, returns a conventional path proposal under a governed layer. ' +
753
756
  'Prefer ark_prepare_write when you already have the source snippet (place+validate+autoPatch in one call).',
754
757
  inputSchema: {
@@ -1007,7 +1010,11 @@ async function main() {
1007
1010
  // Deterministic placement guidance (in-process; no TS resolver needed): which layer a
1008
1011
  // path falls in, and — from the same rules ark-check enforces (default allow, explicit
1009
1012
  // `allowed:false` denies) — which layers it may and must not import.
1013
+ // Q03: when present, attach optional `.ark/golden-pattern.json` (advisory for NEW code only).
1010
1014
  function placeResult(filePath, description) {
1015
+ const golden = loadGoldenPattern(args.root);
1016
+ const withGolden = (placement) => attachGoldenToPlacement(placement, golden);
1017
+
1011
1018
  if ((typeof filePath !== 'string' || !filePath) && typeof description === 'string' && description.trim()) {
1012
1019
  const slug = description
1013
1020
  .trim()
@@ -1017,7 +1024,7 @@ async function main() {
1017
1024
  .slice(0, 48) || 'component';
1018
1025
  const proposedPath = `src/components/${slug}.tsx`;
1019
1026
  const layerName = inferLayer(proposedPath, config, args.root) || 'PresentationAdapters';
1020
- return {
1027
+ return withGolden({
1021
1028
  filePath: proposedPath,
1022
1029
  proposed: true,
1023
1030
  description: description.trim(),
@@ -1026,7 +1033,7 @@ async function main() {
1026
1033
  note:
1027
1034
  'filePath was omitted — proposed a conventional path from description. ' +
1028
1035
  'Pass filePath explicitly for authoritative placement.',
1029
- };
1036
+ });
1030
1037
  }
1031
1038
  if (typeof filePath !== 'string' || !filePath) {
1032
1039
  return {
@@ -1038,7 +1045,7 @@ async function main() {
1038
1045
  const layerName = inferLayer(filePath, config, args.root);
1039
1046
  if (!layerName) {
1040
1047
  const noLayers = configLayers.length === 0;
1041
- return {
1048
+ return withGolden({
1042
1049
  filePath,
1043
1050
  layer: null,
1044
1051
  governed: noLayers,
@@ -1051,7 +1058,7 @@ async function main() {
1051
1058
  'rules enforced). Place it under a directory a layer in ark.config.json ' +
1052
1059
  'matches, or add a layer. See suggestedLayers for conventional homes.',
1053
1060
  suggestedLayers: suggestedLayers(),
1054
- };
1061
+ });
1055
1062
  }
1056
1063
  const layerMeta = configLayers.find((layer) => layer.name === layerName);
1057
1064
  const rules = config.rules ?? DEFAULT_RULES;
@@ -1060,7 +1067,7 @@ async function main() {
1060
1067
  rules.some((rule) => !rule.allowed && rule.from === layerName && rule.to === to)
1061
1068
  );
1062
1069
  const mayImport = otherNames.filter((name) => !mustNotImport.includes(name));
1063
- return {
1070
+ return withGolden({
1064
1071
  filePath,
1065
1072
  layer: layerName,
1066
1073
  governed: true,
@@ -1072,7 +1079,7 @@ async function main() {
1072
1079
  note:
1073
1080
  'mayImport = layers with no explicit deny (default is allow). Respect ' +
1074
1081
  'forbiddenGlobals, then verify the actual snippet with validate_code or ark_prepare_write.',
1075
- };
1082
+ });
1076
1083
  }
1077
1084
 
1078
1085
  function runPlace(params) {
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Q05 — AI-velocity evidence (fixture-measured, no live LLM).
3
+ *
4
+ * Same fixed feature scenario on two arms:
5
+ * - design-weak: no golden norm → confused multi-path placement attempts
6
+ * - golden-path: `.ark/golden-pattern.json` newCodeHome → first attempt correct home
7
+ *
8
+ * Metric: **placementTurns** (agent-equivalent steps until a DomainModel landing).
9
+ * Golden must be strictly better (fewer turns). Gate is never weakened.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { layerForFile } from '../ark-shared.mjs';
14
+ import { loadGoldenPattern, summarizeGoldenPattern } from './golden-pattern.mjs';
15
+ import {
16
+ detectDesignSmells,
17
+ buildPatternBetsFromSmells,
18
+ summarizeDesignFitness,
19
+ assertPatternBetsNeverMechanicalSafe,
20
+ } from './design-smells.mjs';
21
+ import { collectGovernedFiles } from './scan-files.mjs';
22
+
23
+ /** Fixed feature prompt + pure-domain snippet (same on both arms). */
24
+ export const FEATURE_SCENARIO = {
25
+ id: 'add-pure-domain-canRefund',
26
+ prompt:
27
+ 'Add a pure domain rule canRefund(status: string): boolean for order refund eligibility. No I/O, no ORM.',
28
+ fileName: 'canRefund.ts',
29
+ source:
30
+ "export function canRefund(status: string): boolean {\n return status === 'paid';\n}\n",
31
+ /** Correct layer for this pure rule under the design-weak-enforce contract. */
32
+ correctLayer: 'DomainModel',
33
+ correctPath: 'src/domain/canRefund.ts',
34
+ };
35
+
36
+ /**
37
+ * Agent-equivalent placement order when no golden guides new code (spaghetti confusion).
38
+ * Wrong homes first (presentation / mixed), then domain — measured as friction.
39
+ */
40
+ export const DESIGN_WEAK_PLACEMENT_CANDIDATES = [
41
+ 'src/features/orders/ui/canRefund.ts',
42
+ 'src/routes/canRefund.ts',
43
+ 'src/services/canRefund.ts',
44
+ FEATURE_SCENARIO.correctPath,
45
+ ];
46
+
47
+ /**
48
+ * @param {string} root
49
+ * @param {object} config
50
+ * @param {string} filePath relative
51
+ */
52
+ export function layerForPlacement(root, config, filePath) {
53
+ const layers = config?.layers || [];
54
+ if (!layers.length) return null;
55
+ return layerForFile(root, filePath, layers) || null;
56
+ }
57
+
58
+ /**
59
+ * A landing is successful when the path is under the correct layer for the scenario.
60
+ * Pure snippet has no imports — we do not invent gate denials; cost is placement friction.
61
+ *
62
+ * @param {string} root
63
+ * @param {object} config
64
+ * @param {string} filePath
65
+ */
66
+ export function isCorrectLanding(root, config, filePath) {
67
+ const layer = layerForPlacement(root, config, filePath);
68
+ return layer === FEATURE_SCENARIO.correctLayer;
69
+ }
70
+
71
+ /**
72
+ * Build ordered placement attempts for an arm.
73
+ * Golden with newCodeHome → single first attempt under that home.
74
+ * Absent golden → design-weak candidate ladder.
75
+ *
76
+ * Accepts loadGoldenPattern result or summarizeGoldenPattern summary.
77
+ * @param {object | null | undefined} golden
78
+ */
79
+ export function placementAttemptsForArm(golden) {
80
+ if (!golden || typeof golden !== 'object') {
81
+ return { guidedByGolden: false, attempts: [...DESIGN_WEAK_PLACEMENT_CANDIDATES] };
82
+ }
83
+
84
+ const present = golden.present === true;
85
+ const newCodeHome =
86
+ (typeof golden.newCodeHome === 'string' && golden.newCodeHome) ||
87
+ (typeof golden.golden?.newCodeHome === 'string' && golden.golden.newCodeHome) ||
88
+ null;
89
+
90
+ if (present && typeof newCodeHome === 'string' && newCodeHome.trim()) {
91
+ const home = newCodeHome.replace(/\\/g, '/').replace(/\/?$/, '/');
92
+ const rel = `${home}${FEATURE_SCENARIO.fileName}`.replace(/\/{2,}/g, '/');
93
+ return {
94
+ guidedByGolden: true,
95
+ attempts: [rel.startsWith('src/') || rel.startsWith('.') ? rel : rel.replace(/^\//, '')],
96
+ };
97
+ }
98
+
99
+ // Golden absent or no newCodeHome → full confused ladder (honest multi-layout friction).
100
+ return {
101
+ guidedByGolden: false,
102
+ attempts: [...DESIGN_WEAK_PLACEMENT_CANDIDATES],
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Measure placement turns for one arm (real layer resolution + golden load).
108
+ *
109
+ * @param {{
110
+ * root: string,
111
+ * config: object,
112
+ * armId: string,
113
+ * files?: string[],
114
+ * }} opts
115
+ */
116
+ export function measureArmVelocity(opts) {
117
+ const { root, config, armId } = opts;
118
+ const goldenLoad = loadGoldenPattern(root);
119
+ const goldenSummary = summarizeGoldenPattern(goldenLoad);
120
+ const { guidedByGolden, attempts } = placementAttemptsForArm(goldenLoad);
121
+
122
+ const steps = [];
123
+ let placementTurns = 0;
124
+ let landedPath = null;
125
+ let landedLayer = null;
126
+
127
+ for (const candidate of attempts) {
128
+ placementTurns += 1;
129
+ const layer = layerForPlacement(root, config, candidate);
130
+ const ok = isCorrectLanding(root, config, candidate);
131
+ steps.push({
132
+ turn: placementTurns,
133
+ path: candidate,
134
+ layer,
135
+ correctLanding: ok,
136
+ });
137
+ if (ok) {
138
+ landedPath = candidate;
139
+ landedLayer = layer;
140
+ break;
141
+ }
142
+ }
143
+
144
+ // Real design residual sensors (honesty — not used to invent ENFORCE).
145
+ const files = opts.files ?? collectGovernedFiles(root, config);
146
+ const smells = detectDesignSmells(root, config, files, {
147
+ layersWithoutRules: [],
148
+ emptyLayers: [],
149
+ layers: [],
150
+ });
151
+ const patternBets = buildPatternBetsFromSmells(smells);
152
+ const designFitness = summarizeDesignFitness(smells, {
153
+ activeViolations: 0,
154
+ governedPercent: 100,
155
+ totalFiles: files.length,
156
+ });
157
+ const betsHonesty = assertPatternBetsNeverMechanicalSafe(patternBets);
158
+
159
+ return {
160
+ armId,
161
+ scenarioId: FEATURE_SCENARIO.id,
162
+ prompt: FEATURE_SCENARIO.prompt,
163
+ guidedByGolden,
164
+ goldenPattern: goldenSummary,
165
+ placementTurns: landedPath ? placementTurns : attempts.length + 1,
166
+ landed: Boolean(landedPath),
167
+ landedPath,
168
+ landedLayer,
169
+ steps,
170
+ designFitness: {
171
+ designWeak: designFitness.designWeak,
172
+ smellCount: designFitness.smellCount,
173
+ ids: designFitness.ids,
174
+ },
175
+ patternBetCount: patternBets.length,
176
+ patternBetsNeverMechanicalSafe: betsHonesty.ok,
177
+ metric: 'placementTurns',
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Compare two arms: golden must be strictly better on placementTurns.
183
+ *
184
+ * @param {ReturnType<typeof measureArmVelocity>} designWeakArm
185
+ * @param {ReturnType<typeof measureArmVelocity>} goldenArm
186
+ */
187
+ export function compareVelocityArms(designWeakArm, goldenArm) {
188
+ const dw = designWeakArm.placementTurns;
189
+ const gp = goldenArm.placementTurns;
190
+ const goldenBetter = gp < dw;
191
+ const delta = dw - gp;
192
+ return {
193
+ metric: 'placementTurns',
194
+ designWeakTurns: dw,
195
+ goldenPathTurns: gp,
196
+ goldenStrictlyBetter: goldenBetter,
197
+ deltaTurns: delta,
198
+ relativeReduction: dw > 0 ? delta / dw : null,
199
+ method:
200
+ 'Agent-equivalent placement attempts until DomainModel home for fixed pure-rule feature ' +
201
+ `(${FEATURE_SCENARIO.id}). Design-weak arm walks concurrent-layout candidates; ` +
202
+ 'golden-path arm uses .ark/golden-pattern.json newCodeHome as first attempt. ' +
203
+ 'No live LLM; gate not weakened; design-weak residual may remain on both arms.',
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Run full Q05 report object from two prepared roots (same config shape expected).
209
+ *
210
+ * @param {{
211
+ * designWeakRoot: string,
212
+ * goldenPathRoot: string,
213
+ * config: object,
214
+ * }} opts
215
+ */
216
+ export function runAiVelocityComparison(opts) {
217
+ const designWeakArm = measureArmVelocity({
218
+ root: opts.designWeakRoot,
219
+ config: opts.config,
220
+ armId: 'design-weak',
221
+ });
222
+ const goldenArm = measureArmVelocity({
223
+ root: opts.goldenPathRoot,
224
+ config: opts.config,
225
+ armId: 'golden-path',
226
+ });
227
+ const comparison = compareVelocityArms(designWeakArm, goldenArm);
228
+
229
+ return {
230
+ schemaVersion: '1',
231
+ id: 'q05-ai-velocity',
232
+ mode: 'fixture-measured',
233
+ scenario: {
234
+ id: FEATURE_SCENARIO.id,
235
+ prompt: FEATURE_SCENARIO.prompt,
236
+ fileName: FEATURE_SCENARIO.fileName,
237
+ correctLayer: FEATURE_SCENARIO.correctLayer,
238
+ correctPath: FEATURE_SCENARIO.correctPath,
239
+ sourceBytes: Buffer.byteLength(FEATURE_SCENARIO.source, 'utf8'),
240
+ },
241
+ arms: {
242
+ 'design-weak': designWeakArm,
243
+ 'golden-path': goldenArm,
244
+ },
245
+ comparison,
246
+ honesty: {
247
+ designWeakArmStillDesignWeak: designWeakArm.designFitness.designWeak === true,
248
+ goldenDoesNotRequireClearingDesignWeak: true,
249
+ patternBetsNeverMechanicalSafe:
250
+ designWeakArm.patternBetsNeverMechanicalSafe &&
251
+ goldenArm.patternBetsNeverMechanicalSafe,
252
+ gateNotWeakened: true,
253
+ liveLlmRequired: false,
254
+ },
255
+ ok: comparison.goldenStrictlyBetter === true,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Materialize golden-path arm: copy design-weak tree + write golden pattern.
261
+ * Pure helper for harness / tests (caller owns temp lifecycle).
262
+ *
263
+ * @param {string} designWeakRoot
264
+ * @param {string} destRoot
265
+ * @param {{ name?: string, norm?: string, newCodeHome?: string }} [golden]
266
+ */
267
+ export function materializeGoldenPathArm(designWeakRoot, destRoot, golden = {}) {
268
+ copyTree(designWeakRoot, destRoot);
269
+ const arkDir = path.join(destRoot, '.ark');
270
+ fs.mkdirSync(arkDir, { recursive: true });
271
+ const body = {
272
+ schemaVersion: '1',
273
+ name: golden.name || 'domain-first pure rules',
274
+ norm:
275
+ golden.norm ||
276
+ 'New pure business rules live under src/domain/; adapters and routes never own can*/policy helpers.',
277
+ newCodeHome: golden.newCodeHome || 'src/domain/',
278
+ examplePath: golden.examplePath || 'src/domain/canRefund.ts',
279
+ };
280
+ fs.writeFileSync(path.join(arkDir, 'golden-pattern.json'), JSON.stringify(body, null, 2) + '\n');
281
+ return body;
282
+ }
283
+
284
+ function copyTree(src, dst) {
285
+ fs.mkdirSync(dst, { recursive: true });
286
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
287
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
288
+ const s = path.join(src, entry.name);
289
+ const d = path.join(dst, entry.name);
290
+ if (entry.isDirectory()) copyTree(s, d);
291
+ else fs.copyFileSync(s, d);
292
+ }
293
+ }
@@ -158,21 +158,23 @@ Do **not** run overlapping skills for the same job. Pick **one** primary skill f
158
158
  | When | Invoke | Not this |
159
159
  |------|--------|----------|
160
160
  | Unsure / make architecture sound (apply path) | **/ark-autopilot** (default) | explore-only, coverage-only |
161
- | Map / residual / dual-plan **seed** / spaghetti Shape plan (no apply) | \`/ark-explore\` | coverage (fitness only), think (one decision) |
162
- | ENFORCE but design-weak / concurrent patterns / god modules | \`/ark-explore\` shape-focus or dual-plan seed → then autopilot for apply | loop (empty A is not done) |
161
+ | **Messy / spaghetti / design-weak after green / clarify for AI** | **Single path:** \`/ark-explore\` shape-focus dual-plan B, then \`/ark-autopilot\` only to apply B with OK | coverage, think, loop-as-done, skill-shopping |
162
+ | Map / residual / dual-plan seed only (no apply, already know you want recon) | \`/ark-explore\` | coverage (fitness only) |
163
163
  | Greenfield shape / empty tree | \`/ark-architect\` | adopt |
164
164
  | Brownfield / wrong contract / false-green | \`/ark-adopt\` then \`/ark-contract\` if globs wrong | architect |
165
165
  | Edit \`ark.config.json\` layers/rules/intents | \`/ark-contract\` | fix/loop for config |
166
166
  | New file “where does this go?” | \`/ark-place\` | architect (unless greenfield shape missing) |
167
167
  | Gate violation on a change (small cluster) | \`/ark-fix\` | loop/autopilot unless bulk |
168
- | Drive plan **A** to goal.met | \`/ark-loop\` | explore (unless A empty + design residual) |
169
- | Ark **fitness** only (governed%, gates, baseline, install gaps) | \`/ark-coverage\` | explore (full recon) |
170
- | One design decision, 2–3 options | \`/ark-think\` | explore (full dual-plan) |
168
+ | Drive plan **A** to goal.met | \`/ark-loop\` | explore (unless A empty + design residual → single Shape path above) |
169
+ | Ark **fitness** only (governed%, gates, baseline, install gaps) | \`/ark-coverage\` | Shape / design-weak (use single path above) |
170
+ | One design decision, 2–3 options | \`/ark-think\` | full Shape residual (use single path) |
171
171
  | Explain / HTML report tour | \`/ark-explain\` | explore |
172
172
  | Bump arkgate + refresh hosts | \`/ark-upgrade\` | — |
173
173
  | Optional runtime kernel evaluate | \`/ark-runtime\` | — |
174
174
 
175
- **Phases (brownfield honesty):** Align (contract truth) Stabilize (real baseline) Shape (golden pattern + pilot). Empty plan A after Stabilize still leaves Shape work that is explore/autopilot **B**, not “healthy finished.”
175
+ **Post-green door (Q01):** when doctor reports ENFORCE · design-weak, the **primary** next action is the single Shape path above not a choice among explore / coverage / think. Doctor JSON: \`postGreenPath\` / \`primaryNextAction\`.
176
+
177
+ **Phases (brownfield honesty):** Align (contract truth) → Stabilize (real baseline) → Shape (golden pattern + pilot). Empty plan A after Stabilize still leaves Shape work — that is the single post-green path, not “healthy finished.”
176
178
 
177
179
  ## Before editing TypeScript or JavaScript source files
178
180