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 +38 -1
- package/README.md +30 -2
- package/bin/ark-mcp.mjs +14 -7
- package/bin/lib/ai-velocity.mjs +293 -0
- package/bin/lib/ci-and-commands.mjs +8 -6
- package/bin/lib/design-smells.mjs +122 -58
- package/bin/lib/doctor-plan.mjs +104 -7
- package/bin/lib/golden-pattern.mjs +184 -0
- package/bin/lib/pilot-loop.mjs +266 -0
- package/bin/lib/post-green-path.mjs +79 -0
- package/bin/lib/prepare-write.mjs +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/agent-guide.md +70 -3
- package/docs/ai-gates.md +7 -0
- package/docs/brownfield-adoption.md +22 -1
- package/docs/demos/03-copilot-autopilot.md +14 -7
- package/docs/package-surface.md +6 -2
- package/package.json +2 -1
- package/server.json +2 -2
- package/templates/skills/ark-autopilot.md +7 -0
- package/templates/skills/ark-explain.md +34 -3
- package/templates/skills/ark-explore.md +15 -5
- package/templates/skills/ark-place.md +4 -1
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
|
162
|
-
|
|
|
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\` |
|
|
170
|
-
| One design decision, 2–3 options | \`/ark-think\` |
|
|
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
|
-
**
|
|
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
|
|