@danmoisan/drm-copilot-mcp 1.0.26 → 1.1.0
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/out/mcp-server.js +542 -42
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/hooks/check-powershell-test-purity.ps1 +17 -21
- package/resources/claude-customizations/.claude/hooks/check-python-test-purity.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +17 -19
- package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +18 -19
- package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +54 -32
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +172 -24
- package/resources/claude-customizations/.claude/hooks/enforce-epic-wave-barrier.ps1 +51 -22
- package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +71 -28
- package/resources/claude-customizations/.claude/hooks/enforce-feature-folder-order.ps1 +68 -23
- package/resources/claude-customizations/.claude/hooks/enforce-mermaid-validation.ps1 +402 -0
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +20 -22
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +59 -14
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +17 -20
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier-helpers.ps1 +278 -0
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +55 -271
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +57 -22
- package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +56 -19
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +228 -0
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +68 -225
- package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +161 -34
- package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +59 -22
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +70 -27
- package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +32 -18
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +105 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +32 -52
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +107 -99
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +295 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/hook-payload/HookPayload.psm1 +494 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidGrammar.psm1 +491 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidLineScanner.psm1 +488 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidMarkdownFences.psm1 +298 -0
- package/resources/claude-customizations/.claude/lib/mermaid/MermaidValidation.psm1 +496 -0
- package/resources/claude-customizations/.claude/rules/mermaid.md +142 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +124 -1
- package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +116 -0
- package/resources/claude-customizations/.claude/settings.json +5 -0
- package/resources/claude-customizations/.claude/skills/atomic-plan-contract/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/claude-customizations/.claude/skills/feature-promotion-lifecycle/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/SKILL.md +184 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/c4.md +50 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/class.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/er.md +56 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/flowchart.md +68 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/gantt.md +51 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/other-types.md +82 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/pie.md +32 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/sequence.md +63 -0
- package/resources/claude-customizations/.claude/skills/mermaid-diagram/references/state.md +49 -0
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +8 -7
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +24 -4
- package/resources/claude-customizations/config/blast-radius.json +16 -2
- package/resources/claude-customizations/pack-manifests/core.json +22 -1
- package/resources/codex-and-agents-customizations/.agents/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
- package/resources/customizations/.github/skills/evidence-and-timestamp-conventions/SKILL.md +13 -0
- package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +31 -1
|
@@ -110,7 +110,7 @@ Enforced by `validate_parallel_manifest_text(text)` in `scripts/dev_tools/parall
|
|
|
110
110
|
|
|
111
111
|
The value must be authored as a YAML BLOCK sequence. The destination-runtime bash YAML subset parser (`.claude/lib/bash/parallel-yaml-scan.sh`) rejects a non-empty flow collection, so a flow-style value such as `members: [101, 102]` is outside the supported subset and is not accepted on the bash path.
|
|
112
112
|
|
|
113
|
-
`expected_conflict_components` is an ASSERTION, not a declaration. It NEVER overrides a derived conflict edge, NEVER feeds `compute_cohorts`, and NEVER influences scheduling. It is consumed by a planner diagnostic (`scripts/dev_tools/parallel_lane_assertion.py`), invoked advisory-only, whose findings never block. Its name deliberately references the DERIVED conflict graph: the field asserts what the operator expects blast-radius derivation to produce, and a mismatch is a signal to re-examine the radii, never a licence to edit the graph. The prohibition on narrowing a radius to suppress an edge is unaffected, as is the `depends_on` prohibition of invariant 10, P3, and M7 — this key is not a dependency edge and does not express ordering.
|
|
113
|
+
`expected_conflict_components` is an ASSERTION, not a declaration. It NEVER overrides a derived conflict edge, NEVER feeds `compute_cohorts`, and NEVER influences scheduling. It is consumed by a planner diagnostic (`scripts/dev_tools/parallel_lane_assertion.py`), invoked advisory-only, whose findings never block. Its name deliberately references the DERIVED conflict graph: the field asserts what the operator expects blast-radius derivation to produce, and a mismatch is a signal to re-examine the radii, never a licence to edit the graph. The prohibition on narrowing a radius beyond the configured exclusions to suppress an edge is unaffected, as is the `depends_on` prohibition of invariant 10, P3, and M7 — this key is not a dependency edge and does not express ordering.
|
|
114
114
|
|
|
115
115
|
Example, in the mandatory block-sequence form:
|
|
116
116
|
|
|
@@ -198,6 +198,128 @@ F3 deliberately excludes the kickoff-prompt contract module `scripts/dev_tools/p
|
|
|
198
198
|
|
|
199
199
|
F3's `require_ready_for_execution` gate is STRUCTURAL ONLY. It enforces the kickoff-PATH invariant (P9: `kickoff_prompt_path` must equal `artifacts/orchestration/parallel-kickoff-<parallel_slug>.md`) and does not parse or cross-check kickoff CONTENT. The deeper readiness-integrity machinery of the epic surface — git-integrity checks, launch-evidence binding, and kickoff-contract cross-checks — is left to F4, which may layer repository-aware checks behind an additional keyword without changing the schema. F3 likewise does not recompute the cohort coloring (planner invariant P5).
|
|
200
200
|
|
|
201
|
+
## Blast-Radius Contention Doctrine (issue #489)
|
|
202
|
+
|
|
203
|
+
The conflict graph that seeds cohorts is only as good as the evidence that produces its edges. Two
|
|
204
|
+
classes of derivation defect made thematically unrelated items contend, and the corrections below
|
|
205
|
+
are part of the landed contract. Enforcement remains prose plus validator logic; no JSON Schema is
|
|
206
|
+
authored, imported, or read for any of it.
|
|
207
|
+
|
|
208
|
+
### Read-by-mandate classification
|
|
209
|
+
|
|
210
|
+
Every agent in this repository is instructed to read the policy rules, the tier map, and the process
|
|
211
|
+
artifacts before doing any work. A plan that cites `.claude/rules/python.md` or `quality-tiers.yml`
|
|
212
|
+
is therefore reporting compliance with the reading order, not declaring that its diff will write
|
|
213
|
+
those files. Counting such a citation as contention made every well-formed plan collide with every
|
|
214
|
+
other well-formed plan.
|
|
215
|
+
|
|
216
|
+
`config/blast-radius.json` carries an optional `mandate_reads` list enumerating those paths as exact
|
|
217
|
+
entries and `**` subtree globs. That list is the mandate-read exclusion set. `derive_blast_radius` removes matching citations from the harvest
|
|
218
|
+
before resolving modules and shared surfaces, and `validate_blast_radius` removes them from its
|
|
219
|
+
plan-side extraction so V1 and V2 stay self-consistent against a radius derived from the same plan.
|
|
220
|
+
The key is optional and fail-closed: a truth table that omits it excludes nothing and reproduces
|
|
221
|
+
pre-change behaviour exactly.
|
|
222
|
+
|
|
223
|
+
Three constraints bound the mandate-read exclusion:
|
|
224
|
+
|
|
225
|
+
1. **The planner remains obliged to enumerate a genuine write explicitly.** An exclusion describes
|
|
226
|
+
the default reading relationship, not a permanent ban. When an item's plan will actually write an
|
|
227
|
+
excluded path, the planner appends that exact path to the declared radius after normalization.
|
|
228
|
+
2. **`quality-tiers.yml` stays a shared surface.** It is listed in both `shared_surfaces` and
|
|
229
|
+
`mandate_reads`: the first governs what happens when an item really writes it, the second governs
|
|
230
|
+
what happens when an item merely cites it.
|
|
231
|
+
3. **`detect_escaped_paths` makes the read/write distinction exact at execution time.** The
|
|
232
|
+
derivation heuristic reads intent from plan text and can be wrong in either direction; drift
|
|
233
|
+
detection compares the declared radius against the paths a diff actually touched, so an item that
|
|
234
|
+
wrote an excluded path is caught against observed evidence rather than against prose.
|
|
235
|
+
|
|
236
|
+
The extractor additionally rejects three token shapes that were never write claims: a wildcard-free
|
|
237
|
+
token whose final component names a directory rather than a file, a `docs/features/` glob whose
|
|
238
|
+
wildcard occupies or truncates the feature-folder segment, and a contract token carrying no ASCII
|
|
239
|
+
letter. `artifacts/` is not a known top-level segment, so a bare `artifacts/**` subtree claim no
|
|
240
|
+
longer satisfies the shape rules.
|
|
241
|
+
|
|
242
|
+
### Module-map granularity criterion
|
|
243
|
+
|
|
244
|
+
Issue #472 removed the location-bucket modules `docs` and `tests` because a bucket keyed on where a
|
|
245
|
+
file lives rather than on which subsystem owns it attaches to nearly every work item. The same
|
|
246
|
+
reasoning extends to umbrella buckets keyed on a top-level directory that essentially every item
|
|
247
|
+
writes into: an umbrella that matches almost every radius is not a coherent unit of contention,
|
|
248
|
+
because a level that always fires carries no information and only suppresses concurrency.
|
|
249
|
+
|
|
250
|
+
Under that criterion `python-dev-tools`, `vscode-extension`, `claude-runtime`, `copilot-surface`,
|
|
251
|
+
and `agents-surface` were removed, leaving the seven subsystem modules `mcp-server`, `benchmarks`,
|
|
252
|
+
`poshqc`, `powershell-dev-tools`, `codex-runtime`, `config`, and `schemas`. Removing a module never
|
|
253
|
+
weakens the relation below the path level: two items editing the same file still contend on
|
|
254
|
+
`path_overlap`, and two items editing a declared shared surface still contend on
|
|
255
|
+
`shared_surface_overlap`.
|
|
256
|
+
|
|
257
|
+
A candidate module belongs in the map when it names a subsystem an item could plausibly not touch.
|
|
258
|
+
A candidate that matches the majority of work items belongs nowhere.
|
|
259
|
+
|
|
260
|
+
### The published truth table is not a copy of this one (issue #500)
|
|
261
|
+
|
|
262
|
+
The push-down publishes a second truth table into a destination workspace at
|
|
263
|
+
`extensions/drm-copilot/resources/claude-customizations/config/blast-radius.json`. That copy stood
|
|
264
|
+
stale after issue #489 corrected only the self-hosted one, and correcting it fixed contention in
|
|
265
|
+
both directions at once. Three points fix the relation between the two copies so a later maintainer
|
|
266
|
+
does not re-synchronise them by hand.
|
|
267
|
+
|
|
268
|
+
**A destination's module map is DERIVED, so the bundled `modules` key is not consumed.**
|
|
269
|
+
`assembleModules` in `extensions/drm-copilot/src/lib/push-down/claude-blast-radius-derive-core.ts`
|
|
270
|
+
computes a destination's module map from the destination's OWN layout — the manifest-bearing
|
|
271
|
+
directories its scan observes — unioned with `PAYLOAD_MODULES`. It never reads the source document's
|
|
272
|
+
`modules` key. The bundled `modules` key is retained rather than deleted only so that a maintainer
|
|
273
|
+
reading the file is not told something false, and because
|
|
274
|
+
`tests/scripts/dev_tools/test_blast_radius_config.py` calls `load_module_globs` on it and that
|
|
275
|
+
helper raises on an absent key. Nothing schedules on it.
|
|
276
|
+
|
|
277
|
+
**`PAYLOAD_MODULES` carries `config` only.** `claude-runtime` was removed from it by the same
|
|
278
|
+
granularity criterion that removed it from this repository's own map. The criterion transfers
|
|
279
|
+
without modification: every agent in the runtime is instructed to read the policy rules and process
|
|
280
|
+
skills before doing any work, so a `.claude/**` umbrella matches nearly every radius in a
|
|
281
|
+
destination exactly as it did here. The no-signal floor is preserved because `config/**` in a
|
|
282
|
+
destination holds only the two published files, which makes `config` a subsystem an item can
|
|
283
|
+
plausibly not touch and keeps the assembled map non-empty so the forbidden-glob guard has a
|
|
284
|
+
non-vacuous input.
|
|
285
|
+
|
|
286
|
+
**The bundled `shared_surfaces` and `shared_surface_globs` sets are the destination-portable
|
|
287
|
+
subset, not a copy of the self-hosted sets.** They were authored narrow when the bundled copy was
|
|
288
|
+
created and were never a copy that fell behind, so the correct gate is portable-set equality against
|
|
289
|
+
a declared constant plus a subset relation against the self-hosted list — never byte-equality with
|
|
290
|
+
the self-hosted file. Only `version`, `over_breadth_fraction`, and `mandate_reads` are byte-equal
|
|
291
|
+
across the two copies.
|
|
292
|
+
|
|
293
|
+
The reason the two key groups take different relations is an asymmetry between surfaces and modules.
|
|
294
|
+
An over-matching MODULE glob costs concurrency on every pair of items it touches, because a module
|
|
295
|
+
that fires for both radii forces contention whether or not the items are related. A SURFACE or
|
|
296
|
+
mandate-read entry naming a path the destination lacks is inert: it matches nothing, so it costs
|
|
297
|
+
nothing. Erring wide is therefore free on the surface side and expensive on the module side, which
|
|
298
|
+
is why the portable surface set carries ecosystem-standard root filenames a given destination may
|
|
299
|
+
not have. A separator-free shared surface carries additional weight: it is the sole gate on whether
|
|
300
|
+
the path-token extractor accepts a separator-free token at all, so a published table with no
|
|
301
|
+
separator-free surface entry cannot detect two items rewriting the same root build file, whatever
|
|
302
|
+
that file is named.
|
|
303
|
+
|
|
304
|
+
**A directional invariant closes the residual Class 2 gap (issue #500 remediation).** Portable-set
|
|
305
|
+
equality against the declared portable-surface constant and the `bundled <= self_hosted` subset
|
|
306
|
+
relation together do not observe the self-hosted copy gaining a portable separator-free surface
|
|
307
|
+
that never reaches the bundle: both checks are satisfied by a bundled set that stays fixed while
|
|
308
|
+
the self-hosted set grows around it. `test_every_separator_free_self_hosted_shared_surface_reaches_the_bundle`
|
|
309
|
+
in `tests/scripts/dev_tools/test_blast_radius_config_parity.py`, mirrored in
|
|
310
|
+
`tests/scripts/claude-lib/blast-radius/BlastRadius.KeyPartition.Tests.ps1`, closes that gap
|
|
311
|
+
structurally by asserting the reverse containment for separator-free entries: every separator-free
|
|
312
|
+
self-hosted `shared_surfaces` entry must also appear in the bundled separator-free set.
|
|
313
|
+
|
|
314
|
+
**The key-partition gate now asserts exhaustiveness (issue #500 remediation, R8).** The three
|
|
315
|
+
declared classes each assert a property of the keys they name, but none of them asserted that
|
|
316
|
+
the two committed copies' top-level key sets are identical, or that every top-level key
|
|
317
|
+
belongs to one of the three declared classes. `test_every_top_level_key_is_classified_and_shared_by_both_copies`
|
|
318
|
+
in `tests/scripts/dev_tools/test_blast_radius_config_parity.py`, mirrored in
|
|
319
|
+
`tests/scripts/claude-lib/blast-radius/BlastRadius.KeyPartition.Tests.ps1`, closes that gap: the
|
|
320
|
+
union of both copies' top-level keys is exhaustively covered by the three declared classes, and
|
|
321
|
+
an unclassified key or a key present in only one copy fails loudly and names itself.
|
|
322
|
+
|
|
201
323
|
## Enforcement
|
|
202
324
|
|
|
203
325
|
- `scripts/dev_tools/validate_parallel_orchestrator_state.py`, with the helper modules `scripts/dev_tools/_parallel_state_common.py`, `scripts/dev_tools/_parallel_state_structures.py`, and `scripts/dev_tools/_parallel_state_records.py`, appends one error per violated orchestrator invariant. The completion-gate invariants 20 and 21 run only when the caller passes `require_complete=True`.
|
|
@@ -207,3 +329,4 @@ F3's `require_ready_for_execution` gate is STRUCTURAL ONLY. It enforces the kick
|
|
|
207
329
|
- The TypeScript parity port at `extensions/drm-copilot/src/lib/validate/parallel-state-shared.ts`, `parallel-state-structures.ts`, `parallel-state-records.ts`, `parallel-orchestrator-state-core.ts`, and `parallel-planner-state-core.ts` reproduces the same invariants and is dispatched from `extensions/drm-copilot/src/lib/validate/orchestration-artifacts.ts` for both new `artifact_type` values. Verified scope: 96 of 96 error strings matched across 43 constructed documents, for JSON-representable values that round-trip through both runtimes' native types. Three divergence classes are known outside that verified scope: (1) **`pythonRepr` quote selection** — `parallel-state-shared.ts:112-132` always single-quotes, while Python's `repr` switches to double quotes when the value contains a single quote (recorded repo-wide at `docs/features/potential/2026-08-07-python-repr-quote-selection-divergence.md`); (2) **integral floats** — `JSON.parse` erases Python's `int`/`float` distinction, so an integral float value produces a different Python-side error count than the TypeScript side; (3) **boolean/integer equality** — `parallel-state-structures.ts:228` uses `===`, so a boolean value is not selected the way Python's `True == 1` equality selects it, producing differing error counts.
|
|
208
330
|
- Enforcement is therefore Python validator logic, plus the TypeScript parity port, plus this prose file. It is NEVER an imported JSON Schema. No schema file is read at validation time.
|
|
209
331
|
- The `parallel` route entry lives in `config/orchestration-routing.json` with `requires_pr_gate: false` (there is no run-level pull request to gate; each child's own route checkpoint enforces its per-item pull-request gate) and is mirrored byte-for-byte in `extensions/drm-copilot/resources/config/orchestration-routing.json`.
|
|
332
|
+
- The `PreToolUse` merge gate `.claude/hooks/enforce-epic-merge-gate.ps1` carries a parallel allow-branch that authorizes a per-item `gh pr merge --merge` from the parallel-orchestrator checkpoint when `route_id == "parallel"`, the target item's `merge_status == "ci_green"`, and the command's PR number matches that item's `pr_number`; any other case fails closed with `EPIC_MERGE_GATE_BLOCKED`.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Atomic-Plan Acceptance Gates (G1 through G6)
|
|
2
|
+
|
|
3
|
+
This rule governs the acceptance-gate rules the plan validator applies to the shell commands an atomic plan states as acceptance conditions. It exists because a plan can state an acceptance condition that cannot fail: a coverage argument that collects no data, or a search for a literal that returns zero matches whatever the executor does. Such a condition reads as a verification step and gates nothing (issue #486).
|
|
4
|
+
|
|
5
|
+
The rules are enforced by `scripts/dev_tools/plan_gate_discrimination.py` and by the TypeScript parity port at `extensions/drm-copilot/src/lib/validate/plan-gate-discrimination.ts` with its shared-predicate module `plan-gate-rules.ts`, both fed by the command extractor (`scripts/dev_tools/plan_gate_commands.py` and `extensions/drm-copilot/src/lib/validate/plan-gate-commands.ts`). Enforcement is validator logic plus this prose file. No JSON Schema is authored, imported, or read.
|
|
6
|
+
|
|
7
|
+
## Scope of Invocation — no grandfathering or exemption mechanism
|
|
8
|
+
|
|
9
|
+
The plan validator only ever runs against the single artifact it is pointed at. No CI job, test, or scheduled task sweeps the committed plan corpus, and none is added by this feature. A pre-existing plan that would produce a finding is therefore never evaluated unless someone deliberately points the validator at it.
|
|
10
|
+
|
|
11
|
+
That scope is the argument against a grandfathering list, an exemption marker, a per-plan suppression comment, and an allowlist file. Each of those mechanisms exists to protect an existing corpus from a newly added sweep. With no sweep there is nothing to protect, and the mechanism would add a suppression surface whose only reachable use is to silence a finding on the plan currently being authored — which is precisely the case the gate exists to report.
|
|
12
|
+
|
|
13
|
+
The consequence is that adding a rule to this set is cheap in migration cost and expensive in authoring cost. Weigh a new rule on its false-positive rate at authoring time, not on how many committed plans it would have flagged.
|
|
14
|
+
|
|
15
|
+
## Rule Table
|
|
16
|
+
|
|
17
|
+
Every finding string begins with the square-bracketed `P#-T#` identifier of the task the command is attributed to, and renders the offending value or literal between backticks.
|
|
18
|
+
|
|
19
|
+
| Rule | Condition | Shipped severity |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| **G1** | A non-placeholder `--cov` value whose text, truncated at the first `::`, ends with `.py`. A `.py` suffix proves a filesystem path, which `coverage.py` rejects; the check is context-free and needs no repository lookup. | **Blocking** |
|
|
22
|
+
| **G2** | A `--cov` value containing a path separator whose text plus `.py` is a tracked file. The tracked sibling names the intended module exactly, so the dotted remedy is known. | **Blocking** |
|
|
23
|
+
| **G3** | A `--cov` value containing a path separator that resolves to neither a tracked file plus `.py` nor a tracked directory. Data collection is unknown rather than provably absent. | **Warning** |
|
|
24
|
+
| **G4** | A `--cov` value supplied space-separated (`--cov <value>`) rather than with `=`. The ambiguous form can bind the following positional argument. Independent of resolvability, so it is reported for every value. | **Warning** |
|
|
25
|
+
| **G5** | A checkable search literal that is absent from the tracked tree **and** not quoted in the plan document outside the command span it was read from. | **Warning** (see below) |
|
|
26
|
+
| **G6** | A checkable search literal absent from every single line of a tracked file but present in that file's sliding-window join of adjacent lines. A line-oriented search returns zero matches. | **Warning** |
|
|
27
|
+
|
|
28
|
+
G1 through G4 form a cascade over each `--cov` value: the value is decided once, so a value G1 rejects is never additionally reported by G2 or G3. G4 is evaluated independently of the cascade because the ambiguous form is a defect whatever the value resolves to. G6 is evaluated before G5, because cross-line presence falsifies G5's tree-absence claim.
|
|
29
|
+
|
|
30
|
+
G1 and G4 are context-free and run on every invocation. G2, G3, G5, and G6 require a repository seam; with no context supplied they do not run, and the Blocking list is byte-identical to the pre-change output for the same text.
|
|
31
|
+
|
|
32
|
+
### Attribution window
|
|
33
|
+
|
|
34
|
+
A command span is attributed to the current `P#-T#` when it sits on a task line or on a following line that is not itself a task line and is not separated from the task line by a Markdown ATX heading. A span in the document preamble, in a phase preamble, or after an intervening heading belongs to no task and is dropped rather than reported. A span that belongs to no task cannot be reported against one.
|
|
35
|
+
|
|
36
|
+
### Graceful degradation
|
|
37
|
+
|
|
38
|
+
A repository seam that raises, or that reports a non-zero exit, causes G2, G3, G5, and G6 to be skipped. No finding is produced and no exception escapes the evaluation entry point. A validation run must never fail because the repository could not be queried.
|
|
39
|
+
|
|
40
|
+
## Severity Decisions
|
|
41
|
+
|
|
42
|
+
### G5 — fixed by the corpus measurement and by nothing else
|
|
43
|
+
|
|
44
|
+
The shipped G5 severity was not chosen by argument. It was fixed by a pre-declared rule applied to a measurement over the committed plan corpus: Blocking if and only if the total G5 finding count is greater than zero **and** the recorded false-positive count is zero; otherwise Warning.
|
|
45
|
+
|
|
46
|
+
The measurement is recorded in `docs/features/active/2026-08-17-reject-unfalsifiable-acceptance-gates-in-atomic-plans-486/evidence/qa-gates/g5-corpus-measurement.2026-08-20T12-02.md`. It scanned 166 plan files, evaluated 100 candidate literals, and produced a total G5 finding count of 0. A zero false-positive count over zero findings measures nothing, so the first conjunct failed and **G5 ships as a Warning**.
|
|
47
|
+
|
|
48
|
+
The zero count is a property of the corpus, not a defect in the measurement. Every committed plan is a tracked file, so a fixed-string search for a literal quoted inside a committed plan always finds at least that plan itself, and the tree-absence condition holds for no committed candidate. The measurement artifact records the four checks that established this (non-vacuous enumeration, a working repository seam, a self-hit on every sampled lookup, and predicate-order equivalence with the shipped rule).
|
|
49
|
+
|
|
50
|
+
The rule remains meaningful for its intended use. The validator runs against a single plan artifact at authoring time, when that plan is typically uncommitted and therefore untracked, so its own text does not satisfy the tracked-tree presence test. The plan-quotation condition is what exonerates a literal the plan instructs the executor to create.
|
|
51
|
+
|
|
52
|
+
A later feature may revisit the severity, but only against a fresh measurement taken the same way. The severity is a single constant in each runtime (`G5_SEVERITY`), and a parity test asserts the two constants agree.
|
|
53
|
+
|
|
54
|
+
### G6 — ships as a Warning
|
|
55
|
+
|
|
56
|
+
The Blocking argument for G6 is real and is preserved here rather than discarded. A literal present only across a line wrap is *provably* unmatched by a line-oriented search: the tracked evidence shows the phrase exists in the file yet matches no single line, so the assertion is known to return zero matches. That is a stronger evidential position than G3, which only reports that resolution is unknown, and it is comparable to G1, which is Blocking.
|
|
57
|
+
|
|
58
|
+
G6 nonetheless ships as a Warning because of a residual false-positive case the rule cannot distinguish. The window join is computed over the file's committed text at `HEAD`. When the plan's own task is what rewrites that file so the phrase lands on one line, the pre-change committed text legitimately wraps the phrase and the post-change text does not. G6 then reports a search that will match after the task runs. The plan-quotation exoneration catches the common form of this case, but only when the plan quotes the literal contiguously in prose outside the command span; a plan that paraphrases the intended edit is still reported.
|
|
59
|
+
|
|
60
|
+
Rejecting such a plan would block a correct plan on evidence about a state the plan is about to change. Surfacing the finding without failing the gate gives the author the same information at no such cost. Reclassifying G6 as Blocking requires first eliminating that case, for example by evaluating the window join against the working tree rather than `HEAD`.
|
|
61
|
+
|
|
62
|
+
### The G6 sliding window is four adjacent non-blank lines
|
|
63
|
+
|
|
64
|
+
The window size is fixed at four adjacent non-blank lines. Blank lines are removed before windowing, and one window is emitted per start position, so the boundary is exact: two lines further apart than the window size never appear in the same join. The size is recorded here rather than left implicit so that a later feature can revise it against measured wrap-depth data instead of re-deriving it.
|
|
65
|
+
|
|
66
|
+
## Checkable-Literal Definition and the Placeholder Guard
|
|
67
|
+
|
|
68
|
+
G5 and G6 apply only to a *checkable* literal. The specification defines a checkable literal by two conditions: the command carries the fixed-string flag `-F`, or the pattern contains none of the regular-expression metacharacters `. * [ ] ^ $ \ ( ) { } | + ?`. That condition is conservative in POSIX BRE, POSIX ERE, PCRE, and the Rust regex dialect simultaneously, so no dialect-selection logic is required.
|
|
69
|
+
|
|
70
|
+
The shipped predicate **extends** that definition with a third condition, and the extension is deliberate: a pattern operand containing any placeholder or interpolation marker is never checkable, even when `-F` is supplied. The markers are `<`, `>`, `${`, `$(`, and `%` — the same set the coverage rules use to skip a placeholder `--cov` value.
|
|
71
|
+
|
|
72
|
+
The guard exists because a command span whose operand is a placeholder was never intended to be executed verbatim. It documents a command *shape*, so it states no real acceptance assertion, and the resolvability of a placeholder operand is not decidable. Without the guard, every plan that documents a command shape using a placeholder operand receives a G5 finding.
|
|
73
|
+
|
|
74
|
+
### Known false-negative class
|
|
75
|
+
|
|
76
|
+
The guard is purely textual, so it fires on any pattern containing a placeholder character in any role. A literal that uses `<`, `>`, or `%` as an ordinary character — a TypeScript generic, a comparison operator, a version constraint, a percentage, an HTML or XML tag — is therefore skipped and can never produce a G5 or G6 finding, however unfalsifiable the assertion actually is.
|
|
77
|
+
|
|
78
|
+
This is a false-negative class, not a defect to be silently tolerated: it is the cost side of the trade recorded below, and a later feature that narrows the guard must re-measure the false-positive side before doing so. Narrowing candidates include restricting the markers to bracket *pairs* enclosing an identifier-like token, or to the interpolation forms `${` and `$(` plus `%NAME%`, rather than treating every bare `<`, `>`, and `%` as a marker.
|
|
79
|
+
|
|
80
|
+
### Preflight measurement that fixed the trade
|
|
81
|
+
|
|
82
|
+
The trade was settled by measurement, not by preference. Across the 164-plan corpus examined at preflight, the placeholder guard suppressed exactly three pattern operands and suppressed zero additional findings:
|
|
83
|
+
|
|
84
|
+
1. An **angle-bracketed placeholder** inside a documented `git grep` command shape. This is the guard's intended target: the command was written to show a shape, not to be run.
|
|
85
|
+
2. A **TypeScript generic** of the `warnings?: ReadonlyArray<string>` shape. Its own plan quotes the token contiguously in prose, so the plan-quotation condition would have exonerated it regardless; the guard changed nothing for this operand.
|
|
86
|
+
3. A **version constraint** of the `Node >=18` shape. The token is present in the tracked tree, so the tree-absence condition never held and no finding would have been produced; the guard again changed nothing.
|
|
87
|
+
|
|
88
|
+
Only the first operand was suppressed by the guard in a way that altered the outcome, and it is the case the guard is for. The other two were already exonerated by conditions the guard does not touch. Against that, removing the guard would have produced a finding on every plan that documents a placeholder-bearing command shape. The measured cost of the guard on this corpus is therefore zero suppressed true positives.
|
|
89
|
+
|
|
90
|
+
## Message Formatting — no `repr()`, no `!r`, no `pythonRepr`
|
|
91
|
+
|
|
92
|
+
Every gate message renders the offending coverage value or search literal **between backticks**, in both runtimes, with no surrounding quote characters supplied by a formatting helper.
|
|
93
|
+
|
|
94
|
+
The following are prohibited in gate messages:
|
|
95
|
+
|
|
96
|
+
- Python `repr()` and the `!r` conversion in an f-string.
|
|
97
|
+
- Any `pythonRepr` helper on the TypeScript side.
|
|
98
|
+
|
|
99
|
+
The reason is byte-identity across the two runtimes. Python's `repr` selects its quote character based on the value's contents, switching to double quotes when the value contains a single quote, while the TypeScript `pythonRepr` helper used elsewhere in this repository always single-quotes. A value carrying an apostrophe would therefore render differently in the two runtimes, and the parity requirement would fail on exactly the class of value a maintainer is most likely to encounter in a path or a prose literal. Backtick delimiting has no content-dependent behaviour and needs no helper.
|
|
100
|
+
|
|
101
|
+
The prohibition is enforced by tests, not only by prose: a parity test asserts the Python gate module contains neither `!r` nor `repr(`, a companion test asserts no `pythonRepr(` call appears in any of the three TypeScript gate modules, and the parity fixture set includes an apostrophe-bearing `--cov` value and an apostrophe-bearing search literal whose expected strings are asserted identically in both runtimes.
|
|
102
|
+
|
|
103
|
+
## Authoring Guidance for Plan Authors
|
|
104
|
+
|
|
105
|
+
- Express coverage targets as importable dotted names (`--cov=scripts.dev_tools.module`), never as filesystem paths, and always with the `=` form.
|
|
106
|
+
- Where an acceptance condition is a search, assert a short, single-line, non-interpolated token that the plan quotes verbatim.
|
|
107
|
+
- Prefer a named test over a phrase search whenever a test can carry the assertion.
|
|
108
|
+
|
|
109
|
+
`.claude/skills/atomic-plan-contract/SKILL.md` carries the authoring-side statement of this guidance and cross-references this file.
|
|
110
|
+
|
|
111
|
+
## Enforcement
|
|
112
|
+
|
|
113
|
+
- `scripts/dev_tools/plan_gate_commands.py` extracts task-attributed command candidates; `scripts/dev_tools/plan_gate_discrimination.py` evaluates G1 through G6 and returns the two severity channels.
|
|
114
|
+
- `scripts/dev_tools/validate_orchestration_artifacts.py` routes the existing `plan` artifact type through the two-channel entry point, prints each Warning to stderr prefixed with `PLAN GATE WARNING: `, and derives its exit code from the error channel alone. No new flag, option, or artifact type is added.
|
|
115
|
+
- The TypeScript parity port is dispatched from `extensions/drm-copilot/src/lib/validate/orchestration-artifacts.ts` for the existing `plan` artifact type. The MCP `validate_orchestration_artifacts` input-schema property-key set is unchanged; Warnings surface on an optional `warnings` field that is absent when there are none.
|
|
116
|
+
- `.claude/hooks/validate-planner-output.ps1` is not modified by this rule and carries no part of its enforcement.
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"Skill(execute-hard-lock *)",
|
|
65
65
|
"Skill(identify-session-id *)",
|
|
66
66
|
"Skill(show-my-agent-tree *)",
|
|
67
|
+
"Skill(mermaid-diagram *)",
|
|
67
68
|
"Edit(/.claude/skills/execute-hard-lock/**)",
|
|
68
69
|
"Edit(/.claude/skills/feature-review-workflow/**)",
|
|
69
70
|
"Edit(/.claude/skills/csharp-qa-gate/**)"
|
|
@@ -170,6 +171,10 @@
|
|
|
170
171
|
{
|
|
171
172
|
"type": "command",
|
|
172
173
|
"command": "pwsh -NoProfile -File .claude/hooks/enforce-discovery-artifact-gate.ps1"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"type": "command",
|
|
177
|
+
"command": "pwsh -NoProfile -File .claude/hooks/enforce-mermaid-validation.ps1"
|
|
173
178
|
}
|
|
174
179
|
]
|
|
175
180
|
},
|
|
@@ -157,6 +157,21 @@ Before a plan can be treated as approved:
|
|
|
157
157
|
- reject the plan if that validator exits non-zero,
|
|
158
158
|
- do not treat human-readable summaries as a substitute for validator success.
|
|
159
159
|
|
|
160
|
+
The same validator call also applies the acceptance-gate rules G1 through G6 defined in `.claude/rules/plan-acceptance-gates.md`. Those rules report acceptance conditions that cannot fail — a coverage argument that collects no data, or a search for a literal that returns zero matches whatever the executor does. They run automatically on the existing `plan` route with no additional flag. Blocking findings appear in the validator's error output and fail the gate; Warnings are surfaced without failing it, prefixed with `PLAN GATE WARNING: ` on the CLI and carried on the optional `warnings` field of the MCP result. Read that rule file before authoring acceptance conditions.
|
|
161
|
+
|
|
162
|
+
## Wrap-Tolerant Assertion Authoring (Mandatory)
|
|
163
|
+
|
|
164
|
+
An acceptance condition must be able to fail. A condition whose command returns the same result whatever the executor does verifies nothing, however precise it reads. Author every acceptance condition in a wrap-tolerant form: one that survives line wrapping and shell quoting in the file it asserts against.
|
|
165
|
+
|
|
166
|
+
Rules:
|
|
167
|
+
|
|
168
|
+
- **Prefer a named test over a phrase search.** When a test can carry the assertion, name the test and its node ID and assert its pass count. A test node ID is stable under reformatting; a prose phrase is not. Reserve searches for cases where no test can express the condition.
|
|
169
|
+
- **Single-line token rule.** Where a search is unavoidable, assert a short, single-line, non-interpolated token that the plan quotes verbatim. A multi-word phrase drawn from prose is wrap-fragile: once the target file reflows, the phrase spans two lines and a line-oriented search returns zero matches even though the text is present. Rule G6 in `.claude/rules/plan-acceptance-gates.md` reports this case.
|
|
170
|
+
- **No placeholders in an asserted token.** A token containing `<`, `>`, `${`, `$(`, or `%` is treated as a documented command shape rather than a real assertion and is skipped by the gate, so it gates nothing. Substitute the concrete value.
|
|
171
|
+
- **Dotted coverage-argument form.** Coverage assertions must name an importable dotted module, for example `--cov=scripts.dev_tools.plan_gate_discrimination`. The filesystem-path spellings `--cov=scripts/dev_tools/module.py` and `--cov=scripts/dev_tools/module` collect no data, so a coverage threshold asserted against them cannot fail. Rules G1 through G3 report those spellings.
|
|
172
|
+
- **Use the `=` form, not the space-separated form.** `--cov <value>` can bind the following positional argument. Rule G4 reports it.
|
|
173
|
+
- **Quote what the task will create.** When an asserted literal does not yet exist in the tree, quote the exact literal in the plan prose outside the command span. The gate reads that quotation as the executor's instruction and exonerates the assertion; a paraphrase does not.
|
|
174
|
+
|
|
160
175
|
## Plan-Path Continuity Contract (Mandatory)
|
|
161
176
|
|
|
162
177
|
When a caller provides an explicit target plan file path (for example `${plan-path}` or `${file}`):
|
package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md
CHANGED
|
@@ -110,6 +110,19 @@ When evidence artifacts are used for automated checking or plan reconciliation,
|
|
|
110
110
|
- `Command: <exact command>`
|
|
111
111
|
- `EXIT_CODE: <int>`
|
|
112
112
|
|
|
113
|
+
One optional field may also be declared:
|
|
114
|
+
- `ExpectedExitCode: <int>` — the exit code the gate is expected to produce.
|
|
115
|
+
|
|
116
|
+
Rules for the optional expectation field:
|
|
117
|
+
- The spelling is exact and case-sensitive: `ExpectedExitCode`. `expectedexitcode` and `Expected Exit Code` do not match the accept-list and are discarded as unrecognized rows.
|
|
118
|
+
- The value is a single integer. A leading sign is accepted and no range check is applied; the value is used for an equality comparison only.
|
|
119
|
+
- When the field is absent the expectation defaults to `0`, so every artifact that omits it keeps its existing result. Writing `ExpectedExitCode: 0` explicitly renders identically to omitting the field.
|
|
120
|
+
- A present but non-integer value (including an empty value) makes the WHOLE artifact `unparseable`. An unparseable artifact is dropped by the collector filter, so a typo in the expectation removes the row from the PR body rather than degrading it to `fail`.
|
|
121
|
+
- When the field is duplicated, the FIRST occurrence wins in both the Python and the TypeScript parser; later occurrences are ignored.
|
|
122
|
+
- The field is per-FILE, not per-gate: one artifact carries exactly one expectation, so an artifact recording several gates cannot express a different expectation for each. Record a gate that needs a non-zero expectation in its own artifact file.
|
|
123
|
+
|
|
124
|
+
A gate whose observed `EXIT_CODE` equals its declared expectation is normalized to `pass`. The observed exit code is still displayed, and the rendered row additionally carries ` - Expected EXIT_CODE: <int>` between the `EXIT_CODE` and `Normalized result` lines when the expectation is non-zero.
|
|
125
|
+
|
|
113
126
|
### Baseline Evidence Output Summary (Required)
|
|
114
127
|
|
|
115
128
|
For baseline evidence artifacts stored under `evidence/baseline/`, include an output summary in addition to the schema fields above:
|
|
@@ -68,8 +68,14 @@ When orchestrator routing selects short path, promotion/folder initialization st
|
|
|
68
68
|
- `${feature-folder}/issue.md` contains an explicit `## Acceptance Criteria` section
|
|
69
69
|
- `${feature-folder}/spec.md` does not exist
|
|
70
70
|
- `${feature-folder}/user-story.md` does not exist
|
|
71
|
+
- the promoted record under `docs/features/potential/promoted/` is still present (see 4b)
|
|
71
72
|
- if any check fails, stop and remediate before planning
|
|
72
73
|
|
|
74
|
+
4b) Verify the promoted record was retained after `new_active_feature_folder`:
|
|
75
|
+
- the promoted file the earlier `potential_to_issue` step reported as its `destination_path` must still exist under `docs/features/potential/promoted/`
|
|
76
|
+
- `new_active_feature_folder` COPIES a promoted source into the active folder as `issue.md`; it MOVES a source resolved from `docs/features/potential/` directly. An absent promoted record after a promoted-source run is a defect, not expected cleanup (issue #487).
|
|
77
|
+
- this check applies to every work mode, not only `minor-audit`
|
|
78
|
+
|
|
73
79
|
5) Delegate minimal-audit plan creation to `atomic_planner` with directive:
|
|
74
80
|
- `DIRECTIVE: MINIMAL-AUDIT PLAN REQUIRED`
|
|
75
81
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mermaid-diagram
|
|
3
|
+
description: 'Generate, validate, and render Mermaid diagrams (flowchart, sequence, class, state, ER, C4, gantt, pie). Use when asked to create, edit, fix, or visualize a diagram, write a .mmd file, or embed a mermaid fence in Markdown. Bundles per-type syntax references and the generate-validate-render workflow enforced by the enforce-mermaid-validation hook.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mermaid Diagram
|
|
7
|
+
|
|
8
|
+
Authoring workflow for Mermaid diagrams on the Claude runtime. The constraints — file conventions,
|
|
9
|
+
the validation mandate, the managed-diagram rule, and the opt-out marker — are in
|
|
10
|
+
`.claude/rules/mermaid.md`. This skill carries the workflow and the generation recipes.
|
|
11
|
+
|
|
12
|
+
Pinned Mermaid documentation version: **11.17.0**.
|
|
13
|
+
|
|
14
|
+
## Workflow: Generate, Validate, Render
|
|
15
|
+
|
|
16
|
+
1. **Determine the diagram type.** Pick from the per-type references below. When the type is
|
|
17
|
+
unfamiliar, read its reference file before generating; when the reference does not answer the
|
|
18
|
+
question, `WebFetch` the pinned documentation page (see [Syntax References](#syntax-references)).
|
|
19
|
+
2. **Generate the syntax.** Follow the reference's first-line keyword form and its arrow token set.
|
|
20
|
+
Keywords are case-sensitive.
|
|
21
|
+
3. **Write the diagram.** A standalone diagram goes in a `.mmd` file; a diagram that belongs to
|
|
22
|
+
prose goes in a fenced ` ```mermaid ` block in that document.
|
|
23
|
+
4. **Validate.** The `Write` is gated automatically by
|
|
24
|
+
`.claude/hooks/enforce-mermaid-validation.ps1`. A deny names the defect class, the line number,
|
|
25
|
+
and points back here. To check before writing, call the validator directly:
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
Import-Module ./.claude/lib/mermaid/MermaidValidation.psm1 -Force
|
|
29
|
+
Test-MermaidDiagram -Content $diagramText
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The result carries `Verdict` (`Valid`, `Invalid`, `NotJudged`), `DiagramType`, `Findings` (each
|
|
33
|
+
with `Class`, `Line`, `Message`), and `Warnings`.
|
|
34
|
+
5. **Render** per [Rendering](#rendering). Rendering is a workflow step, never something the hook
|
|
35
|
+
does: a hook is a non-interactive subprocess whose stdout belongs to the hook protocol.
|
|
36
|
+
|
|
37
|
+
### What "validated" means here
|
|
38
|
+
|
|
39
|
+
The gate rejects the defect classes listed in `.claude/rules/mermaid.md`. It does not parse and it
|
|
40
|
+
cannot prove a diagram renders. Do not report a diagram as "validated" without that qualifier; say
|
|
41
|
+
the structural gate accepted it. Semantic errors — an undefined node reference, a malformed gantt
|
|
42
|
+
date, an invalid `classDef` property — pass the gate and still fail to render.
|
|
43
|
+
|
|
44
|
+
## Generation Recipes
|
|
45
|
+
|
|
46
|
+
Eight recipes, one per generation intent of the Copilot `@mermaid-chart` participant. Each names
|
|
47
|
+
the source to read, the diagram type to emit, and the shape that survives review.
|
|
48
|
+
|
|
49
|
+
### 1. Diagram from code (`/generate_diagram_from_code`)
|
|
50
|
+
|
|
51
|
+
Read the entry point and follow control flow outward one level at a time. Emit a `flowchart`
|
|
52
|
+
whose nodes are functions or modules and whose edges are calls. Keep node labels to the symbol
|
|
53
|
+
name; put qualifiers in a quoted label rather than in the identifier. Stop at the first boundary
|
|
54
|
+
the reader does not need (framework internals, third-party libraries) and mark it as one node.
|
|
55
|
+
|
|
56
|
+
### 2. Execution sequence (`/generate_execution_sequence`)
|
|
57
|
+
|
|
58
|
+
Emit a `sequenceDiagram`. One `participant` per process, service, or object that owns state;
|
|
59
|
+
messages in call order; the message text after the first colon carries the payload summary. Use
|
|
60
|
+
`-->>` for returns and `->>` for calls so the direction reads without the labels. Reserve `activate`
|
|
61
|
+
and `deactivate` for lifetimes the reader must see; they add noise otherwise.
|
|
62
|
+
|
|
63
|
+
### 3. ER diagram (`/generate_er_diagram`)
|
|
64
|
+
|
|
65
|
+
Read the schema, ORM models, or migration files. Emit an `erDiagram`. One entity per table, the
|
|
66
|
+
cardinality token pair chosen from the reference table, and the relationship label as the verb the
|
|
67
|
+
domain uses. Include an attribute block only for the columns that carry the relationship (keys) or
|
|
68
|
+
that the reader must see; a full column dump defeats the diagram.
|
|
69
|
+
|
|
70
|
+
### 4. Cloud or CI/CD architecture (`/generate_cloud_architecture_diagram`)
|
|
71
|
+
|
|
72
|
+
Read the infrastructure-as-code files and the workflow definitions. Emit a `flowchart` with one
|
|
73
|
+
`subgraph` per environment, account, or region boundary, and `-.->` for asynchronous or
|
|
74
|
+
event-driven edges against `-->` for synchronous ones. State the direction convention in a comment
|
|
75
|
+
so the next reader keeps it.
|
|
76
|
+
|
|
77
|
+
### 5. Docker architecture (`/generate_docker_diagram`)
|
|
78
|
+
|
|
79
|
+
Read the Dockerfiles and the compose file. Emit a `flowchart` with one node per service, one
|
|
80
|
+
`subgraph` per compose network, and edges labelled with the published or internal port. Show
|
|
81
|
+
volumes as nodes only when a volume is shared between services.
|
|
82
|
+
|
|
83
|
+
### 6. C4 top-down architecture (`/generate_c4_topdown_architecture`)
|
|
84
|
+
|
|
85
|
+
Emit `C4Context` for the system landscape, then `C4Container` for the chosen system, then
|
|
86
|
+
`C4Component` for the chosen container: one diagram per level, not one diagram with three levels.
|
|
87
|
+
Keywords carry a capital `C4`. Relationships use the `Rel(...)` call form rather than arrow tokens.
|
|
88
|
+
See `references/c4.md`.
|
|
89
|
+
|
|
90
|
+
### 7. Code ownership (`/analyze_code_ownership`)
|
|
91
|
+
|
|
92
|
+
Read `CODEOWNERS`, or derive ownership from directory structure when no such file exists. Emit a
|
|
93
|
+
`flowchart` with one `subgraph` per owning team and the owned directories as nodes. When ownership
|
|
94
|
+
is derived rather than declared, say so in the diagram title; an inferred ownership map presented
|
|
95
|
+
as authoritative is worse than none.
|
|
96
|
+
|
|
97
|
+
### 8. Dependency or security visualisation (`/generate_dependency_diagram`)
|
|
98
|
+
|
|
99
|
+
Read the manifest and lock files. Emit a `flowchart` for the dependency graph, direct dependencies
|
|
100
|
+
at the first level and transitive ones only where they matter to the question being asked. For a
|
|
101
|
+
security view, mark the affected node with a `classDef` and state the advisory identifier in the
|
|
102
|
+
label.
|
|
103
|
+
|
|
104
|
+
## Rendering
|
|
105
|
+
|
|
106
|
+
`Artifact` and `SendUserFile` are harness-dependent and are absent from some sessions. Take the
|
|
107
|
+
first available path:
|
|
108
|
+
|
|
109
|
+
1. **`Artifact` available.** Publish a Markdown artifact containing the ` ```mermaid ` fence. This
|
|
110
|
+
is the preferred path: no CSP handling and no theme handling, unlike an HTML artifact.
|
|
111
|
+
2. **Else `SendUserFile` with `display: "render"` available.** Use it.
|
|
112
|
+
3. **Else** state that the diagram was written to its path and name the viewing route: the Mermaid
|
|
113
|
+
Chart VS Code extension auto-previews `.mmd` and `.mermaid` files, the built-in VS Code Markdown
|
|
114
|
+
preview renders fenced blocks, and GitHub renders ` ```mermaid ` fences natively in Markdown,
|
|
115
|
+
pull requests, and issues.
|
|
116
|
+
|
|
117
|
+
Never claim a diagram was rendered when only path 3 was taken. Say where it was written and how to
|
|
118
|
+
view it.
|
|
119
|
+
|
|
120
|
+
## Opt-Out Marker
|
|
121
|
+
|
|
122
|
+
To quote invalid Mermaid deliberately, place the exact HTML comment on the line immediately before
|
|
123
|
+
the fence:
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
<!-- mermaid-validator: ignore -->
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The marker suppresses validation for exactly that one block, must have no intervening line before
|
|
130
|
+
the fence, applies only to Markdown fences, and never suppresses the managed-diagram guard. Full
|
|
131
|
+
rules are in `.claude/rules/mermaid.md`.
|
|
132
|
+
|
|
133
|
+
## Syntax References
|
|
134
|
+
|
|
135
|
+
Per-type references under `references/`, pinned to Mermaid 11.17.0:
|
|
136
|
+
|
|
137
|
+
| File | Covers |
|
|
138
|
+
| --- | --- |
|
|
139
|
+
| `references/flowchart.md` | `flowchart`, `graph`, `flowchart-elk` |
|
|
140
|
+
| `references/sequence.md` | `sequenceDiagram` |
|
|
141
|
+
| `references/class.md` | `classDiagram`, `classDiagram-v2` |
|
|
142
|
+
| `references/state.md` | `stateDiagram-v2`, `stateDiagram` |
|
|
143
|
+
| `references/er.md` | `erDiagram` |
|
|
144
|
+
| `references/c4.md` | `C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment` |
|
|
145
|
+
| `references/gantt.md` | `gantt` |
|
|
146
|
+
| `references/pie.md` | `pie` |
|
|
147
|
+
| `references/other-types.md` | every remaining keyword of the pinned table |
|
|
148
|
+
|
|
149
|
+
**`WebFetch` fallback.** The references are a snapshot, not the documentation. When a construct is
|
|
150
|
+
absent from them, or when a first-line keyword is not in the validator's allowlist, fetch the
|
|
151
|
+
pinned page and confirm the form before generating:
|
|
152
|
+
|
|
153
|
+
- entry point: `https://mermaid.js.org/intro/syntax-reference.html`
|
|
154
|
+
- per-type pages: `https://mermaid.js.org/syntax/<type>.html`
|
|
155
|
+
|
|
156
|
+
Confirming a keyword against the documentation is also the mechanism for updating
|
|
157
|
+
`.claude/lib/mermaid/MermaidGrammar.psm1` when Mermaid adds a diagram type: the validator warns
|
|
158
|
+
rather than blocks on an unknown keyword, so a warning is the signal to check and extend the table.
|
|
159
|
+
|
|
160
|
+
## Out of Scope
|
|
161
|
+
|
|
162
|
+
The VS Code extension mechanisms the Copilot instruction pack relies on are not reachable from a
|
|
163
|
+
Claude Code session. The full disposition table, one row per mechanism with its reason and its
|
|
164
|
+
replacement, is in `.claude/rules/mermaid.md` under "Out of Scope: The Non-Portable Extension
|
|
165
|
+
Mechanisms". In summary: the three LM tools, the sixteen `mermaidChart.*` command IDs, the
|
|
166
|
+
`@mermaid-chart` chat participants, and the Mermaid Chart cloud login/sync/review flows are not
|
|
167
|
+
ported; validation, generation, preview, and sync cooperation are ported by substitution to the
|
|
168
|
+
hook, these recipes, the rendering paths above, and the `id:` guard. Deep `mmdc`/Chromium
|
|
169
|
+
validation in CI, and retrofitting the existing repository Mermaid emitters through this validator,
|
|
170
|
+
are recorded follow-ups.
|
|
171
|
+
|
|
172
|
+
## Worked Example
|
|
173
|
+
|
|
174
|
+
```mermaid
|
|
175
|
+
---
|
|
176
|
+
title: Generate, validate, render
|
|
177
|
+
---
|
|
178
|
+
flowchart LR
|
|
179
|
+
A[Pick diagram type] --> B[Read references type page]
|
|
180
|
+
B --> C[Generate syntax]
|
|
181
|
+
C --> D{Structural gate}
|
|
182
|
+
D -->|deny with class and line| C
|
|
183
|
+
D -->|allow| E[Render or state the path]
|
|
184
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# C4 Diagram Syntax Reference
|
|
2
|
+
|
|
3
|
+
Pinned to Mermaid **11.17.0**. Source: `https://mermaid.js.org/syntax/c4.html`.
|
|
4
|
+
When a construct is absent here, `WebFetch` that page and confirm the form before generating.
|
|
5
|
+
|
|
6
|
+
## First-line keyword forms
|
|
7
|
+
|
|
8
|
+
`C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment`.
|
|
9
|
+
|
|
10
|
+
The capital `C4` is part of the keyword. `c4context` does not resolve, and the validator is
|
|
11
|
+
case-sensitive by design because Mermaid is.
|
|
12
|
+
|
|
13
|
+
Emit one diagram per C4 level rather than one diagram spanning levels: context first, then the
|
|
14
|
+
container view of the chosen system, then the component view of the chosen container.
|
|
15
|
+
|
|
16
|
+
## Statement forms
|
|
17
|
+
|
|
18
|
+
C4 uses call-style statements, not arrow tokens. The validator therefore keyword-checks a C4
|
|
19
|
+
diagram and does not judge its body.
|
|
20
|
+
|
|
21
|
+
- Elements: `Person(alias, label, description)`, `Person_Ext(...)`,
|
|
22
|
+
`System(alias, label, description)`, `System_Ext(...)`, `SystemDb(...)`, `SystemQueue(...)`,
|
|
23
|
+
`Container(alias, label, technology, description)`, `ContainerDb(...)`, `ContainerQueue(...)`,
|
|
24
|
+
`Component(alias, label, technology, description)`.
|
|
25
|
+
- Boundaries: `Enterprise_Boundary(alias, label) { ... }`, `System_Boundary(...)`,
|
|
26
|
+
`Container_Boundary(...)`, `Boundary(alias, label, type)`. Braces are paired.
|
|
27
|
+
- Relationships: `Rel(from, to, label, technology)` plus the directional variants `Rel_U`, `Rel_D`,
|
|
28
|
+
`Rel_L`, `Rel_R`, and `BiRel(...)` for a two-way relationship.
|
|
29
|
+
- Layout: `UpdateLayoutConfig($c4ShapeInRow, $c4BoundaryInRow)`. Styling:
|
|
30
|
+
`UpdateElementStyle(alias, $bgColor, $fontColor, $borderColor)`,
|
|
31
|
+
`UpdateRelStyle(from, to, $offsetX, $offsetY)`.
|
|
32
|
+
|
|
33
|
+
## Example
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
C4Context
|
|
37
|
+
title System context for the order service
|
|
38
|
+
Person(customer, "Customer", "Places and tracks orders")
|
|
39
|
+
Enterprise_Boundary(company, "Retail company") {
|
|
40
|
+
System(orders, "Order service", "Accepts and tracks orders")
|
|
41
|
+
System(billing, "Billing service", "Charges cards and issues refunds")
|
|
42
|
+
SystemDb(orderdb, "Order store", "Durable order records")
|
|
43
|
+
}
|
|
44
|
+
System_Ext(psp, "Payment provider", "Third-party card processing")
|
|
45
|
+
Rel(customer, orders, "Places an order", "HTTPS/JSON")
|
|
46
|
+
Rel(orders, orderdb, "Reads and writes", "SQL")
|
|
47
|
+
Rel(orders, billing, "Requests a charge", "internal API")
|
|
48
|
+
Rel(billing, psp, "Authorises the card", "HTTPS")
|
|
49
|
+
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
|
50
|
+
```
|