arkgate 4.8.19 → 4.8.21
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 +220 -0
- package/README.md +107 -117
- package/bin/ark-check-runtime.mjs +26 -4
- package/bin/ark-layer-match.mjs +236 -12
- package/bin/ark.mjs +6 -2
- package/bin/lib/analysis-engine.mjs +13 -6
- package/bin/lib/config-contract.mjs +6 -0
- package/bin/lib/diagnostic-catalog.mjs +1 -0
- package/bin/lib/doctor-advisories.mjs +22 -1
- package/bin/lib/doctor-human.mjs +11 -9
- package/bin/lib/doctor-plan.mjs +17 -14
- package/bin/lib/enforcement-honesty.mjs +30 -2
- package/bin/lib/first-run-help.mjs +7 -1
- package/bin/lib/html-report-advisories.mjs +8 -0
- package/bin/lib/html-report-depth.mjs +12 -7
- package/bin/lib/invariant-coverage.mjs +406 -54
- package/bin/lib/managed-upgrade.mjs +59 -14
- package/bin/lib/package-manager.mjs +5 -2
- package/bin/lib/physical-cohesion.mjs +68 -51
- package/bin/lib/pilot-loop.mjs +183 -37
- package/bin/lib/reshape-decisions.mjs +2 -2
- package/bin/lib/rules-under-contract.mjs +40 -4
- package/bin/lib/start-install-recovery.mjs +156 -0
- package/bin/lib/start-preview.mjs +0 -54
- package/bin/lib/upgrade-behavior-changes.mjs +83 -0
- package/bin/lib/upgrade-command.mjs +69 -31
- package/bin/lib/upgrade-outcome.mjs +359 -0
- package/bin/lib/violations.mjs +109 -0
- package/dist/{configTypes-VD0qcubY.d.ts → configTypes-CTELo87-.d.ts} +9 -0
- package/dist/{diagnosticCatalog-D2v0ASN2.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +79 -5
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +35 -4
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +25 -18
- package/dist/index.d.ts +13 -4
- package/dist/index.js +27 -20
- package/dist/nestjs/index.cjs +9 -9
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +9 -9
- package/dist/runtime/index.cjs +18 -11
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +18 -11
- package/dist/{types-D5GT5ZT8.d.ts → types-fQ9-KrBj.d.ts} +1 -1
- package/dist/{types-BSzRy2X1.d.ts → types-yDNEQ603.d.ts} +1 -1
- package/docs/README.md +4 -3
- package/docs/agent-guide.md +15 -7
- package/docs/brownfield-adoption.md +4 -3
- package/docs/configuration.md +46 -8
- package/docs/develop.md +1 -1
- package/docs/diagnostics.md +12 -2
- package/docs/package-surface.md +4 -3
- package/docs/product-voice.md +11 -3
- package/docs/use.md +1 -1
- package/package.json +3 -3
- package/schemas/ark.config.schema.json +14 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +18 -1
- package/templates/agent-skills/ark-architect/SKILL.md +2 -0
- package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
- package/templates/agent-skills/ark-contract/SKILL.md +2 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +3 -1
- package/templates/agent-skills/ark-explain/SKILL.md +3 -1
- package/templates/agent-skills/ark-explore/SKILL.md +15 -1
- package/templates/agent-skills/ark-fix/SKILL.md +2 -0
- package/templates/agent-skills/ark-loop/SKILL.md +2 -0
- package/templates/agent-skills/ark-order/SKILL.md +2 -0
- package/templates/agent-skills/ark-place/SKILL.md +2 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
- package/templates/agent-skills/ark-think/SKILL.md +2 -0
- package/templates/agent-skills/ark-upgrade/SKILL.md +25 -10
- package/templates/skills/ark-adopt.md +18 -1
- package/templates/skills/ark-architect.md +2 -0
- package/templates/skills/ark-autopilot.md +6 -2
- package/templates/skills/ark-contract.md +2 -0
- package/templates/skills/ark-coverage.md +3 -1
- package/templates/skills/ark-explain.md +3 -1
- package/templates/skills/ark-explore.md +15 -1
- package/templates/skills/ark-fix.md +2 -0
- package/templates/skills/ark-loop.md +2 -0
- package/templates/skills/ark-order.md +2 -0
- package/templates/skills/ark-place.md +2 -0
- package/templates/skills/ark-runtime.md +2 -0
- package/templates/skills/ark-think.md +2 -0
- package/templates/skills/ark-upgrade.md +25 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as Policy, P as PolicyViolation, I as IntentName, j as IntentCreator, k as IntentRelationship, b as ArchitectureProfile, D as DomainEvent, E as EventMetadata, h as PolicyEnforcementMode, A as ArchitectureLayer, c as ArchitectureRule, d as ArkCheckConfig } from './types-
|
|
1
|
+
import { i as Policy, P as PolicyViolation, I as IntentName, j as IntentCreator, k as IntentRelationship, b as ArchitectureProfile, D as DomainEvent, E as EventMetadata, h as PolicyEnforcementMode, A as ArchitectureLayer, c as ArchitectureRule, d as ArkCheckConfig } from './types-fQ9-KrBj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* PolicyEngine
|
package/docs/README.md
CHANGED
|
@@ -73,12 +73,13 @@ These are **not** the day-to-day product path. They stay in the repo for evidenc
|
|
|
73
73
|
| Field adoption kit (scaffolding, not closed) | [field/](field/) |
|
|
74
74
|
| Runtime hardening (experimental) | [production-hardening.md](production-hardening.md) |
|
|
75
75
|
|
|
76
|
-
Current
|
|
76
|
+
Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.21`; confirm npm with `npm view arkgate@4.8.21 version`; does not close `K01`).
|
|
77
|
+
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.20`). Plain-English history: [arkgate.online/changelog](https://www.arkgate.online/changelog/).
|
|
77
78
|
Prior published: [releases/4.8.11.md](releases/4.8.11.md) (`arkgate@4.8.11`).
|
|
78
79
|
Prior published: [releases/4.8.10.md](releases/4.8.10.md) (`arkgate@4.8.10`).
|
|
79
80
|
Prior published: [releases/4.8.9.md](releases/4.8.9.md) (`arkgate@4.8.9`).
|
|
80
|
-
Prior: [releases/4.8.6.md](releases/4.8.6.md) · [releases/4.8.5.md](releases/4.8.5.md) · [releases/4.8.4.md](releases/4.8.4.md) · [releases/4.8.3.md](releases/4.8.3.md) · [releases/4.8.2.md](releases/4.8.2.md) · [releases/4.8.1.md](releases/4.8.1.md) · [4.8.0](releases/4.8.0.md) · [4.7.6](releases/4.7.6.md) · [4.7.5](releases/4.7.5.md) · [4.7.4](releases/4.7.4.md) · [4.7.3](releases/4.7.3.md) · [4.7.2](releases/4.7.2.md) · [4.7.1](releases/4.7.1.md) · [4.7.0](releases/4.7.0.md) · [4.6.7](releases/4.6.7.md) · [4.6.6](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md).
|
|
81
|
-
Older notes: [releases/](releases/). Config: [configuration.md](configuration.md).
|
|
81
|
+
Prior: [releases/4.8.8.md](releases/4.8.8.md) · [releases/4.8.7.md](releases/4.8.7.md) · [releases/4.8.6.md](releases/4.8.6.md) · [releases/4.8.5.md](releases/4.8.5.md) · [releases/4.8.4.md](releases/4.8.4.md) · [releases/4.8.3.md](releases/4.8.3.md) · [releases/4.8.2.md](releases/4.8.2.md) · [releases/4.8.1.md](releases/4.8.1.md) · [4.8.0](releases/4.8.0.md) · [4.7.6](releases/4.7.6.md) · [4.7.5](releases/4.7.5.md) · [4.7.4](releases/4.7.4.md) · [4.7.3](releases/4.7.3.md) · [4.7.2](releases/4.7.2.md) · [4.7.1](releases/4.7.1.md) · [4.7.0](releases/4.7.0.md) · [4.6.7](releases/4.6.7.md) · [4.6.6](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md).
|
|
82
|
+
Older notes: [releases/4.5.7.md](releases/4.5.7.md) · [releases/4.5.6.md](releases/4.5.6.md) · [releases/4.5.5.md](releases/4.5.5.md) · [releases/](releases/). Config: [configuration.md](configuration.md).
|
|
82
83
|
|
|
83
84
|
---
|
|
84
85
|
|
package/docs/agent-guide.md
CHANGED
|
@@ -161,6 +161,8 @@ npx arkgate upgrade --apply # registry-aware package step, then re-previ
|
|
|
161
161
|
npx arkgate upgrade --apply --no-install --plan-digest sha256:… --refresh-skills
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
`upgrade --apply` exits `0` when the managed files were applied and the architecture check is green, `3` when they were applied but the check is red, `2` when a recorded file still needs consent, and `1` on error (including a stale plan digest). Scripts comparing the status against `1` must switch to non-zero. JSON adds `outcome` and `postUpgrade` (`verdict`, `failing[{ruleId,count,sample}]`, `behaviorChanges`). `behaviorChanges` is the shipped per-version table, the same sentences as the changelog migration notes. A missing gitignored managed file is `absent-local` and is recreated without `--accept-conflicts`. Every blocked asset has `reason` and `nextCommand`. If `postUpgrade.verdict` is `red`, stop: report `failing` and `behaviorChanges`, and do not edit tests, titles, or config in that turn.
|
|
165
|
+
|
|
164
166
|
### Two-axis done recipe
|
|
165
167
|
|
|
166
168
|
Architecture residual and feature residual are **separate axes**. Never collapse them:
|
|
@@ -361,13 +363,16 @@ npx ark-check --doctor --fail-on-new-smells --base-ref origin/main --json # opt-
|
|
|
361
363
|
(`clarify-for-ai`) — **one** Shape door: `/ark-explore` shape-focus → dual-plan B, then
|
|
362
364
|
`/ark-autopilot` applies one pilot. Do not skill-shop coverage/think for the same residual.
|
|
363
365
|
|
|
364
|
-
**Pilot loop (Q04):**
|
|
365
|
-
|
|
366
|
+
**Pilot loop (Q04):** `pilotLoop` is active when a pilot candidate exists. Design-weak
|
|
367
|
+
pattern bets are one source. A proposed physical-cohesion reshape card is the other, even
|
|
368
|
+
when the tree is not design-weak. `pilotLoop.nextPilot` / `extractionCard` is **one**
|
|
369
|
+
extraction card (pilot target, move, success, kill-switch). Apply **that one pilot only**,
|
|
370
|
+
then re-doctor. Further candidates stay queued. Inactive reason is `no-pilot-candidates`.
|
|
366
371
|
Success = reduced smell evidence on pilot paths; residual outside the pilot may remain.
|
|
367
372
|
Never select seed/fixture/demo/migration/generated files as god-module pilots. A real UI business
|
|
368
373
|
rule moves Domain → Application → UI; local permission/UI-state helpers are not selected by their
|
|
369
374
|
`canEdit`-style name alone. Never multi-pilot batch; never mechanical-safe; never claim healthy
|
|
370
|
-
finished while
|
|
375
|
+
finished while `doctor.productHonesty.finished` is false.
|
|
371
376
|
|
|
372
377
|
**AI-velocity evidence (Q05):** deterministic fixture bench (no live LLM) compares the same
|
|
373
378
|
feature add on design-weak vs golden-path trees. Run `npm run eval:ai-velocity`; metric is
|
|
@@ -432,9 +437,11 @@ port. Advisory only — never blocks, never feeds `designFitness`; no strict mod
|
|
|
432
437
|
clusters per anchor directory (concentration, not volume — dispersed hooks never fire) with
|
|
433
438
|
fixed corpus-calibrated thresholds; anchors under `app/`/`pages/` are `fixedByConvention` and
|
|
434
439
|
never move. `reshapePilot.nextPilot` is a **proposed** one-at-a-time card (`moveSample`,
|
|
435
|
-
`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`)
|
|
436
|
-
|
|
437
|
-
|
|
440
|
+
`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`) and, when proposed, the single
|
|
441
|
+
`pilotLoop` extraction card if no design-weak pattern bet is ahead of it. Run it only via
|
|
442
|
+
`/ark-loop` through the write gate + atomic preflight; merges are `/ark-adopt` /
|
|
443
|
+
`/ark-autopilot` judgment cards. `notAScore`, never a verdict/`designFitness` input; there
|
|
444
|
+
is no apply path.
|
|
438
445
|
|
|
439
446
|
**Reshape decision memory (Y01):** when the team accepts, defers, or rejects that target, record
|
|
440
447
|
the explicit verdict in `.ark/reshape-decisions.json` using the card's exact
|
|
@@ -1135,7 +1142,8 @@ edges are not denied by that rule.
|
|
|
1135
1142
|
|
|
1136
1143
|
- **Denied:** `src/features/auth/**` → `src/features/payments/**` (different slice id).
|
|
1137
1144
|
- **Allowed:** same-slice imports when both paths classify; classic non-peerIsolation denies still apply across layers.
|
|
1138
|
-
- **`sliceFolders`:** optional parent segments (default: inferred from layer globs).
|
|
1145
|
+
- **`sliceFolders`:** optional parent segments (default: inferred from layer globs). A bare name stays an unanchored one-segment match (`features` on `src/features/auth` is `features/auth`). A starred anchored pattern with `sliceIdentity` `path` (the default) includes the literal prefix plus the star bindings (`lib/features/*/*` → `lib/features/projects/rfi`), so parallel trees get different ids.
|
|
1146
|
+
- **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus the star bindings (`lib/features/*/*` and `lib/repositories/features/*/*` both yield `features/projects/rfi`). Doctor names both prefixes when they collapse onto one id. Additive — no migration.
|
|
1139
1147
|
- **Fail-closed:** missing paths, empty/unresolvable slice folders, or unclassifiable either side → **deny** via peerIsolation (cannot prove same-slice).
|
|
1140
1148
|
- **`sharedRoots`** (4.8.4): roots the repo declares shared on purpose (`["ui", "hooks", "lib/permissions"]`). A file under a declared shared root is evidence, not an unclassifiable path, so fail-closed stops firing on every shared file. **Anchored** — the root starts the path, optionally after one `src/` or `app/`; write deeper or monorepo roots out (`packages/web/src/ui`) or glob them, and a bare `*` / `**` is refused. A path that still resolves to a slice keeps its slice.
|
|
1141
1149
|
- **`allowedCrossSlice`** (4.8.4): `[{ "from": "features/checkout", "to": "features/catalog" }]` — one directed slice→slice edge the repo declares on purpose. The reverse still denies.
|
|
@@ -228,12 +228,13 @@ ark-check --plan --json # patternBets[] with neverMechanicalSafe: true
|
|
|
228
228
|
|
|
229
229
|
### Pilot loop (Q04) — one pilot at a time → re-doctor
|
|
230
230
|
|
|
231
|
-
When
|
|
231
|
+
When a pilot candidate exists, doctor/plan JSON includes **`pilotLoop`**:
|
|
232
232
|
|
|
233
233
|
| Field | Meaning |
|
|
234
234
|
|-------|---------|
|
|
235
|
-
| `pilotLoop.active` | `true` when design-weak
|
|
236
|
-
| `pilotLoop.nextPilot` | **One** extraction card (same fields as §6)
|
|
235
|
+
| `pilotLoop.active` | `true` when a candidate exists: a design-weak pattern bet, or a proposed reshape card |
|
|
236
|
+
| `pilotLoop.nextPilot` / `extractionCard` | **One** extraction card (same fields as §6). Further candidates stay queued |
|
|
237
|
+
| `pilotLoop.reason` | `no-pilot-candidates` when the list is empty |
|
|
237
238
|
| `pilotLoop.oneAtATime` | Always true — do not multi-pilot batch |
|
|
238
239
|
| `pilotLoop.neverMechanicalSafe` | Always true — judgment only; re-doctor is the success sensor |
|
|
239
240
|
|
package/docs/configuration.md
CHANGED
|
@@ -114,7 +114,10 @@ Top-level fields:
|
|
|
114
114
|
Nothing is dropped in silence: files past the budget, files over the 256KB per-file cap,
|
|
115
115
|
unreadable files or directories (permissions, broken symlinks), directories past the walk depth
|
|
116
116
|
limit (8), symlinks whose target resolves outside the project root, and tests naming no
|
|
117
|
-
catalogued invariant are each counted and named in the diagnostic.
|
|
117
|
+
catalogued invariant are each counted and named in the diagnostic. Those same counts are printed
|
|
118
|
+
on a green doctor ArkRules line and on `--rules-inventory`, not only inside a failing
|
|
119
|
+
`INVARIANT_UNCOVERED` sentence. The walk-depth and per-file byte caps stay internal — they are
|
|
120
|
+
not config keys. A symlinked test is read only
|
|
118
121
|
when its target is inside the root: a file that is not in this repo never proves an invariant
|
|
119
122
|
covered.
|
|
120
123
|
|
|
@@ -247,18 +250,50 @@ process module-capability family must be denied.
|
|
|
247
250
|
|
|
248
251
|
Rule fields:
|
|
249
252
|
|
|
250
|
-
- `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `
|
|
251
|
-
`allowedCrossSlice`
|
|
253
|
+
- `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `sliceIdentity`,
|
|
254
|
+
`sharedRoots`, `allowedCrossSlice`, `sharedImportsSlice`
|
|
252
255
|
- `peerIsolation: true` + `allowed: false`: deny only when slice ids differ; same-slice allows
|
|
253
256
|
when both paths classify. Applies to **any** declared `from`→`to` pair, not only self-edges.
|
|
254
257
|
Missing paths, empty slice folders, or unclassifiable slices **fail closed** (deny — cannot
|
|
255
258
|
prove same-slice).
|
|
259
|
+
- `sliceFolders` is a bare name or a starred prefix. A bare name (`features`) stays an
|
|
260
|
+
unanchored one-segment match: `src/lib/features/projects/rfi/x.ts` with `["features"]` is
|
|
261
|
+
`features/projects`. The next segment is never a filename, so
|
|
262
|
+
`src/components/features/projects/rfi-status-pill.tsx` with `["projects"]` is not a slice.
|
|
263
|
+
A starred prefix (`lib/features/*/*`) is anchored like `sharedRoots` — it starts the
|
|
264
|
+
repo-relative path, or sits one segment in after `src/` or `app/`. Each `*` binds one
|
|
265
|
+
directory and never the filename. With `sliceIdentity` `path` (the default, also
|
|
266
|
+
when the key is absent) the slice id includes the literal prefix plus those
|
|
267
|
+
star bindings (`lib/features/projects/rfi`), so parallel trees get different ids.
|
|
268
|
+
A file directly under `lib/features/projects/` stays `lib/features/projects`. A path
|
|
269
|
+
off that prefix (`src/app/api/projects/...`) is not that slice.
|
|
270
|
+
- `sliceIdentity` is `path` or `stars`. Absent and `path` are the same: today's ids,
|
|
271
|
+
byte for byte, so baselines and `allowedCrossSlice` keep working with no migration.
|
|
272
|
+
`stars` names a starred prefix as the last literal plus the star bindings.
|
|
273
|
+
`lib/features/*/*` and `lib/repositories/features/*/*` both yield
|
|
274
|
+
`features/projects/rfi`, so one feature has one id across parallel trees. Bare names
|
|
275
|
+
(`features`) are unchanged. Doctor warns when two different prefixes bind as the
|
|
276
|
+
same id, and the warning names both paths (`admin/features/*` and `public/features/*`
|
|
277
|
+
both bind as `features/*`). That warning is advisory. Unrelated trees that share a
|
|
278
|
+
last folder name should stay on `path`.
|
|
279
|
+
|
|
280
|
+
```jsonc
|
|
281
|
+
{
|
|
282
|
+
"from": "ApplicationOrchestration",
|
|
283
|
+
"to": "PersistenceAdapters",
|
|
284
|
+
"allowed": false,
|
|
285
|
+
"peerIsolation": true,
|
|
286
|
+
"sliceFolders": ["lib/features/*/*", "lib/repositories/features/*/*"],
|
|
287
|
+
"sliceIdentity": "stars"
|
|
288
|
+
}
|
|
289
|
+
```
|
|
256
290
|
|
|
257
291
|
#### Cross-layer slice walls (already in the engine)
|
|
258
292
|
|
|
259
293
|
A slice wall on a cross-layer edge is a `peerIsolation` rule on that `from`/`to` pair
|
|
260
|
-
(`allowed: false`). There is no slice-wide engine mode
|
|
261
|
-
|
|
294
|
+
(`allowed: false`). There is no slice-wide engine mode and no new skill name.
|
|
295
|
+
`sliceIdentity` only changes how a starred prefix is named. `findDeniedEdgeDecision`
|
|
296
|
+
already applies `peerIsolation` to any declared pair
|
|
262
297
|
(same-layer or cross-layer) — locked on `EdgeRule` and `findDeniedEdgeDecision` in
|
|
263
298
|
[`src/domain/layerMatch.ts`](../src/domain/layerMatch.ts). There is no dedicated ADR to add.
|
|
264
299
|
|
|
@@ -306,8 +341,11 @@ is evidence:
|
|
|
306
341
|
tree you never declared. Write a deeper or monorepo root out (`packages/web/src/ui`) or glob it
|
|
307
342
|
(`packages/*/src/ui`). Matching is case-insensitive; a bare `*` or `**` is refused, because one
|
|
308
343
|
character must not disable fail-closed. A path that still resolves to a slice keeps its slice —
|
|
309
|
-
`features/auth/ui/form.tsx` stays `features/auth
|
|
310
|
-
|
|
344
|
+
`features/auth/ui/form.tsx` stays `features/auth`. Shared roots are a sink: a slice may import
|
|
345
|
+
a shared root, and a shared root may import a slice unless the rule sets `sharedImportsSlice`
|
|
346
|
+
to `"deny"`. That hop is direct only — `allowedCrossSlice` does not excuse it. A green
|
|
347
|
+
`ark-check` prints one line per layer edge (the count and three examples) and says the
|
|
348
|
+
wall is direct-only. Every edge stays in `ark-check --json` and `ark-check --doctor`.
|
|
311
349
|
- `allowedCrossSlice` entries match a full slice id (`features/catalog`) or a bare slice name
|
|
312
350
|
(`catalog`), and only in the direction written. The reverse edge still denies. A bare name
|
|
313
351
|
matches that name under **any** slice folder, so in a repo with several slice parents
|
|
@@ -398,7 +436,7 @@ Each `arkrules/<Layer>.json` may declare:
|
|
|
398
436
|
| Section | Purpose | Modes | What it really enforces |
|
|
399
437
|
|---------|---------|--------|-------------------------|
|
|
400
438
|
| `structure[]` | Closed sensor ids (e.g. `orchestration-only`, `thin-adapter`, `writes-via-aggregate`, `aggregate-private-state`, `always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model`) | `advisory` (default) or `enforced` | **Heuristics of module shape** — not proof that logic was extracted to Domain. `writes-via-aggregate` is driver-import + write-token in the declaring layer (ADR 0032). Tier-2 sensors (`no-anemic-model`) stay advisory-only (cannot promote to enforced). |
|
|
401
|
-
| `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence
|
|
439
|
+
| `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence.** `coverage.symbol` means a declaration of that identifier in a non-test file (witness path `symbolEvidenceFile`; additive `shape`: `function`, `class`, `const`, `method`, `type`, `interface`, or `enum`). Imports and test calls do not count. A `describe` / `it` / `test` / `context` title may also name the invariant id. A comment, string, or other bare mention does not. Does **not** execute tests or business logic and does **not** replace behavior/property tests. Doctor and `--rules-inventory` print the witness path and discard counts on green runs too. A declaration match is not “the tests pass.” |
|
|
402
440
|
|
|
403
441
|
**Reporting:** diagnostics carry `evidence.arkruleId` + `evidence.arkruleSource`. Label residual
|
|
404
442
|
**`[Layer]`** vs **`[ArkRules]`** in agent output. Doctor / HTML: `rulesUnderContract` (catalog +
|
package/docs/develop.md
CHANGED
|
@@ -39,7 +39,7 @@ npx arkgate start --apply
|
|
|
39
39
|
npx --package=arkgate arkgate-check --doctor
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. `arkgate-check` is a command in the `arkgate` package. After a local install, `npx arkgate-check --doctor` also works.
|
|
42
|
+
`start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. `arkgate-check` is a command in the `arkgate` package. After a local install, `npx arkgate-check --doctor` also works. If pnpm Age (`minimumReleaseAge`) blocks a brand-new release, keep using `npx --package=arkgate@<version>` — do not re-run the same add. Put arkgate on `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI flag is not enough), or wait until the package is mature.
|
|
43
43
|
|
|
44
44
|
Make the import-rules check a **required** merge **status context** (GitHub/GitLab/etc.). The CLI
|
|
45
45
|
command is `arkgate-check --strict-merge` / `ark-check --strict-merge` — the hard boundary is
|
package/docs/diagnostics.md
CHANGED
|
@@ -101,6 +101,7 @@ Link form for agents: `docs/diagnostics.md#RULE_ID` (exact-case HTML anchors bel
|
|
|
101
101
|
| [`CONFIG_AMBIGUOUS_LAYERS`](#CONFIG_AMBIGUOUS_LAYERS) | config | Ambiguous layer classification |
|
|
102
102
|
| [`CONFIG_UNCLASSIFIED_FILES`](#CONFIG_UNCLASSIFIED_FILES) | config | Unclassified included files |
|
|
103
103
|
| [`CONFIG_LAYER_MISSING_OWNER`](#CONFIG_LAYER_MISSING_OWNER) | config | Layer missing owner |
|
|
104
|
+
| [`CONFIG_SLICE_IDENTITY_COLLISION`](#CONFIG_SLICE_IDENTITY_COLLISION) | config | Starred slice prefixes share one id |
|
|
104
105
|
| [`ARK_UNKNOWN`](#ARK_UNKNOWN) | meta | Unknown diagnostic |
|
|
105
106
|
|
|
106
107
|
## Layer and dependency graph
|
|
@@ -308,8 +309,8 @@ Link form for agents: `docs/diagnostics.md#RULE_ID` (exact-case HTML anchors bel
|
|
|
308
309
|
|
|
309
310
|
**Invariant without coverage evidence**
|
|
310
311
|
|
|
311
|
-
- **Why:** An ArkRules invariant is under contract but no covering test title or declared symbol evidence was found (or coverage is partial). Kind is `never-had-tests` (adopt residual) vs `tests-disappeared` (suite exists).
|
|
312
|
-
- **Fix:**
|
|
312
|
+
- **Why:** An ArkRules invariant is under contract but no covering test title or declared symbol evidence was found (or coverage is partial). A title is a `describe` / `it` / `test` / `context` string. A comment, string, test body, or import that only mentions the id does not count. `coverage.symbol` is a non-test declaration: `function`, `class`, `const`, `method`, `type`, `interface`, or `enum`. Kind is `never-had-tests` (adopt residual) vs `tests-disappeared` (suite exists). The message names what the scan found.
|
|
313
|
+
- **Fix:** Put the invariant id in a `describe` / `it` title, or declare the `coverage.symbol` name in a non-test file, then preflight again. A comment is not a title — move the id into the `it()` title. Treat never-had-tests as adopt residual; treat tests-disappeared as a regression. Missing test globs report partial — never fake green. When the message reports an exhausted file budget, raise `coverage.maxFiles` (or narrow `coverage.testGlobs`) in ark.config.json. The message also names every file the scan discarded and why (budget, per-file byte cap, unreadable, walk depth limit, symlink resolving outside the project root, no catalogued invariant named) — nothing is dropped in silence.
|
|
313
314
|
|
|
314
315
|
<a id="INVARIANT_COVERAGE_OUTSIDE_ROOTS"></a>
|
|
315
316
|
|
|
@@ -894,6 +895,15 @@ never opting out of knowing.
|
|
|
894
895
|
- **Why:** `requireLayerOwners` is on and this layer has no owners. Writes to that house fail closed, like a folder with no name on the door.
|
|
895
896
|
- **Fix:** Add a GitHub handle or email to that layer’s owners in ark.config.json (`/ark-adopt`), then re-run. Reserved/allowEmpty houses may stay unnamed.
|
|
896
897
|
|
|
898
|
+
<a id="CONFIG_SLICE_IDENTITY_COLLISION"></a>
|
|
899
|
+
|
|
900
|
+
### `CONFIG_SLICE_IDENTITY_COLLISION`
|
|
901
|
+
|
|
902
|
+
**Starred slice prefixes share one id**
|
|
903
|
+
|
|
904
|
+
- **Why:** `sliceIdentity` is `stars` and two different `sliceFolders` prefixes bind as the same slice id. Parallel trees of one feature do that on purpose; unrelated trees that share the last folder name merge by accident. The warning names both paths.
|
|
905
|
+
- **Fix:** Keep `stars` when those prefixes are one feature. Otherwise use `path`, or rename the last literal so the prefixes do not bind as the same id. Advisory — it does not fail the check.
|
|
906
|
+
|
|
897
907
|
## Meta
|
|
898
908
|
|
|
899
909
|
<a id="ARK_UNKNOWN"></a>
|
package/docs/package-surface.md
CHANGED
|
@@ -48,18 +48,19 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
|
|
|
48
48
|
| **ArkRun doctor / status / report (RN08)** | `ark-check --doctor --json` → `doctor.arkRun`; HTML `data-advisory="arkRun"`; `ark status --json` / MCP `ark_status` → `arkRun`; `rulesUnderContract.mergePlanes.arkRun` | Additive. Always **`notAScore: true`**. Residual is a finding-id count (status residual `null` = unknown, not green). Advisory and absence never arm extra merge teeth; enforced teeth follow the classified-layer floor. Never a score or LLM verdict. |
|
|
49
49
|
| **Upgrade what’s new (4.5.6+)** | `ark upgrade --json` → `whatsNew` (+ human **Suggested improvements** block; also on preview). | Always **`notAScore: true`**, **`neverGateInput: true`**. Closed try/inspect list: deep-module coach, improvement compass, session/status honesty, two-axis done, self-service honesty, registry-aware upgrade, skill drift/refresh, multi-project MCP, Codex hard-write refresh/trust/restart/verify, stale MCP/global CLI recovery. Never invents residual or flips gates. |
|
|
50
50
|
| **Field upgrade truth (4.5.6)** | `ark upgrade` registry-aware install; JSON `reasonCode` / `suggestedInstallCmd`; `skillDrift`; `--refresh-skills`; `postUpgradeChecks`; `hostSelection`. | No false-skip when registry ahead; offline honesty; customized skills preserved unless opt-in refresh; checks are advisory only. |
|
|
51
|
+
| **Upgrade outcome (4.8.21)** | `ark upgrade --apply` JSON → additive `outcome`, `postUpgrade` (`verdict`, `failing[{ruleId,count,sample}]`, `behaviorChanges`), and on each blocked asset `reason` + `nextCommand`. State `absent-local` recreates a recorded gitignored file. | Exit `0` applied and green, `3` applied but red, `2` blocked, `1` error. Scripts comparing against `1` must switch to non-zero. `behaviorChanges` is the shipped per-version table (same sentences as the changelog). JSON fields are additive. `/ark-upgrade` stops when verification is red and does not edit tests, titles, or config in that turn. |
|
|
51
52
|
| **MCP process package honesty (4.5.6; fail-closed in 4.6.4)** | Every MCP tool context → `processPackage` (`processPackageMismatch` / `processStale`, versions, `nextAction`). | Multi-checkout users: restart MCP after pin bump. From 4.6.4, stale process context is non-authoritative; `ark_identity` remains diagnostic and project tools return `PROCESS_PACKAGE_STALE` until restart/retarget. Prefer project-local CLI until identity matched and versions align. |
|
|
52
53
|
| **Doctor design fitness** | `ark-check --doctor --json` → `doctor.designFitness`, `doctor.designSmells[]` | Additive. Stable smell `id`s: `io-under-application`, `handler-in-persistence`, `god-module`, `domain-logic-in-ui`, `facade-sql-in-routes`, `mixed-pattern-cluster`, `soft-contract`. `handler-in-persistence` covers static ES imports/re-exports of framework HTTP surfaces (`next/server`), `defineRoute` calls, and existing handler bodies inside Persistence-role layers or specific persistence paths; `require()` and dynamic `import()` are outside this narrow advisory, and a generic `Infrastructure` role alone is not Persistence. Persistence candidates are filtered and sorted before the bounded content scan so large application prefixes cannot hide the advisory. The detector inspects the first 800 sorted Persistence candidates; later candidates are uninspected, so **absence of a smell is not full-tree proof** above that envelope (incomplete/`partial` analysis also never proves “no smells”). **4.2 feedback hardening:** mode labels preserve the observed SUGGEST/ADAPT/ENFORCE state; a local permission/UI-state `canEdit` name alone is not a domain smell; real UI business rules route Domain → Application → UI; seed/fixture/demo/migration/generated files are not god-module pilots. Each smell has `evidence[]`, `fix`, technical `message`, and plain-language **`outcome`**. Does **not** fail the gate by itself. |
|
|
53
54
|
| **Post-green Shape door** | `doctor.postGreenPath`, `doctor.primaryNextAction`, `doctor.healthyFinishedForbidden` | Additive when `designFitness.designWeak`. Single Shape door (`id: clarify-for-ai`): explore shape-focus → dual-plan B → autopilot only with OK. Never empty plan A = healthy finished. A **passing** `ark-check` human run also prints one dim line naming `--plan`, the smell count and the smell ids. Its weakness test is `isDesignWeak` over the **blocking** violation count — the same input doctor uses, so the two surfaces cannot disagree about `designWeak`, and non-blocking type-only placement debt still gets the line. Report only: never a warning, never an exit-code change, silent when no smell remains, silent on `--changed` (a partial scan would report a slice as the tree) and on `--watch`. A baselined run is not called clean: the opening clause names the suppressed count instead. `--json` is unchanged. |
|
|
54
55
|
| **Golden pattern (new code)** | Optional `.ark/golden-pattern.json`; doctor JSON `doctor.goldenPattern`; MCP `ark_place` / `ark_prepare_write` → `goldenPattern` | Additive, **advisory for NEW code only**. Required fields: `name`, `norm`; optional `newCodeHome`, `examplePath`, `schemaVersion`. **Absent is normal** (no claim). Never ENFORCE; never clears design-weak. Malformed → `invalid: true`, not silent guidance. |
|
|
55
56
|
| **Plan pattern B (Shape bets)** | `ark-check --plan --json` → `plan.patternBets[]`, `plan.goal.designWeak` | Additive. Each bet: `id`, `smellId`, `pilot`, `evidence`, `successSignal`, `killSwitch`, **`neverMechanicalSafe: true`**, `class: "judgment"`. **Never** auto-applied by loop/autoPatch; not a `remediationKind` mechanical-safe. `goal.met` remains edge honesty only. |
|
|
56
|
-
| **Pilot loop (one at a time)** | `plan.pilotLoop` / `doctor.pilotLoop` | Additive.
|
|
57
|
+
| **Pilot loop (one at a time)** | `plan.pilotLoop` / `doctor.pilotLoop` | Additive. Active when at least one pilot candidate exists: a design-weak pattern bet, or a proposed physical-cohesion reshape card. Inactive reason is `no-pilot-candidates`. `oneAtATime`, `neverMechanicalSafe`, **one** `nextPilot` / `extractionCard` (same extraction-card fields: `pilotTarget`, `smellId`, `move`, `successSignal`, `killSwitch`, `doNot[]`). Further candidates stay queued. **One pilot → re-doctor**; never multi-pilot batch; never mechanical-safe. Gate verdict unchanged. |
|
|
57
58
|
| **AI-velocity eval (maintainer)** | Repository-only evidence: `npm run eval:ai-velocity` → `eval/ai-velocity-report.json` | Fixture-measured (no live LLM). Same feature scenario on design-weak vs golden-path arms; metric **`placementTurns`** (agent-equivalent). Golden must be strictly better. Method string lives next to the number. Does not weaken the gate. |
|
|
58
59
|
| **Contract health** | `ark-check --doctor --json` → `doctor.contractHealth`; optional `.ark/contract-smell-acks.json` | Additive, **advisory only** — meta-lint of the contract itself (layer-name heuristics; imprecision costs a warning line, never a verdict); never changes the verdict, `designFitness`, or `patternBets`. Stable smell ids: `contract-bidirectional-allow`, `contract-peripheral-depends-core`, `contract-lateral-adapter-allow`, `contract-dead-rule`; each smell has `severity`, `evidence[]` (sorted, honest `…(+N more)` truncation), `fix`, `message`, plain-language `outcome`, and `acknowledgedEdges` (acks applied to that id). **X03/X06**: the lateral smell does not fire on an adapter reaching its **own family's infra base** — the target reads `<Family><InfraWords…>` (**every** remaining target token an infra word: `Infra(structure)`/`Base`/`Core`/`Shared`/`Common`/`Kernel`/`Platform`/`Foundation`) and the source carries the family token **anywhere** in its name (X06, field: `HoursPersistenceAdapters -> PersistenceInfrastructure` — mid-name families). `PaymentsCoreAdapters` is still a sibling; cross-family edges, non-infra siblings, and the reverse (base → member) still fire. Acknowledgments live in the bounded sidecar (`{ acks: [{ id, edge, reason, reviewBy? }] }`, ≤64 KB / ≤200 entries; bidirectional edges order-insensitive, exact two segments); `contractHealth.acknowledged` counts **applied** acks only (stale acks count 0). **X02 ack lifecycle**: optional `reviewBy` (`YYYY-MM-DD`, strict round-trip validation — `2026-02-30` is malformed) — past the date the ack **stops applying** and the smell returns with `(ack expired …)` annotated evidence; among dated entries a fresh re-ack wins over a dead one, but once ANY dated ack exists for an edge the dated entries govern — a leftover undated duplicate cannot resurrect an expired exception. `detectContractSmells` defaults `today` to the real clock (pass `null` to disable expiry); `analyzeContractSmells` stays pure (clock injected). `contractHealth.ackLifecycle` reports `{ undated, malformed, expiredCount, expired[], staleCount, stale[] (lists capped at 12) }`; undated acks apply (backward compatible) but surface in doctor, report, and the fossilization note even when every smell is suppressed. **X05**: an ack matching **no detected edge** (orphaned by a fixed contract or quieted heuristic, unknown id, or typo'd edge) is `stale` — it suppresses nothing and doctor/report list the exact entries to fix or delete, even at zero visible smells. Malformed `reviewBy` never applies (fail-loud, like a sloppy edge); non-string `reviewBy` → whole file `invalid`. **Absent is normal**; malformed file or edge grammar → ignored + `ackFile.invalid` where applicable, never silent suppression. |
|
|
59
60
|
| **Effect capabilities** | Public root API: `analyzeProject(...).ir.capabilityUses`; the CLI/hook adapters add symbol-aware ambient evidence internally | Additive within IR `1.0`. Seven **closed** ids: `network`, `filesystem`, `clock`, `randomness`, `environment`, `process`, `persistence` (ADR 0009). `collectCapabilityUses` and the Domain vocabulary are internal implementation exports, **not** exports from `arkgate`; the related public low-level helper is `collectForbiddenCapabilityUses`. Direct evidence only — transitive inference never detects. The symbol-aware adapter path covers ambient globals (shadowing/type-only/globalThis-alias precision from the S05/C04 machinery) plus imports; the compiler-free IR engine carries **import-based** uses only (exact module or subpath match, never substring; textual `import type`/`export type` erasure and all-type named lists (`import { type A }`) are type-only there; mixed `{ type A, B }` stays a value import; template-literal bodies are skipped entirely (specifiers inside `${…}` are the symbol path's job); package `require(…)` counts as capability evidence only, while relative `require(…)` also emits a pure-path graph edge). **U04 walls are opt-in:** per-layer `capabilities: { deny: [...] }` or the dual-depth sugar `pure: true` (denies all seven); absence changes no verdict. `CAPABILITY_VIOLATION` is judgment-class (never mechanical-safe) with a port-injection `nextAction`; D7 dedup — evidence already owned by the layer's `forbiddenGlobals` reports only `FORBIDDEN_GLOBAL`. Y08 adds one deliberately narrow import dual: `forbiddenGlobals: ["process"]` owns exact value imports of `process` and `node:process`, but not subpaths or `child_process`; statement-level `import type` / `export type` remains erased on every path (pure-IR residual envelope: mixed `{ type A, B }`, default+named type lists, and comment-interrupted forms stay value imports; symbol path owns full precision). Atomic preflight blocks denied capabilities and that exact dual across a complete multi-file candidate (import-based on the pure path; other ambient evidence adds on the symbol-aware CLI/hook path). T01 policy-delta classifies the surface on **coverage atoms** (`ambient:<entry>` prefix-expanded, narrow `import-exact:<specifier>` duals, and `import:<capability>` for a complete wall): any lost atom is weakening (`fetch`→`XMLHttpRequest`, `Date`→`Date.now`, wall→fg all weaken; finding path `$.layers[name].capabilities`); fg → equivalent-or-stronger wall never needs an acknowledgment; unlowerable custom globals keep raw key comparison. |
|
|
60
61
|
| **Ambient state (pure layers)** | `ark-check --doctor --json` → `doctor.ambientState`; optional `.ark/ambient-state-acks.json` | Additive, **advisory only and opt-in**: only layers declared `pure: true` are scanned; the MVP shape is module-scope `let`/`var`. Findings carry `file`/`line`/`name`/`kind` (sorted, capped with honest `truncated` count). Acknowledgments live in the bounded sidecar (`{ acks: [{ file, name, reason }] }`, ≤64 KB / ≤200 entries); `acknowledged` counts applied acks; malformed file suppresses nothing. When TypeScript is unavailable the sensor reports `available: false` instead of guessing. **No strict mode exists** — A5: strictness requires a completed corpus and an explicit later decision. |
|
|
61
62
|
| **Parse health + analysis completeness** | `ark-check --doctor --json` → `doctor.parseHealth` + `doctor.completeness`; check JSON → `completeness`; report section `data-advisory="parseHealth"` | The resolved candidate facts contribute only `parseDiagnosticCount` per governed file (no raw diagnostics, second parser pass, or `tsc`). Z04's correctness path ignores legacy v9 caches and parses the complete candidate on every invocation; Z07 owns any future identity-keyed warm snapshot. Doctor remains diagnostic: parse health adds no architecture violation and does not change `designFitness` or `patternBets`. Verdict surfaces consume the evidence fail-closed: affected governed files mean `partial`, plan `goal.met: false`, normal JSON `valid:false`/`ok:false`, and strict merge exit `1`; the non-strict process exit remains advisory for compatibility. No usable host means `unavailable`, plan false, and CLI exit `2`. JSON reports `scannedFiles`, `affectedFiles`, `diagnosticCount`, deterministic top-12 `{ file, diagnosticCount }` entries, and honest `truncated`/`overflow`; missing/unsafe evidence never becomes a clean claim. |
|
|
62
|
-
| **Physical cohesion + reshape pilot** | `ark-check --doctor --json` → `doctor.physicalCohesion` (`reshapePilot`, `reshapeDecisions`); optional `.ark/reshape-decisions.json`; report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). **Y01 verdict memory:** bounded sidecar `{ schemaVersion?: "1", decisions: [{ concept, anchors, verdict: "accepted"|"deferred"|"rejected", reason, reviewBy? }] }` (≤64 KiB / ≤200 unique targets). Identity is concept + complete sorted anchor set, never counts/change-map evidence. Current rejected/deferred records suppress pilot pressure only; accepted keeps the existing path. Expired/malformed/stale/invalid records suppress nothing; lifecycle and decisions render in doctor/report. Explicit only — golden-pattern prose never infers a verdict. |
|
|
63
|
+
| **Physical cohesion + reshape pilot** | `ark-check --doctor --json` → `doctor.physicalCohesion` (`reshapePilot`, `reshapeDecisions`); optional `.ark/reshape-decisions.json`; report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; each `to` is `concept/` under the source anchor and is kept only when layer and slice id are unchanged — if every candidate fails, `nextPilot` is null; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). **Y01 verdict memory:** bounded sidecar `{ schemaVersion?: "1", decisions: [{ concept, anchors, verdict: "accepted"|"deferred"|"rejected", reason, reviewBy? }] }` (≤64 KiB / ≤200 unique targets). Identity is concept + complete sorted anchor set, never counts/change-map evidence. Current rejected/deferred records suppress pilot pressure only; accepted keeps the existing path. Expired/malformed/stale/invalid records suppress nothing; lifecycle and decisions render in doctor/report. Explicit only — golden-pattern prose never infers a verdict. |
|
|
63
64
|
| **Capability walls, every adapter** | CLI scan, pure IR engine, atomic preflight, `ark-mcp --hook` / MCP gate (`capabilityWalls`), ESLint `ark/no-denied-capabilities` | The same opt-in deny set enforces across every surface: hook/MCP and CLI cover ambient + import evidence (symbol-aware); the pure engine, preflight, and ESLint cover the import dimension (documented envelope). Dual depth everywhere: plain port hint (`FIX_HINTS`/`suggestion`) + stable JSON (`ruleId`, `capability`, `fixClass: inject-port`, deterministic `nextAction`). |
|
|
64
65
|
| **Hook-path budgets (maintainer)** | Repository-only evidence: `npm run bench:hook-path`; `eval/performance/hook-budgets.v1.json`; CI job "Hook-path end-to-end budgets" | Measures the COMPLETE pre-tool paths as fresh child processes (hook cold/warm, doctor cold) at 1k/10k. D5 method locked: ceilings are Linux-baseline p95 + fixed headroom, set once per cycle, never ratcheted; scenarios without a recorded baseline stay in RECORDING mode and cannot fail CI. |
|
|
65
66
|
| **Governance weight** | `ark-check --doctor --json` → `doctor.contractHealth.governanceWeight` | Additive, **advisory only** — raw facts (`declaredLayers`, `populatedLayers`, `governedFiles`, `rules`, `deniedEdges`, `allowedEdges`, `filesPerLayer`, `rulesPerLayer`) plus a fixed comparative band `weight: heavy | typical | light | unknown` and its fixed `note`. Fixed deterministic thresholds: **heavy** = fewer than 25 governed files per declared layer AND (6+ layers OR 4+ well-formed rules per layer); **light** = at most 2 layers over 150+ governed files; **unknown** = no layers or no governed files; everything else is **typical** (banding uses raw ratios; the reported ratios are rounded for display). `notAScore: true` is explicit: never a composite score, ranking, or gate input; the heavy note asks to justify NEW layers/rules and never suggests deleting working ones. Human doctor prints a line only for `heavy`/`light`. |
|
|
@@ -74,7 +75,7 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
|
|
|
74
75
|
| **`arkgate/schema/project-identity`** or **`arkgate/schema/ark.project-identity.schema.json`** | MCP canonical project, contract, runtime, expectation, and binding envelope | Schema `1.0`. Initial `expectedRoot` must be the exact project root. A contained descendant can match only when `expectedProjectId` is also present and correct; id-only matching stays non-authoritative. Mismatch codes are `PROJECT_ROOT_MISMATCH`, `PROJECT_ID_MISMATCH`, and `INVALID_PROJECT_EXPECTATION`. |
|
|
75
76
|
| **Package pin dual-truth (4.0)** | doctor JSON `packageVersionTruth`; upgrade JSON/human note when pin behind CLI | Additive, advisory. Surfaces after `upgrade --no-install` when managed CLI is ahead of package.json. |
|
|
76
77
|
| **Managed upgrade self-service honesty (4.5 / DF05)** | `ark upgrade [--json]` → `selfService` (+ human “Self-service honesty” lines) | Additive, advisory. Answers without a maintainer: write-path activation labels per selected host (`hard`\|`advisory`\|`unavailable`) and customized content-identity preserve (`customizedPaths` / `customizedContentPreserved`). Soft hosts never hard; upgrade never invents `hardWriteActive` from disk alone. Always `notAScore: true`. Not a gate input; not part of `planDigest`. |
|
|
77
|
-
| **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. |
|
|
78
|
+
| **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. A physical-cohesion residual that reshape decisions have not suppressed adds `physical-cohesion-residual`, which clears `elegant` and `finished` and sets the headline to residual remaining; the gate verdict, `designFitness`, `designWeak`, and `patternBets` stay unchanged. When that residual still proposes `reshapePilot.nextPilot`, `pilotLoop` is active with that one extraction card. |
|
|
78
79
|
| **Policy transition analysis (3.1.0)** | `analyzePolicyDelta(...)`; MCP `ark_policy_delta`; CLI `--policy-base` / `--policy-base-ref` / `--policy-ack`; check JSON `policyDelta` | Additive schema `1.0`. Classifications and finding ids are deterministic. Weakening/judgment requires an acknowledgement bound to both policy hashes and the exact blocking finding set. A new layer or new allow edge is `judgment-required`. On the CLI/MCP policy-ack plane, `adrPath` must name a short note under `docs/adr/` or `docs/decisions/` (`policyDelta.adrNote`). Domain hash match stays I/O-free. |
|
|
79
80
|
| **Team parliament (law vs feature)** | Optional `stewards` on `ark.config.json` (GitHub handle or email); CLI `--local` / `--changed` / `--against` / `--base` / `--contract-diff` / `--contract-session` / `--persona` / `--author`; check JSON `teamParliament`; `ark status --vs`; write-gate mixed-batch deny | Additive. Law files must not mix with product source. Loosen / baseline-grow are steward-only when `stewards` is set. `--against` ratchets vs the base-ref baseline. `--changed` scans touched sources plus their import closure. `--local` (or `ARK_CHECK_LOCAL=1`) is the same cheap path and stays per `--root`; it cannot combine with `--strict-merge`. Check JSON may add `local`, `scope: "changed"`, `analysisRoot`. `stewards`, `layers[].description`, `layers[].trustBoundary`, and `layers[].owners` are excluded from policy hash. `requireLayerOwners` stays in the hash. Identity is handle or email, not git `user.name`. No org plane. |
|
|
80
81
|
| **Atomic change preflight (3.1.0)** | `preflightChange(...)`; CLI `ark preflight --changes <file> --json`; MCP `ark_prepare_change` | Additive schema `1.0`. One complete governed production-source `{path,content}` / `{path,delete:true}` batch; read-only; returns operation, content/tree/policy/compiler fingerprints and stable graph findings. MCP availability alone is advisory. |
|
package/docs/product-voice.md
CHANGED
|
@@ -66,9 +66,9 @@ Skills never enforce. ξ / Haken stay below the fold.
|
|
|
66
66
|
|
|
67
67
|
## Locked first-contact
|
|
68
68
|
|
|
69
|
-
On the
|
|
70
|
-
`docs/
|
|
71
|
-
`docs/
|
|
69
|
+
On the seven lane openings — `docs/use.md`, `docs/develop.md`, `docs/README.md`,
|
|
70
|
+
`docs/enthusiast/README.md`, `docs/agent-guide.md`, `docs/ai-gates.md`, and
|
|
71
|
+
`docs/package-surface.md` — use:
|
|
72
72
|
|
|
73
73
|
1. **Verbs:** `Write. Check. Ship.`
|
|
74
74
|
2. **Deny:** `When the agent writes a bad import, the write doesn’t land. The same check fails the pull request.`
|
|
@@ -77,6 +77,14 @@ On the eight canonical public openings — `README.md`, `docs/use.md`,
|
|
|
77
77
|
5. **Caption (immediately after the four paragraphs):** `That is **Contener · Guiar · Ordenar**.`
|
|
78
78
|
6. **Technical nouns (below the introduction):** ArkGate is import rules. ArkRules is optional policies. ArkRun is an optional experimental runtime. ArkOrder is the extra for the few big product choices (billing plan, not seat counts) — named when the consumer opts in, never as the first noun. No ξ / Haken / slaving on the first screen.
|
|
79
79
|
|
|
80
|
+
`README.md` is the first-contact module. Its first screen is four beats: what
|
|
81
|
+
(**Contener · Guiar · Ordenar**), why (design the boundary before you implement;
|
|
82
|
+
a green check is **needs a refactor** when leftover mess remains), one command
|
|
83
|
+
(`npx arkgate@4.8.21 start`), and one honest red result (fail-open named; required
|
|
84
|
+
CI is the hard line). The four paragraphs, the not-that line, the host matrix,
|
|
85
|
+
and the release index sit below `## Appendix`. The not-that line does not lead
|
|
86
|
+
the README.
|
|
87
|
+
|
|
80
88
|
Do not lead with folders, `ark.config.json`, “contract”, “gate”, “house”, or “doctor”
|
|
81
89
|
as the first noun. Historical: `If the AI writes an illegal import, the write is rejected`
|
|
82
90
|
and `One architecture config. One check. One coach.` — never first.
|
package/docs/use.md
CHANGED
|
@@ -30,7 +30,7 @@ npx --package=arkgate arkgate-check --doctor # status — one next step
|
|
|
30
30
|
npx --package=arkgate arkgate-check --doctor --all # full details
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. After a local install, `npx arkgate-check --doctor` also works. `arkgate-check` is a command in the `arkgate` package, not its own npm package.
|
|
33
|
+
`start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. After a local install, `npx arkgate-check --doctor` also works. `arkgate-check` is a command in the `arkgate` package, not its own npm package. If pnpm Age (`minimumReleaseAge`) blocks a brand-new release, keep using `npx --package=arkgate@<version>` — do not re-run the same add. Put arkgate on `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI flag is not enough), or wait until the package is mature.
|
|
34
34
|
|
|
35
35
|
`start --apply` refuses when projected governed coverage is below 50% or
|
|
36
36
|
shape confidence is weak (below 0.6 with coverage under 80%), or when the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arkgate",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.21",
|
|
4
4
|
"description": "When the agent writes a bad import, the write doesn’t land. The same check fails the pull request.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -205,14 +205,14 @@
|
|
|
205
205
|
"@stryker-mutator/core": "^9.6.1",
|
|
206
206
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
207
207
|
"@types/node": "^20.14.0",
|
|
208
|
-
"@vitest/coverage-v8": "^
|
|
208
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
209
209
|
"esbuild": "0.28.1",
|
|
210
210
|
"fast-check": "^4.9.0",
|
|
211
211
|
"reflect-metadata": "^0.2.2",
|
|
212
212
|
"rxjs": "^7.8.2",
|
|
213
213
|
"tsup": "^8.1.0",
|
|
214
214
|
"typescript": "5.9.3",
|
|
215
|
-
"vitest": "^
|
|
215
|
+
"vitest": "^4.1.11"
|
|
216
216
|
},
|
|
217
217
|
"engines": {
|
|
218
218
|
"node": ">=18"
|
|
@@ -798,6 +798,14 @@
|
|
|
798
798
|
"uniqueItems": true,
|
|
799
799
|
"minItems": 1
|
|
800
800
|
},
|
|
801
|
+
"sliceIdentity": {
|
|
802
|
+
"type": "string",
|
|
803
|
+
"enum": [
|
|
804
|
+
"path",
|
|
805
|
+
"stars"
|
|
806
|
+
],
|
|
807
|
+
"description": "How a starred sliceFolders prefix is named. Absent and path keep today's ids (every literal and star binding). stars keeps the last literal plus the star bindings, so parallel trees share one feature id. Bare names are unchanged."
|
|
808
|
+
},
|
|
801
809
|
"sharedRoots": {
|
|
802
810
|
"type": "array",
|
|
803
811
|
"items": {
|
|
@@ -828,6 +836,12 @@
|
|
|
828
836
|
}
|
|
829
837
|
}
|
|
830
838
|
}
|
|
839
|
+
},
|
|
840
|
+
"sharedImportsSlice": {
|
|
841
|
+
"type": "string",
|
|
842
|
+
"enum": [
|
|
843
|
+
"deny"
|
|
844
|
+
]
|
|
831
845
|
}
|
|
832
846
|
}
|
|
833
847
|
},
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/pedroknigge/arkgate",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "4.8.
|
|
9
|
+
"version": "4.8.21",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "arkgate",
|
|
14
|
-
"version": "4.8.
|
|
14
|
+
"version": "4.8.21",
|
|
15
15
|
"runtimeHint": "npx",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
@@ -9,7 +9,7 @@ closed `/ark-*` catalog shipped as flat templates for Ark install.
|
|
|
9
9
|
**Closed catalog** (`ARK_SKILL_NAMES`) — first-class doors plus one-release
|
|
10
10
|
redirect stubs. Add a name only with a live ROADMAP item.
|
|
11
11
|
|
|
12
|
-
Package version when last generated context: **arkgate@4.8.
|
|
12
|
+
Package version when last generated context: **arkgate@4.8.21**
|
|
13
13
|
Schema: agent-skills package contract `1.0`
|
|
14
14
|
|
|
15
15
|
## Skills (closed catalog)
|
|
@@ -63,6 +63,11 @@ has honest houses. Skills never enforce — CLI / hooks / CI do.
|
|
|
63
63
|
`coverage.coverageRoots` to the folder the test runner uses. Fail-closed
|
|
64
64
|
until that path exists. `coverage.testGlobs` alone is not enough. Silent
|
|
65
65
|
when no invariant is enforced. No new skill.
|
|
66
|
+
- `coverage.symbol` means a declaration of that identifier in a non-test file.
|
|
67
|
+
Doctor and `--rules-inventory` print `symbolEvidenceFile` and the discard
|
|
68
|
+
counts even when uncovered is 0. Read that path. Imports and calls in tests
|
|
69
|
+
do not count. That declaration text is not “the tests pass” — ArkGate never
|
|
70
|
+
runs tests. No new config key.
|
|
66
71
|
- CLI-first: if `arkgate-check` already resolved the root, do not wait on MCP.
|
|
67
72
|
- Do not add `arkRun` or `arkOrder` unless the user wants that extra. When they do, write
|
|
68
73
|
**advisory** extra in this turn (`arkRun` schema `1.2+`; `arkOrder` schema `1.3+`).
|
|
@@ -353,8 +358,18 @@ Ark protects the **boundary around** a framework, not its internals. Nest/DI pub
|
|
|
353
358
|
The denial names its reason. `unclassifiable path` in bulk means shared code lives outside
|
|
354
359
|
the slice folders — declare those roots (`sharedRoots`) on the rule instead of treating
|
|
355
360
|
thousands of shared files as violations; `cross-slice edge a → b` is the real one, and a
|
|
356
|
-
deliberate directed edge goes in `allowedCrossSlice`.
|
|
361
|
+
deliberate directed edge goes in `allowedCrossSlice`. Shared roots are a sink: a slice may
|
|
362
|
+
import them, and they may import a slice. Doctor lists that shared-root → slice hop even
|
|
363
|
+
when the check stays green, so the wall is direct-only. `sharedImportsSlice: "deny"` closes
|
|
364
|
+
that one hop; it does not finish the wall. Promoting the shared slice to its own
|
|
357
365
|
layer is still the preferred fix.
|
|
366
|
+
`sliceFolders`: a bare name stays an unanchored one-segment match (`features` on
|
|
367
|
+
`src/lib/features/projects/rfi/x.ts` is `features/projects`); a filename is never the
|
|
368
|
+
child segment. A starred prefix (`lib/features/*/*`) is anchored like `sharedRoots`
|
|
369
|
+
(offset 0 or 1). The slice id includes the literal prefix plus the directories
|
|
370
|
+
the stars bind (`lib/features/projects/rfi`), so parallel trees get different ids
|
|
371
|
+
(tracked in #308). Before trusting green, add one deliberate
|
|
372
|
+
feature-to-feature import and expect `cross-slice`.
|
|
358
373
|
If one edge dominates residual debt: **STOP — do not continue this skill as complete.** **STOP — concentrated edge:** rewrite `ark.config.json` **in this turn** with source evidence (do not freeze a wrong config or grind N freezes).
|
|
359
374
|
Empty Domain/Persistence + I/O under Application → false-green.
|
|
360
375
|
**STOP — do not continue this skill as complete.** **STOP — false-green:** fix the config **in this turn** before claiming ENFORCE. Do not claim goal.met / ENFORCE from type-only cleanup while doctor reports `contract-false-green-io-under-application`.
|
|
@@ -423,5 +438,7 @@ End with **exactly** these headings (markdown `###`):
|
|
|
423
438
|
- **Handoff:** `/ark-…` / CLI / `none`
|
|
424
439
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
425
440
|
|
|
441
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
442
|
+
|
|
426
443
|
If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
|
|
427
444
|
**Skill incomplete if missing** any of the bullets above.
|
|
@@ -59,3 +59,5 @@ Skill incomplete if missing any field below.
|
|
|
59
59
|
- **Compass:** top residual lenses | `n/a`
|
|
60
60
|
- **Handoff:** `/ark-adopt` / `/ark-place` / `none`
|
|
61
61
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
62
|
+
|
|
63
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
@@ -110,8 +110,10 @@ decision-grade explore pass **and** without opening violating files.
|
|
|
110
110
|
layout — no pilot”; never reconstruct that dead card from `findings`. When the user accepts,
|
|
111
111
|
defers, or rejects a live card, persist its exact `decisionTarget` with a non-empty reason and
|
|
112
112
|
optional `reviewBy` in `.ark/reshape-decisions.json`; never infer a verdict from golden prose.
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
A cohesion residual starts **one** reshape judgment card on `pilotLoop.nextPilot`
|
|
114
|
+
(one pilot, then re-doctor). The loop is active when a pilot candidate exists,
|
|
115
|
+
including that reshape card when the tree is not design-weak. It does not start
|
|
116
|
+
a pattern-bet batch.
|
|
115
117
|
|
|
116
118
|
## MCP workspace binding (mandatory)
|
|
117
119
|
|
|
@@ -306,5 +308,7 @@ End with **exactly** these headings (markdown `###`):
|
|
|
306
308
|
- **Handoff:** `/ark-…` / CLI / `none`
|
|
307
309
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
308
310
|
|
|
311
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
312
|
+
|
|
309
313
|
If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
|
|
310
314
|
**Skill incomplete if missing** any of the bullets above.
|
|
@@ -64,3 +64,5 @@ Skill incomplete if missing any field below.
|
|
|
64
64
|
- **Compass:** top residual lenses | `n/a`
|
|
65
65
|
- **Handoff:** `/ark-adopt` / `/ark-autopilot` / `/ark-order` / `none`
|
|
66
66
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
67
|
+
|
|
68
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
@@ -96,7 +96,7 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
|
|
|
96
96
|
3. Absence of `arkRules` is **valid** — do not force ArkRules unless the user wants them or residual inventory clearly wants a pilot.
|
|
97
97
|
4. Editing `arkrules/*` or promoting modes is **`/ark-adopt`**; fixing code under a structure sensor is **`/ark-autopilot`** (judgment, never invent mechanical-safe).
|
|
98
98
|
5. CLI helpers: `ark-check --rules-inventory --json`, doctor JSON `rulesUnderContract`, sensors emit `ARKRULE_*` / `INVARIANT_UNCOVERED` / `INVARIANT_CATALOG_EMPTY` with `evidence.arkruleId`.
|
|
99
|
-
5b. Invariant coverage is a **text match, never an execution**: a file walk plus the invariant id in a `describe`/`it` title. Never report `INVARIANT_UNCOVERED: 0
|
|
99
|
+
5b. Invariant coverage is a **text match, never an execution**: a file walk plus the invariant id in a `describe`/`it` title. `coverage.symbol` means a declaration of that identifier in a non-test file (`symbolEvidenceFile`); imports and test calls do not count. Doctor and `--rules-inventory` print that witness path and the scan discard counts on a green run too. Never report `INVARIANT_UNCOVERED: 0`, a witness path, or a discard line as “the tests pass” or “the tests run” — ArkGate does not run tests and reads no runner config. Declaration text is not “the tests pass.” When the project declares `coverage.coverageRoots`, a covering test found outside them raises the advisory `INVARIANT_COVERAGE_OUTSIDE_ROOTS`; without that declaration ArkGate makes no claim about where tests run. Adopted + catalogued invariants without `coverage.testGlobs` or `coverage.coverageRoots` fails closed (`INVARIANT_TESTS_PATH_MISSING`) — write the path (`/ark-adopt`). When any invariant is enforced, missing `coverage.coverageRoots` fails closed (`INVARIANT_COVERAGE_ROOTS_MISSING`) — write the runner roots (`/ark-adopt`). `testGlobs` alone is not enough.
|
|
100
100
|
6. Extras silent when off. Doctor `arkRun` / `arkOrder` is `notAScore`. Do not force extras. Wire one kernel candidate via `/ark-runtime`; one order-plane candidate via `/ark-order`. Do not invent `/ark-run`.
|
|
101
101
|
|
|
102
102
|
|
|
@@ -200,5 +200,7 @@ End with **exactly** these headings (markdown `###`):
|
|
|
200
200
|
- **Handoff:** `/ark-…` / CLI / `none`
|
|
201
201
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
202
202
|
|
|
203
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
204
|
+
|
|
203
205
|
If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
|
|
204
206
|
**Skill incomplete if missing** any of the bullets above.
|
|
@@ -188,7 +188,7 @@ else is judgment/deferred and must not be auto-applied.
|
|
|
188
188
|
**Type-edge honesty:** type-only denied edges are **placement debt** (`typeOnly` +
|
|
189
189
|
`failsStrict: false`) — prefer SharedTypes / owning layer; they do not fail merge like value
|
|
190
190
|
edges. Doctor `productHonesty` and `violations.typeEdgePolicy` must not be sold as “all done”
|
|
191
|
-
when residual signals remain.
|
|
191
|
+
when residual signals remain (`physical-cohesion-residual` included).
|
|
192
192
|
|
|
193
193
|
## Spoken / written explanation
|
|
194
194
|
|
|
@@ -244,6 +244,8 @@ End with **exactly** these headings (markdown `###`):
|
|
|
244
244
|
- **Handoff:** `/ark-…` / CLI / `none`
|
|
245
245
|
- **Incomplete?** `no` | `yes — <what is missing>`
|
|
246
246
|
|
|
247
|
+
When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
|
|
248
|
+
|
|
247
249
|
Prefer **Incomplete?** `yes` if the showcase report was generated but the browser-open step was
|
|
248
250
|
skipped without CI/headless/user-opt-out reason.
|
|
249
251
|
|