mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-26 18:35:05 +00:00
feat(governance): port optional module and context workflows
Integrate contributor PRs #3533, #3534, and #3535 on current main. Preserve the read-only module audit, canonical glossary and ADR ownership, and opt-in install closure. Refresh only the manual trigger manifest registry binding and coverage after adding the skill.
This commit is contained in:
1 parent
e3292b0010
commit
7b1449ef53
52 files changed
+1352
-64
No files matched your search
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Matt Pocock
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -10,7 +10,7 @@ Govern “what these business terms mean” separately from “why this option w
|
||||
|
||||
## Domain Context Artifact: Domain Language Only
|
||||
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. Only when no equivalent exists should you lazily create the default `CONTEXT.md` from `templates/context.example.md`.
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. If an existing context map identifies separate business contexts, follow its links to the relevant glossary; ask when the topic's owner is unclear. Different contexts may legitimately use the same word differently. Do not create a glossary for every directory or impose one meaning across unrelated contexts. Only when no equivalent exists and a term has been resolved should you propose the default `CONTEXT.md` from `templates/context.example.md`. Obtain the user's explicit approval to create that new artifact before writing it, and stay within the authorized documentation scope. General permission to update documentation does not itself approve a new artifact; editing an existing mapped glossary within the authorized scope does not require a new-artifact approval.
|
||||
|
||||
Record only:
|
||||
|
||||
@@ -21,11 +21,23 @@ Record only:
|
||||
|
||||
Do not include implementation details, current state, task schedules, full requirements, decision rationale, or history. Keep those in code/MAP, STATUS/Issue Tracker, Spec, ADR, and LOG respectively. Terms must be traceable to code, contracts, or business evidence. Mark unconfirmed items as `needs confirmation`; do not guess.
|
||||
|
||||
## Refine Language During the Conversation
|
||||
|
||||
This is an active modeling workflow, not a requirement to rewrite the glossary whenever another skill reads it.
|
||||
|
||||
- **Challenge contradictions.** Compare the user's wording with the glossary. If “cancellation” already means cancelling an entire order but the request permits cancelling one line, explain that distinction and ask which meaning is intended; do not silently redefine the term.
|
||||
- **Separate overloaded concepts.** For “account,” establish whether the speaker means the customer organization, a person's login, or a billing relationship. Propose a precise term for each distinct concept. Record rejected synonyms only where they really denote the same concept in this context.
|
||||
- **Probe relationships with concrete scenarios.** Ask, for example, whether one customer can have multiple logins, or what happens to an invoice when only part of an order is cancelled. Scenarios expose boundaries; they are questions, not invented business rules.
|
||||
- **Check claims against evidence.** Inspect relevant code, tests, contracts, and existing records. If the implementation cancels only whole orders, surface the mismatch with the proposed partial-cancellation rule. Code demonstrates current behavior, not the business owner's desired behavior; do not decide which is correct by yourself.
|
||||
- **Capture confirmed terms as they settle.** Within an authorized documentation edit, update the existing glossary at that point instead of deferring all definitions until closeout. Keep definitions to one or two sentences, with a source and context-specific avoided synonyms where useful. Do not rewrite code, contracts, or accepted decisions merely to match a proposed term. In discussion-only or read-only work, present the proposed wording without writing files.
|
||||
|
||||
Use the project's existing glossary format. For a new document, the [context template](templates/context.example.md) provides a compact starting point; omit sections without real content. Include business-specific concepts, not generic programming vocabulary such as timeouts or utility functions.
|
||||
|
||||
## ADR Handoff: Reuse the Existing Capability
|
||||
|
||||
This skill does not define a second ADR directory, numbering scheme, lifecycle, or template. When a decision is hard to reverse, would confuse a future reader without context, and involves a real tradeoff:
|
||||
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location and format.
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location, format, and explicit approval gates for initialization and writing.
|
||||
2. If no convention exists, let that skill choose the minimum viable location; do not create a separate system here.
|
||||
3. Link to the ADR from `CONTEXT.md`, the project map, or the relevant Spec/Issue instead of copying the decision body.
|
||||
4. When a decision is accepted, superseded, or deprecated, append an event to project history according to the living-document rules.
|
||||
@@ -38,14 +50,16 @@ Routine implementation details, easily reversible changes, and short-lived exper
|
||||
- The project needs a stable domain-language entry point but has no equivalent document.
|
||||
- Context work uncovers a durable decision that should be handed to `architecture-decision-records`.
|
||||
|
||||
Merely consuming established vocabulary during an ordinary bug fix does not require a modeling session or new documentation.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when stable domain language needs one evidence-backed home, or when context work reveals a hard-to-reverse decision that belongs in the repository's existing ADR system.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available.
|
||||
2. Record precise domain definitions, relationships, confirmed interpretations, and explicitly unconfirmed ambiguities without mixing in implementation or task state.
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available and the user has explicitly approved creating that new artifact.
|
||||
2. Challenge conflicting language, probe concrete edge cases, and cross-check claims against code and contracts before recording confirmed definitions and relationships. Keep unresolved interpretations explicit without mixing in implementation or task state.
|
||||
3. Route durable tradeoffs to `architecture-decision-records` and link the resulting ADR instead of copying its rationale into context documents.
|
||||
4. Keep schedules, blockers, and execution status in the existing issue tracker.
|
||||
|
||||
@@ -60,6 +74,8 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
- Copying current tasks, schedules, full requirements, or implementation details into `CONTEXT.md`.
|
||||
- Creating empty context files or a second ADR system for completeness.
|
||||
- Treating unconfirmed domain interpretations as facts, or copying ADR bodies into maps and logs.
|
||||
- Renaming distinct business concepts as synonyms, or forcing separate contexts into one universal glossary.
|
||||
- Treating a glossary update as permission to change implementation, or treating code as proof of the intended business rule.
|
||||
|
||||
## Related Skills
|
||||
|
||||
@@ -70,3 +86,7 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
## Scheduling and Task Boundary
|
||||
|
||||
Keep tasks, status, blockers, and schedules in the project's existing tracker, such as GitHub Issues or Linear, as the single source of truth. Use local `.scratch/` only when no external tracker exists and the project explicitly adopts it. Do not put schedules in `CONTEXT.md`, ADRs, PROJECT_STATUS, or the history index database.
|
||||
|
||||
## Attribution
|
||||
|
||||
The active terminology-refinement workflow adapts Matt Pocock's MIT-licensed `domain-modeling` skill. See [source and adaptation boundaries](references/source.md). ECC's existing ADR workflow remains the sole decision-record owner.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Source and adaptation
|
||||
|
||||
The active terminology-refinement workflow and compact glossary guidance are adapted from [Matt Pocock's domain-modeling skill](https://github.com/mattpocock/skills/tree/c55ee46073ed923f86ce59a5eb3b6d895095d1b7/skills/engineering/domain-modeling), pinned at `c55ee46073ed923f86ce59a5eb3b6d895095d1b7`, under the [MIT license](../LICENSE).
|
||||
|
||||
This is an in-place ECC adaptation, not another skill or an external runtime dependency. It preserves ECC's mapped glossary selection, evidence and authorization boundaries, and handoff to `architecture-decision-records`. It does not import a second ADR template, directory, numbering scheme, or lifecycle. The existing ECC context template is extended rather than replaced with a second glossary format.
|
||||
@@ -1,12 +1,14 @@
|
||||
# CONTEXT.md — Domain Language
|
||||
|
||||
> Create only when needed. Do not include implementation, status, scheduling, or decisions.
|
||||
> Create only after the first term is resolved, no equivalent glossary exists, and the user has explicitly approved creating this new artifact within the authorized documentation scope. General documentation-update permission alone is not creation approval. Reuse an existing glossary and its format instead of creating a duplicate; authorized edits to that existing glossary do not need a new-artifact approval. Omit sections without content. Do not include implementation, status, scheduling, or decision rationale.
|
||||
|
||||
{One or two sentences identifying the business context this vocabulary describes.}
|
||||
|
||||
## Core Terms
|
||||
|
||||
| Term | Precise definition | Evidence/source |
|
||||
|---|---|---|
|
||||
| `{term}` | `{what it means and how it differs from adjacent terms}` | `{business rule, contract, or code location}` |
|
||||
| Term | Precise definition | Avoid in this context | Evidence/source |
|
||||
|---|---|---|---|
|
||||
| `{canonical term}` | `{one or two sentences defining the concept and distinguishing adjacent concepts}` | `{confirmed synonyms to avoid; not names for distinct concepts}` | `{confirmed business source, contract, or code location}` |
|
||||
|
||||
## Concept Relationships
|
||||
|
||||
|
||||
@@ -51,7 +51,30 @@ verification step. Resolve the selected skill directory first, then invoke:
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope full
|
||||
```
|
||||
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, and `full`.
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, `modules`, and `full`.
|
||||
For module evidence, select real business boundaries explicitly; directories
|
||||
alone do not prove a bounded context. Repeat `--module` for several boundaries:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope modules --module src/inventory --module src/billing
|
||||
```
|
||||
|
||||
`modules` requires a selected directory. `full` also accepts `--module`; other
|
||||
scopes reject it rather than silently skipping the requested checks. The module
|
||||
check finds local `AGENTS.md` and `CONTEXT.md`, or reports the nearest ancestor
|
||||
and mapped project sources as shared candidates. Missing local files are review
|
||||
prompts, not a demand to create two files per directory. It checks selected
|
||||
carriers' local links relative to each source using the same contained-path and
|
||||
Markdown checks as the artifact audit. It never infers harness loading from
|
||||
file presence.
|
||||
|
||||
After mechanical checks, inspect the actual module code, callers, contracts and
|
||||
tests: compare responsibilities and import/call boundaries with documented
|
||||
rules, and terminology with the canonical context source. Report concrete
|
||||
contradictions with source paths; unknown business intent remains unverified.
|
||||
Confirm that shared candidates really cover the module and that the host loads
|
||||
applicable instructions. Reuse existing contract and ADR owners rather than
|
||||
copying them into `AGENTS.md`. Passing links is not passing this semantic review.
|
||||
|
||||
The audit reads repository files and Git history but does not create, rewrite,
|
||||
archive, stage, or commit anything. It reports:
|
||||
|
||||
@@ -66,6 +66,14 @@ The audit does not determine whether prose is true, whether a requirement is
|
||||
satisfied, whether a task is complete, or whether a change should be merged.
|
||||
Warnings explicitly require semantic review.
|
||||
|
||||
Module auditing is opt-in through explicit `--module` directories with scope
|
||||
`modules` or `full`. Local `AGENTS.md` and `CONTEXT.md` are conventional evidence
|
||||
locations, not mandatory per-directory artifacts. Ancestor and mapped sources
|
||||
are shared candidates whose applicability must be confirmed manually; the audit
|
||||
does not equate a file's existence with instruction loading or business coverage.
|
||||
Module responsibilities, code/contract boundaries and vocabulary require source
|
||||
and test evidence after the contained-path and source-relative link checks.
|
||||
|
||||
For link checks, any `scheme://` destination is external. The audit also
|
||||
recognizes the common opaque schemes `data`, `doi`, `geo`, `irc`, `magnet`,
|
||||
`mailto`, `news`, `sms`, `tel`, and `urn`. Other colon-shaped destinations are
|
||||
|
||||
@@ -16,6 +16,7 @@ from markdown_links import (
|
||||
normalize_link_target,
|
||||
without_fenced_code,
|
||||
)
|
||||
from module_audit import check_modules
|
||||
|
||||
DEFAULT_ROLES = {
|
||||
"constitution": "CLAUDE.md",
|
||||
@@ -242,10 +243,12 @@ def resolve_target(root: Path, source: Path, target: str) -> Path | None:
|
||||
return resolve_within_root(root, source.parent, target)
|
||||
|
||||
|
||||
def check_markdown_links(root: Path, files: list[Path], report: Report) -> None:
|
||||
def check_markdown_links(
|
||||
root: Path, files: list[Path], report: Report, *, skip_templates: bool = True
|
||||
) -> None:
|
||||
broken: list[str] = []
|
||||
for source in files:
|
||||
if "templates" in source.relative_to(root).parts:
|
||||
if skip_templates and "templates" in source.relative_to(root).parts:
|
||||
continue
|
||||
text = source.read_text(encoding="utf-8")
|
||||
for raw in markdown_link_targets(text):
|
||||
@@ -716,11 +719,19 @@ def main() -> int:
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument(
|
||||
"--scope",
|
||||
choices=("spine", "context", "adr", "artifacts", "full"),
|
||||
choices=("spine", "context", "adr", "artifacts", "modules", "full"),
|
||||
default="full",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--module", action="append", default=[],
|
||||
help="Repository-relative module directory; repeat for several modules",
|
||||
)
|
||||
parser.add_argument("--log-threshold", type=int, default=200)
|
||||
args = parser.parse_args()
|
||||
if args.scope == "modules" and not args.module:
|
||||
parser.error("--scope modules requires at least one --module directory")
|
||||
if args.module and args.scope not in ("modules", "full"):
|
||||
parser.error("--module is supported only with --scope modules or full")
|
||||
|
||||
root = args.root.resolve()
|
||||
if not root.is_dir():
|
||||
@@ -728,6 +739,11 @@ def main() -> int:
|
||||
return 2
|
||||
report = Report()
|
||||
roles = load_roles(root, report)
|
||||
if args.module:
|
||||
check_modules(
|
||||
root, roles, args.module, report,
|
||||
resolved_within_root, read_utf8, check_markdown_links,
|
||||
)
|
||||
if args.scope in ("spine", "full"):
|
||||
check_spine(root, roles, report, args.log_threshold)
|
||||
if args.scope in ("context", "full"):
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Read-only evidence discovery for explicitly selected business modules."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
|
||||
class AuditReport(Protocol):
|
||||
def section(self, title: str) -> None: ...
|
||||
def ok(self, message: str) -> None: ...
|
||||
def warn(self, message: str) -> None: ...
|
||||
def fail(self, message: str) -> None: ...
|
||||
|
||||
|
||||
ReportT = TypeVar("ReportT", bound=AuditReport, contravariant=True)
|
||||
|
||||
|
||||
class LinkChecker(Protocol[ReportT]):
|
||||
def __call__(
|
||||
self, root: Path, files: list[Path], report: ReportT,
|
||||
*, skip_templates: bool = True,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
def carrier_candidates(root: Path, module: Path, name: str, mapped: str) -> list[Path]:
|
||||
directories = [module, *module.parents]
|
||||
directories = [root] if module == root else directories[:directories.index(root) + 1]
|
||||
candidates = [directory / name for directory in directories] + [root / mapped]
|
||||
return list(dict.fromkeys(candidates))
|
||||
|
||||
|
||||
def check_modules(
|
||||
root: Path,
|
||||
roles: dict[str, str],
|
||||
names: list[str],
|
||||
report: ReportT,
|
||||
resolve_source: Callable[[Path, Path], Path | None],
|
||||
read_source: Callable[[Path, ReportT, str], str | None],
|
||||
check_links: LinkChecker[ReportT],
|
||||
) -> None:
|
||||
"""Use the audit's existing containment, UTF-8 and Markdown checks unchanged."""
|
||||
report.section("modules")
|
||||
sources: set[Path] = set()
|
||||
for name in sorted(set(names)):
|
||||
requested = Path(name)
|
||||
module = resolve_source(root, root / requested)
|
||||
if requested.is_absolute() or module is None or not module.is_dir():
|
||||
report.fail(
|
||||
f"Module must be a repository-relative directory inside root: {name!r}"
|
||||
)
|
||||
continue
|
||||
report.ok(f"Selected module: {str(module.relative_to(root))!r}")
|
||||
for role, filename in (("agents", "AGENTS.md"), ("context", "CONTEXT.md")):
|
||||
found = False
|
||||
for candidate in carrier_candidates(root, module, filename, roles[role]):
|
||||
if not candidate.exists() and not candidate.is_symlink():
|
||||
continue
|
||||
resolved = resolve_source(root, candidate)
|
||||
if resolved is None or not resolved.is_file():
|
||||
report.fail(
|
||||
f"Module {role} source must be a repository-contained regular file: "
|
||||
f"{str(candidate.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if read_source(resolved, report, f"module {role} source") is None:
|
||||
found = True
|
||||
break
|
||||
sources.add(resolved)
|
||||
source_label = str(candidate.relative_to(root))
|
||||
if candidate.parent == module:
|
||||
report.ok(f"Local {role} source: {source_label!r}")
|
||||
else:
|
||||
report.warn(
|
||||
f"Shared {role} candidate: {source_label!r}; "
|
||||
f"confirm it covers module {str(module.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
report.warn(
|
||||
f"No {role} source found for module {str(module.relative_to(root))!r}; "
|
||||
"review existing rules or glossary before creating a file"
|
||||
)
|
||||
check_links(root, sorted(sources), report, skip_templates=False)
|
||||
report.warn(
|
||||
"Module responsibilities, instruction loading, terminology and code/contract "
|
||||
"consistency require semantic review; file presence is not proof of coverage"
|
||||
)
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: module-governance
|
||||
description: >-
|
||||
Establish or maintain a business module's local agent instructions, domain-context links, and interface ownership when implementing a new module or changing its responsibilities. Reuse project-level rules and existing ECC workflows; ordinary bug fixes, styling, and read-only design discussions do not start module documentation setup.
|
||||
metadata:
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Module Governance
|
||||
|
||||
Connect module-level working rules to existing domain language, contracts, and
|
||||
decisions. This is an orchestration skill, not a second DDD methodology or an
|
||||
automatic documentation generator.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- The user asks to implement a new business module, without needing to name this skill.
|
||||
- The user asks to document an existing module or maintain a confirmed responsibility change.
|
||||
- An authorized module change needs local instructions and canonical knowledge pointers.
|
||||
|
||||
Do not start setup for an ordinary bug, styling change, internal refactor with
|
||||
unchanged boundaries, or a new directory/UI component that simply inherits
|
||||
parent conventions. For design discussion or an audit alone, inspect and report
|
||||
without creating files. Intent-based selection is not a background hook and
|
||||
does not grant permission for unrelated code, commits, pushes, or business choices.
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Discover the boundary before creating documents
|
||||
|
||||
Read applicable project and ancestor harness instructions, the existing map,
|
||||
requirements, relevant code, and contracts. Identify the module's responsibilities,
|
||||
non-responsibilities, state ownership, and actual consumers. Directory names alone
|
||||
do not establish a bounded context. Surface material ambiguities instead of
|
||||
inventing business rules.
|
||||
|
||||
For an existing boundary change, use `change-impact` first. Its read-only and
|
||||
authorization constraints still apply; do not run an unrelated development workflow.
|
||||
|
||||
### 2. Separate project-wide rules from local rules
|
||||
|
||||
The project-level instruction source owns global constraints, module navigation,
|
||||
and cross-module collaboration. A local `AGENTS.md` owns only additional module
|
||||
constraints: responsibilities, state ownership, public-interface pointers, and
|
||||
verification entry points. Local rules cannot waive project-wide constraints.
|
||||
|
||||
Reuse an established `CLAUDE.md` or other active harness surface instead of
|
||||
forcing a rename or maintaining two copies. If parent instructions already
|
||||
cover the module and no local rule is missing, link that source and do not create
|
||||
a redundant local file. Follow an existing explicit local-entrypoint requirement.
|
||||
Do not claim that a harness automatically loads every nested instruction file.
|
||||
|
||||
### 3. Reuse the existing knowledge owners
|
||||
|
||||
| Information | Existing ECC owner |
|
||||
|---|---|
|
||||
| Stable terminology and concept relationships | `context-and-decisions`, using the existing glossary or context role |
|
||||
| Hard-to-reverse decisions and rationale | `architecture-decision-records`, through the existing context handoff |
|
||||
| Cross-boundary machine-checkable interfaces | `contract-first` |
|
||||
| Documentation navigation and lifecycle | `living-docs-governance` |
|
||||
| Affected consumers and boundary-change evidence | `change-impact` |
|
||||
|
||||
Load the applicable skill when its work is needed; do not duplicate its method
|
||||
here. Keep terminology in the relevant existing `CONTEXT.md` or equivalent,
|
||||
not in AGENTS. Frontend and backend in the same business context can share that
|
||||
glossary; UI-specific working rules may be local without creating a glossary per
|
||||
page. Reuse existing contracts and ADRs by reference, not by copying their bodies.
|
||||
|
||||
### 4. Maintain only the authorized records
|
||||
|
||||
Within the user's requested module work, update the smallest missing local rules
|
||||
and confirmed context entries. Use `living-docs-governance` for affected navigation
|
||||
and lifecycle updates, retaining its approval requirement for new top-level
|
||||
artifacts. Do not create empty files to meet a two-file quota. When implementation
|
||||
does not yet exist, label the boundary as confirmed design with code unverified;
|
||||
do not declare documentation/code consistency from prose alone.
|
||||
|
||||
### 5. Verify both meaning and links
|
||||
|
||||
Compare each claimed owner, interface, and constraint with code, canonical
|
||||
contracts, and executable evidence. Report contradictions for owner resolution;
|
||||
do not rewrite the rules merely to match an accidental implementation.
|
||||
|
||||
Resolve relative links from **each referencing document's directory**, not the
|
||||
terminal working directory. Resolve the installed `docs-governance` skill path
|
||||
and run its existing read-only link check as an agreed verification step:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope artifacts
|
||||
```
|
||||
|
||||
Confirm that the audit scanned the changed local instructions, context, and
|
||||
navigation; check its link results and exit code. Fix broken references and rerun,
|
||||
without creating empty targets. If the script is unavailable or excludes a target,
|
||||
report that target as unverified. A passing business test does not prove links are
|
||||
correct, and an existing same-named file may still be the wrong knowledge owner.
|
||||
|
||||
## Examples
|
||||
|
||||
- **“Implement an inventory module.”** Inspect existing boundaries; add local
|
||||
rules only for genuinely new constraints; reuse the domain glossary and API
|
||||
contract; link them from existing navigation; verify code and source-relative links.
|
||||
- **“Should payments become a separate module?”** Explain boundary options and
|
||||
evidence without writing files or treating the discussion as implementation approval.
|
||||
- **“Fix the inventory badge color.”** Read applicable instructions and fix the
|
||||
requested UI behavior; do not generate AGENTS, CONTEXT, or ADR documents.
|
||||
- **“Audit module documentation.”** Use the existing read-only audit and semantic
|
||||
review; a missing local file is not by itself permission to create one.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- A mandatory AGENTS/CONTEXT pair for every directory or frontend page.
|
||||
- Duplicating global constraints, glossary definitions, contracts, or ADR rationale.
|
||||
- Treating documentation checks as proof of completed business implementation.
|
||||
- Installing hooks or asserting automatic nested-file loading without real harness wiring.
|
||||
|
||||
## Delivery
|
||||
|
||||
Report canonical record paths, their project/module scope, actual changes,
|
||||
verification evidence, and unresolved ownership questions. Module governance
|
||||
delivers working rules and knowledge entry points, not a complete DDD conversion.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `context-and-decisions`
|
||||
- `architecture-decision-records`
|
||||
- `contract-first`
|
||||
- `change-impact`
|
||||
- `living-docs-governance`
|
||||
- `docs-governance`
|
||||
@@ -0,0 +1,6 @@
|
||||
interface:
|
||||
display_name: "Module Governance"
|
||||
short_description: "Connect module rules, context, and contracts"
|
||||
default_prompt: "Use $module-governance to establish or maintain module documentation using existing project knowledge owners."
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "Harness-native ECC operator layer - 71 agents, 311 skills, 97 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"description": "Harness-native ECC operator layer - 71 agents, 312 skills, 97 legacy command shims, reusable hooks, rules, selective install profiles, and production-ready workflows for Claude Code, Codex, OpenCode, Cursor, and related agent harnesses",
|
||||
"version": "2.2.3",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.2.3",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 71 agents, 311 skills, 97 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"description": "Harness-native ECC plugin for engineering teams - 71 agents, 312 skills, 97 legacy command shims, reusable hooks, rules, MCP conventions, and operator workflows for Claude Code plus adjacent agent harnesses",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ This file provides Gemini CLI with the baseline ECC workflow, review standards,
|
||||
|
||||
## Overview
|
||||
|
||||
ECC is a cross-harness coding system with 71 specialized agents, 311 skills, and 97 commands.
|
||||
ECC is a cross-harness coding system with 71 specialized agents, 312 skills, and 97 commands.
|
||||
|
||||
Gemini support is currently focused on a strong project-local instruction layer via `.gemini/GEMINI.md`, plus the shared MCP catalog and package-manager setup assets shipped by the installer.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ECC — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 71 specialized agents, 311 skills, 97 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 71 specialized agents, 312 skills, 97 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.2.3
|
||||
|
||||
@@ -202,7 +202,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
```
|
||||
agents/ — 71 specialized subagents
|
||||
skills/ — 311 workflow skills and domain knowledge
|
||||
skills/ — 312 workflow skills and domain knowledge
|
||||
commands/ — 97 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
@@ -141,12 +141,12 @@ Instead of rebuilding that process in every prompt, you install it once and make
|
||||
|
||||
ECC is MIT-licensed open source. It works best with Claude Code today, has native Codex and Qoder plugin paths, and provides capability-limited adapters for Cursor, OpenCode, Gemini, Zed, GitHub Copilot, Antigravity, Qwen, and other harnesses. See the [support status matrix](#platform-support) before assuming feature parity.
|
||||
|
||||
Access to 71 agents, 311 skills, and 97 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
Access to 71 agents, 312 skills, and 97 legacy command shims, plus hooks, rules, memory, continuous learning, and AgentShield security scanning. The agents are specialized for planning, review, build repair, security, architecture, and domain work.
|
||||
|
||||
| Included | Count | What it gives you |
|
||||
| ---------------- | ----------: | ------------------------------------------------------------------------------------ |
|
||||
| Agents | 71 agents | Planning, review, build repair, security, architecture, and domain work |
|
||||
| Skills | 311 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Skills | 312 skills | TDD, research, security, docs, frontend, data, ML, operations, and more |
|
||||
| Commands | 97 commands | Convenient entry points while ECC moves to a skills-first surface |
|
||||
| Hooks and memory | Runtime | Enforcement, session summaries, continuous learning, instincts, and context controls |
|
||||
| Rules | Selective | Always-loaded standards you choose by language or project |
|
||||
@@ -859,7 +859,7 @@ Stable graduation of the 2.0 line: control-pane substrate, worktree lifecycle se
|
||||
```text
|
||||
ECC/
|
||||
|-- agents/ # 71 specialized subagents for delegation
|
||||
|-- skills/ # 311 reusable workflows loaded on demand
|
||||
|-- skills/ # 312 reusable workflows loaded on demand
|
||||
|-- commands/ # 97 maintained slash-command shims
|
||||
|-- rules/ # opt-in common and language standards
|
||||
|-- hooks/ # runtime automation and enforcement
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 71 个代理、311 个技能和 97 个命令。
|
||||
**完成!** 你现在可以使用 71 个代理、312 个技能和 97 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Soul
|
||||
|
||||
## Core Identity
|
||||
ECC is a production-ready AI coding plugin with 71 specialized agents, 311 skills, 97 commands, and automated hook workflows for software development.
|
||||
ECC is a production-ready AI coding plugin with 71 specialized agents, 312 skills, 97 commands, and automated hook workflows for software development.
|
||||
|
||||
## Core Principles
|
||||
1. **Agent-First** — route work to the right specialist as early as possible.
|
||||
|
||||
@@ -107,6 +107,7 @@ skills:
|
||||
- market-research
|
||||
- mcp-dependency-review
|
||||
- mcp-server-patterns
|
||||
- module-governance
|
||||
- module-regression
|
||||
- mybatis-patterns
|
||||
- motion-advanced
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
# ECC — Agent Talimatları
|
||||
|
||||
Bu, yazılım geliştirme için 71 özel agent, 311 skill, 97 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||
Bu, yazılım geliştirme için 71 özel agent, 312 skill, 97 command ve otomatik hook iş akışları sağlayan **üretime hazır bir AI kodlama eklentisidir**.
|
||||
|
||||
**Sürüm:** 2.2.3
|
||||
|
||||
@@ -142,7 +142,7 @@ Başarısızlık sorunlarını giderin: test izolasyonunu kontrol edin → mockl
|
||||
|
||||
```
|
||||
agents/ — 71 özel subagent
|
||||
skills/ — 311 iş akışı skillleri ve alan bilgisi
|
||||
skills/ — 312 iş akışı skillleri ve alan bilgisi
|
||||
commands/ — 97 slash command
|
||||
hooks/ — Tetikleyici tabanlı otomasyonlar
|
||||
rules/ — Her zaman uyulması gereken kurallar (ortak + dile özel)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ECC — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 71 个专业代理、311 项技能、97 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 71 个专业代理、312 项技能、97 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.2.3
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
```
|
||||
agents/ — 71 个专业子代理
|
||||
skills/ — 311 个工作流技能和领域知识
|
||||
skills/ — 312 个工作流技能和领域知识
|
||||
commands/ — 97 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@@ -264,7 +264,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 71 个智能体、311 项技能和 97 个命令了。
|
||||
**搞定!** 你现在可以使用 71 个智能体、312 项技能和 97 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@@ -1178,7 +1178,7 @@ opencode
|
||||
|---------|---------------|----------|--------|
|
||||
| 智能体 | PASS: 71 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 97 个 | PASS: 35 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 311 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 312 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@@ -1286,7 +1286,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|---------|-----------------------|------------|-----------|----------|
|
||||
| **智能体** | 71 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 97 | 共享 | 基于指令 | 35 |
|
||||
| **技能** | 311 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 312 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | SessionStart(1 种类型) | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | 1 个 SessionStart 引导脚本 | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"coverage": {
|
||||
"skills": 311,
|
||||
"skills": 312,
|
||||
"withTriggers": 33
|
||||
},
|
||||
"generatedAt": "2026-09-24T23:51:22.784Z",
|
||||
@@ -10,7 +10,7 @@
|
||||
"id": "hand-seeded",
|
||||
"source": "manual-curation-pending-regeneration"
|
||||
},
|
||||
"registryDigest": "846ac8826dd91e81cd838c2b55d7be536af427eb453efa127501f891e5778ff8",
|
||||
"registryDigest": "963baacb17709ebf0df08643b75e0a76ce0882978131564d72f0d08995f93c02",
|
||||
"schemaVersion": 1,
|
||||
"triggers": {
|
||||
"skill:agent-runtime-gateway-smoke-test": [
|
||||
|
||||
@@ -734,6 +734,14 @@
|
||||
"documentation-change-impact"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "capability:module-governance",
|
||||
"family": "capability",
|
||||
"description": "Optional module-local rules, domain context, and contract ownership workflow.",
|
||||
"modules": [
|
||||
"documentation-module-governance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "capability:documentation-test-regression",
|
||||
"family": "capability",
|
||||
|
||||
@@ -1389,6 +1389,35 @@
|
||||
"cost": "light",
|
||||
"stability": "beta"
|
||||
},
|
||||
{
|
||||
"id": "documentation-module-governance",
|
||||
"kind": "skills",
|
||||
"description": "Optional module-local instructions and documentation ownership workflow.",
|
||||
"paths": [
|
||||
"skills/module-governance"
|
||||
],
|
||||
"targets": [
|
||||
"claude",
|
||||
"claude-project",
|
||||
"cursor",
|
||||
"antigravity",
|
||||
"codex",
|
||||
"opencode",
|
||||
"codebuddy",
|
||||
"joycode",
|
||||
"qwen",
|
||||
"zed"
|
||||
],
|
||||
"dependencies": [
|
||||
"documentation-context",
|
||||
"documentation-governance",
|
||||
"documentation-change-impact",
|
||||
"framework-language"
|
||||
],
|
||||
"defaultInstall": false,
|
||||
"cost": "light",
|
||||
"stability": "beta"
|
||||
},
|
||||
{
|
||||
"id": "documentation-test-regression",
|
||||
"kind": "skills",
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
"documentation-context",
|
||||
"hermes-hooks",
|
||||
"documentation-change-impact",
|
||||
"documentation-module-governance",
|
||||
"documentation-test-regression"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
"living-docs-governance",
|
||||
"make-interfaces-feel-better",
|
||||
"mcp-server-patterns",
|
||||
"module-governance",
|
||||
"module-regression",
|
||||
"ml-adoption-playbook",
|
||||
"mle-workflow",
|
||||
|
||||
@@ -449,6 +449,7 @@
|
||||
"skills/marketing-campaign/",
|
||||
"skills/master-agreement-generator/",
|
||||
"skills/ml-adoption-playbook/",
|
||||
"skills/module-governance/",
|
||||
"skills/module-regression/",
|
||||
"skills/motion-advanced/",
|
||||
"skills/motion-foundations/",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Curation
|
||||
|
||||
pi/core includes 132 of 311 skills and 24 of 97 commands from the root of ECC.
|
||||
pi/core includes 133 of 312 skills and 24 of 97 commands from the root of ECC.
|
||||
Everything excluded is listed here with its reason.
|
||||
|
||||
## Rules
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# ecc-pi-core
|
||||
|
||||
A curated, Pi-native profile of ECC: 132 portable
|
||||
A curated, Pi-native profile of ECC: 133 portable
|
||||
engineering skills and 24 pure prompt-workflow commands, with no extensions,
|
||||
no hooks, no runtime downloads, and no network or SaaS dependencies.
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Matt Pocock
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -10,7 +10,7 @@ Govern “what these business terms mean” separately from “why this option w
|
||||
|
||||
## Domain Context Artifact: Domain Language Only
|
||||
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. Only when no equivalent exists should you lazily create the default `CONTEXT.md` from `templates/context.example.md`.
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. If an existing context map identifies separate business contexts, follow its links to the relevant glossary; ask when the topic's owner is unclear. Different contexts may legitimately use the same word differently. Do not create a glossary for every directory or impose one meaning across unrelated contexts. Only when no equivalent exists and a term has been resolved should you propose the default `CONTEXT.md` from `templates/context.example.md`. Obtain the user's explicit approval to create that new artifact before writing it, and stay within the authorized documentation scope. General permission to update documentation does not itself approve a new artifact; editing an existing mapped glossary within the authorized scope does not require a new-artifact approval.
|
||||
|
||||
Record only:
|
||||
|
||||
@@ -21,11 +21,23 @@ Record only:
|
||||
|
||||
Do not include implementation details, current state, task schedules, full requirements, decision rationale, or history. Keep those in code/MAP, STATUS/Issue Tracker, Spec, ADR, and LOG respectively. Terms must be traceable to code, contracts, or business evidence. Mark unconfirmed items as `needs confirmation`; do not guess.
|
||||
|
||||
## Refine Language During the Conversation
|
||||
|
||||
This is an active modeling workflow, not a requirement to rewrite the glossary whenever another skill reads it.
|
||||
|
||||
- **Challenge contradictions.** Compare the user's wording with the glossary. If “cancellation” already means cancelling an entire order but the request permits cancelling one line, explain that distinction and ask which meaning is intended; do not silently redefine the term.
|
||||
- **Separate overloaded concepts.** For “account,” establish whether the speaker means the customer organization, a person's login, or a billing relationship. Propose a precise term for each distinct concept. Record rejected synonyms only where they really denote the same concept in this context.
|
||||
- **Probe relationships with concrete scenarios.** Ask, for example, whether one customer can have multiple logins, or what happens to an invoice when only part of an order is cancelled. Scenarios expose boundaries; they are questions, not invented business rules.
|
||||
- **Check claims against evidence.** Inspect relevant code, tests, contracts, and existing records. If the implementation cancels only whole orders, surface the mismatch with the proposed partial-cancellation rule. Code demonstrates current behavior, not the business owner's desired behavior; do not decide which is correct by yourself.
|
||||
- **Capture confirmed terms as they settle.** Within an authorized documentation edit, update the existing glossary at that point instead of deferring all definitions until closeout. Keep definitions to one or two sentences, with a source and context-specific avoided synonyms where useful. Do not rewrite code, contracts, or accepted decisions merely to match a proposed term. In discussion-only or read-only work, present the proposed wording without writing files.
|
||||
|
||||
Use the project's existing glossary format. For a new document, the [context template](templates/context.example.md) provides a compact starting point; omit sections without real content. Include business-specific concepts, not generic programming vocabulary such as timeouts or utility functions.
|
||||
|
||||
## ADR Handoff: Reuse the Existing Capability
|
||||
|
||||
This skill does not define a second ADR directory, numbering scheme, lifecycle, or template. When a decision is hard to reverse, would confuse a future reader without context, and involves a real tradeoff:
|
||||
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location and format.
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location, format, and explicit approval gates for initialization and writing.
|
||||
2. If no convention exists, let that skill choose the minimum viable location; do not create a separate system here.
|
||||
3. Link to the ADR from `CONTEXT.md`, the project map, or the relevant Spec/Issue instead of copying the decision body.
|
||||
4. When a decision is accepted, superseded, or deprecated, append an event to project history according to the living-document rules.
|
||||
@@ -38,14 +50,16 @@ Routine implementation details, easily reversible changes, and short-lived exper
|
||||
- The project needs a stable domain-language entry point but has no equivalent document.
|
||||
- Context work uncovers a durable decision that should be handed to `architecture-decision-records`.
|
||||
|
||||
Merely consuming established vocabulary during an ordinary bug fix does not require a modeling session or new documentation.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when stable domain language needs one evidence-backed home, or when context work reveals a hard-to-reverse decision that belongs in the repository's existing ADR system.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available.
|
||||
2. Record precise domain definitions, relationships, confirmed interpretations, and explicitly unconfirmed ambiguities without mixing in implementation or task state.
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available and the user has explicitly approved creating that new artifact.
|
||||
2. Challenge conflicting language, probe concrete edge cases, and cross-check claims against code and contracts before recording confirmed definitions and relationships. Keep unresolved interpretations explicit without mixing in implementation or task state.
|
||||
3. Route durable tradeoffs to `architecture-decision-records` and link the resulting ADR instead of copying its rationale into context documents.
|
||||
4. Keep schedules, blockers, and execution status in the existing issue tracker.
|
||||
|
||||
@@ -60,6 +74,8 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
- Copying current tasks, schedules, full requirements, or implementation details into `CONTEXT.md`.
|
||||
- Creating empty context files or a second ADR system for completeness.
|
||||
- Treating unconfirmed domain interpretations as facts, or copying ADR bodies into maps and logs.
|
||||
- Renaming distinct business concepts as synonyms, or forcing separate contexts into one universal glossary.
|
||||
- Treating a glossary update as permission to change implementation, or treating code as proof of the intended business rule.
|
||||
|
||||
## Related Skills
|
||||
|
||||
@@ -70,3 +86,7 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
## Scheduling and Task Boundary
|
||||
|
||||
Keep tasks, status, blockers, and schedules in the project's existing tracker, such as GitHub Issues or Linear, as the single source of truth. Use local `.scratch/` only when no external tracker exists and the project explicitly adopts it. Do not put schedules in `CONTEXT.md`, ADRs, PROJECT_STATUS, or the history index database.
|
||||
|
||||
## Attribution
|
||||
|
||||
The active terminology-refinement workflow adapts Matt Pocock's MIT-licensed `domain-modeling` skill. See [source and adaptation boundaries](references/source.md). ECC's existing ADR workflow remains the sole decision-record owner.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Source and adaptation
|
||||
|
||||
The active terminology-refinement workflow and compact glossary guidance are adapted from [Matt Pocock's domain-modeling skill](https://github.com/mattpocock/skills/tree/c55ee46073ed923f86ce59a5eb3b6d895095d1b7/skills/engineering/domain-modeling), pinned at `c55ee46073ed923f86ce59a5eb3b6d895095d1b7`, under the [MIT license](../LICENSE).
|
||||
|
||||
This is an in-place ECC adaptation, not another skill or an external runtime dependency. It preserves ECC's mapped glossary selection, evidence and authorization boundaries, and handoff to `architecture-decision-records`. It does not import a second ADR template, directory, numbering scheme, or lifecycle. The existing ECC context template is extended rather than replaced with a second glossary format.
|
||||
@@ -1,12 +1,14 @@
|
||||
# CONTEXT.md — Domain Language
|
||||
|
||||
> Create only when needed. Do not include implementation, status, scheduling, or decisions.
|
||||
> Create only after the first term is resolved, no equivalent glossary exists, and the user has explicitly approved creating this new artifact within the authorized documentation scope. General documentation-update permission alone is not creation approval. Reuse an existing glossary and its format instead of creating a duplicate; authorized edits to that existing glossary do not need a new-artifact approval. Omit sections without content. Do not include implementation, status, scheduling, or decision rationale.
|
||||
|
||||
{One or two sentences identifying the business context this vocabulary describes.}
|
||||
|
||||
## Core Terms
|
||||
|
||||
| Term | Precise definition | Evidence/source |
|
||||
|---|---|---|
|
||||
| `{term}` | `{what it means and how it differs from adjacent terms}` | `{business rule, contract, or code location}` |
|
||||
| Term | Precise definition | Avoid in this context | Evidence/source |
|
||||
|---|---|---|---|
|
||||
| `{canonical term}` | `{one or two sentences defining the concept and distinguishing adjacent concepts}` | `{confirmed synonyms to avoid; not names for distinct concepts}` | `{confirmed business source, contract, or code location}` |
|
||||
|
||||
## Concept Relationships
|
||||
|
||||
|
||||
@@ -51,7 +51,30 @@ verification step. Resolve the selected skill directory first, then invoke:
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope full
|
||||
```
|
||||
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, and `full`.
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, `modules`, and `full`.
|
||||
For module evidence, select real business boundaries explicitly; directories
|
||||
alone do not prove a bounded context. Repeat `--module` for several boundaries:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope modules --module src/inventory --module src/billing
|
||||
```
|
||||
|
||||
`modules` requires a selected directory. `full` also accepts `--module`; other
|
||||
scopes reject it rather than silently skipping the requested checks. The module
|
||||
check finds local `AGENTS.md` and `CONTEXT.md`, or reports the nearest ancestor
|
||||
and mapped project sources as shared candidates. Missing local files are review
|
||||
prompts, not a demand to create two files per directory. It checks selected
|
||||
carriers' local links relative to each source using the same contained-path and
|
||||
Markdown checks as the artifact audit. It never infers harness loading from
|
||||
file presence.
|
||||
|
||||
After mechanical checks, inspect the actual module code, callers, contracts and
|
||||
tests: compare responsibilities and import/call boundaries with documented
|
||||
rules, and terminology with the canonical context source. Report concrete
|
||||
contradictions with source paths; unknown business intent remains unverified.
|
||||
Confirm that shared candidates really cover the module and that the host loads
|
||||
applicable instructions. Reuse existing contract and ADR owners rather than
|
||||
copying them into `AGENTS.md`. Passing links is not passing this semantic review.
|
||||
|
||||
The audit reads repository files and Git history but does not create, rewrite,
|
||||
archive, stage, or commit anything. It reports:
|
||||
|
||||
@@ -66,6 +66,14 @@ The audit does not determine whether prose is true, whether a requirement is
|
||||
satisfied, whether a task is complete, or whether a change should be merged.
|
||||
Warnings explicitly require semantic review.
|
||||
|
||||
Module auditing is opt-in through explicit `--module` directories with scope
|
||||
`modules` or `full`. Local `AGENTS.md` and `CONTEXT.md` are conventional evidence
|
||||
locations, not mandatory per-directory artifacts. Ancestor and mapped sources
|
||||
are shared candidates whose applicability must be confirmed manually; the audit
|
||||
does not equate a file's existence with instruction loading or business coverage.
|
||||
Module responsibilities, code/contract boundaries and vocabulary require source
|
||||
and test evidence after the contained-path and source-relative link checks.
|
||||
|
||||
For link checks, any `scheme://` destination is external. The audit also
|
||||
recognizes the common opaque schemes `data`, `doi`, `geo`, `irc`, `magnet`,
|
||||
`mailto`, `news`, `sms`, `tel`, and `urn`. Other colon-shaped destinations are
|
||||
|
||||
@@ -16,6 +16,7 @@ from markdown_links import (
|
||||
normalize_link_target,
|
||||
without_fenced_code,
|
||||
)
|
||||
from module_audit import check_modules
|
||||
|
||||
DEFAULT_ROLES = {
|
||||
"constitution": "CLAUDE.md",
|
||||
@@ -242,10 +243,12 @@ def resolve_target(root: Path, source: Path, target: str) -> Path | None:
|
||||
return resolve_within_root(root, source.parent, target)
|
||||
|
||||
|
||||
def check_markdown_links(root: Path, files: list[Path], report: Report) -> None:
|
||||
def check_markdown_links(
|
||||
root: Path, files: list[Path], report: Report, *, skip_templates: bool = True
|
||||
) -> None:
|
||||
broken: list[str] = []
|
||||
for source in files:
|
||||
if "templates" in source.relative_to(root).parts:
|
||||
if skip_templates and "templates" in source.relative_to(root).parts:
|
||||
continue
|
||||
text = source.read_text(encoding="utf-8")
|
||||
for raw in markdown_link_targets(text):
|
||||
@@ -716,11 +719,19 @@ def main() -> int:
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument(
|
||||
"--scope",
|
||||
choices=("spine", "context", "adr", "artifacts", "full"),
|
||||
choices=("spine", "context", "adr", "artifacts", "modules", "full"),
|
||||
default="full",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--module", action="append", default=[],
|
||||
help="Repository-relative module directory; repeat for several modules",
|
||||
)
|
||||
parser.add_argument("--log-threshold", type=int, default=200)
|
||||
args = parser.parse_args()
|
||||
if args.scope == "modules" and not args.module:
|
||||
parser.error("--scope modules requires at least one --module directory")
|
||||
if args.module and args.scope not in ("modules", "full"):
|
||||
parser.error("--module is supported only with --scope modules or full")
|
||||
|
||||
root = args.root.resolve()
|
||||
if not root.is_dir():
|
||||
@@ -728,6 +739,11 @@ def main() -> int:
|
||||
return 2
|
||||
report = Report()
|
||||
roles = load_roles(root, report)
|
||||
if args.module:
|
||||
check_modules(
|
||||
root, roles, args.module, report,
|
||||
resolved_within_root, read_utf8, check_markdown_links,
|
||||
)
|
||||
if args.scope in ("spine", "full"):
|
||||
check_spine(root, roles, report, args.log_threshold)
|
||||
if args.scope in ("context", "full"):
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Read-only evidence discovery for explicitly selected business modules."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
|
||||
class AuditReport(Protocol):
|
||||
def section(self, title: str) -> None: ...
|
||||
def ok(self, message: str) -> None: ...
|
||||
def warn(self, message: str) -> None: ...
|
||||
def fail(self, message: str) -> None: ...
|
||||
|
||||
|
||||
ReportT = TypeVar("ReportT", bound=AuditReport, contravariant=True)
|
||||
|
||||
|
||||
class LinkChecker(Protocol[ReportT]):
|
||||
def __call__(
|
||||
self, root: Path, files: list[Path], report: ReportT,
|
||||
*, skip_templates: bool = True,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
def carrier_candidates(root: Path, module: Path, name: str, mapped: str) -> list[Path]:
|
||||
directories = [module, *module.parents]
|
||||
directories = [root] if module == root else directories[:directories.index(root) + 1]
|
||||
candidates = [directory / name for directory in directories] + [root / mapped]
|
||||
return list(dict.fromkeys(candidates))
|
||||
|
||||
|
||||
def check_modules(
|
||||
root: Path,
|
||||
roles: dict[str, str],
|
||||
names: list[str],
|
||||
report: ReportT,
|
||||
resolve_source: Callable[[Path, Path], Path | None],
|
||||
read_source: Callable[[Path, ReportT, str], str | None],
|
||||
check_links: LinkChecker[ReportT],
|
||||
) -> None:
|
||||
"""Use the audit's existing containment, UTF-8 and Markdown checks unchanged."""
|
||||
report.section("modules")
|
||||
sources: set[Path] = set()
|
||||
for name in sorted(set(names)):
|
||||
requested = Path(name)
|
||||
module = resolve_source(root, root / requested)
|
||||
if requested.is_absolute() or module is None or not module.is_dir():
|
||||
report.fail(
|
||||
f"Module must be a repository-relative directory inside root: {name!r}"
|
||||
)
|
||||
continue
|
||||
report.ok(f"Selected module: {str(module.relative_to(root))!r}")
|
||||
for role, filename in (("agents", "AGENTS.md"), ("context", "CONTEXT.md")):
|
||||
found = False
|
||||
for candidate in carrier_candidates(root, module, filename, roles[role]):
|
||||
if not candidate.exists() and not candidate.is_symlink():
|
||||
continue
|
||||
resolved = resolve_source(root, candidate)
|
||||
if resolved is None or not resolved.is_file():
|
||||
report.fail(
|
||||
f"Module {role} source must be a repository-contained regular file: "
|
||||
f"{str(candidate.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if read_source(resolved, report, f"module {role} source") is None:
|
||||
found = True
|
||||
break
|
||||
sources.add(resolved)
|
||||
source_label = str(candidate.relative_to(root))
|
||||
if candidate.parent == module:
|
||||
report.ok(f"Local {role} source: {source_label!r}")
|
||||
else:
|
||||
report.warn(
|
||||
f"Shared {role} candidate: {source_label!r}; "
|
||||
f"confirm it covers module {str(module.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
report.warn(
|
||||
f"No {role} source found for module {str(module.relative_to(root))!r}; "
|
||||
"review existing rules or glossary before creating a file"
|
||||
)
|
||||
check_links(root, sorted(sources), report, skip_templates=False)
|
||||
report.warn(
|
||||
"Module responsibilities, instruction loading, terminology and code/contract "
|
||||
"consistency require semantic review; file presence is not proof of coverage"
|
||||
)
|
||||
@@ -97,6 +97,16 @@ History is append-oriented for traceability, but not immutable at the expense of
|
||||
- preserve a short sanitized note explaining the correction when safe;
|
||||
- do not silently rewrite a decision to make the past look cleaner.
|
||||
|
||||
### Module-local maintenance
|
||||
|
||||
For an authorized new business module or a confirmed responsibility change,
|
||||
use optional `module-governance`, when installed, to connect local harness instructions to the existing
|
||||
context, contract, and decision owners. Keep global rules at project scope;
|
||||
update only affected navigation and lifecycle records here. Ordinary fixes
|
||||
with unchanged boundaries do not require a new local AGENTS/CONTEXT pair.
|
||||
If unavailable, report the optional capability rather than installing it or
|
||||
copying its workflow into project instructions.
|
||||
|
||||
## Lightweight Adoption Template
|
||||
|
||||
Start with a role map, not four new files:
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: module-governance
|
||||
description: >-
|
||||
Establish or maintain a business module's local agent instructions, domain-context links, and interface ownership when implementing a new module or changing its responsibilities. Reuse project-level rules and existing ECC workflows; ordinary bug fixes, styling, and read-only design discussions do not start module documentation setup.
|
||||
metadata:
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Module Governance
|
||||
|
||||
Connect module-level working rules to existing domain language, contracts, and
|
||||
decisions. This is an orchestration skill, not a second DDD methodology or an
|
||||
automatic documentation generator.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- The user asks to implement a new business module, without needing to name this skill.
|
||||
- The user asks to document an existing module or maintain a confirmed responsibility change.
|
||||
- An authorized module change needs local instructions and canonical knowledge pointers.
|
||||
|
||||
Do not start setup for an ordinary bug, styling change, internal refactor with
|
||||
unchanged boundaries, or a new directory/UI component that simply inherits
|
||||
parent conventions. For design discussion or an audit alone, inspect and report
|
||||
without creating files. Intent-based selection is not a background hook and
|
||||
does not grant permission for unrelated code, commits, pushes, or business choices.
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Discover the boundary before creating documents
|
||||
|
||||
Read applicable project and ancestor harness instructions, the existing map,
|
||||
requirements, relevant code, and contracts. Identify the module's responsibilities,
|
||||
non-responsibilities, state ownership, and actual consumers. Directory names alone
|
||||
do not establish a bounded context. Surface material ambiguities instead of
|
||||
inventing business rules.
|
||||
|
||||
For an existing boundary change, use `change-impact` first. Its read-only and
|
||||
authorization constraints still apply; do not run an unrelated development workflow.
|
||||
|
||||
### 2. Separate project-wide rules from local rules
|
||||
|
||||
The project-level instruction source owns global constraints, module navigation,
|
||||
and cross-module collaboration. A local `AGENTS.md` owns only additional module
|
||||
constraints: responsibilities, state ownership, public-interface pointers, and
|
||||
verification entry points. Local rules cannot waive project-wide constraints.
|
||||
|
||||
Reuse an established `CLAUDE.md` or other active harness surface instead of
|
||||
forcing a rename or maintaining two copies. If parent instructions already
|
||||
cover the module and no local rule is missing, link that source and do not create
|
||||
a redundant local file. Follow an existing explicit local-entrypoint requirement.
|
||||
Do not claim that a harness automatically loads every nested instruction file.
|
||||
|
||||
### 3. Reuse the existing knowledge owners
|
||||
|
||||
| Information | Existing ECC owner |
|
||||
|---|---|
|
||||
| Stable terminology and concept relationships | `context-and-decisions`, using the existing glossary or context role |
|
||||
| Hard-to-reverse decisions and rationale | `architecture-decision-records`, through the existing context handoff |
|
||||
| Cross-boundary machine-checkable interfaces | `contract-first` |
|
||||
| Documentation navigation and lifecycle | `living-docs-governance` |
|
||||
| Affected consumers and boundary-change evidence | `change-impact` |
|
||||
|
||||
Load the applicable skill when its work is needed; do not duplicate its method
|
||||
here. Keep terminology in the relevant existing `CONTEXT.md` or equivalent,
|
||||
not in AGENTS. Frontend and backend in the same business context can share that
|
||||
glossary; UI-specific working rules may be local without creating a glossary per
|
||||
page. Reuse existing contracts and ADRs by reference, not by copying their bodies.
|
||||
|
||||
### 4. Maintain only the authorized records
|
||||
|
||||
Within the user's requested module work, update the smallest missing local rules
|
||||
and confirmed context entries. Use `living-docs-governance` for affected navigation
|
||||
and lifecycle updates, retaining its approval requirement for new top-level
|
||||
artifacts. Do not create empty files to meet a two-file quota. When implementation
|
||||
does not yet exist, label the boundary as confirmed design with code unverified;
|
||||
do not declare documentation/code consistency from prose alone.
|
||||
|
||||
### 5. Verify both meaning and links
|
||||
|
||||
Compare each claimed owner, interface, and constraint with code, canonical
|
||||
contracts, and executable evidence. Report contradictions for owner resolution;
|
||||
do not rewrite the rules merely to match an accidental implementation.
|
||||
|
||||
Resolve relative links from **each referencing document's directory**, not the
|
||||
terminal working directory. Resolve the installed `docs-governance` skill path
|
||||
and run its existing read-only link check as an agreed verification step:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope artifacts
|
||||
```
|
||||
|
||||
Confirm that the audit scanned the changed local instructions, context, and
|
||||
navigation; check its link results and exit code. Fix broken references and rerun,
|
||||
without creating empty targets. If the script is unavailable or excludes a target,
|
||||
report that target as unverified. A passing business test does not prove links are
|
||||
correct, and an existing same-named file may still be the wrong knowledge owner.
|
||||
|
||||
## Examples
|
||||
|
||||
- **“Implement an inventory module.”** Inspect existing boundaries; add local
|
||||
rules only for genuinely new constraints; reuse the domain glossary and API
|
||||
contract; link them from existing navigation; verify code and source-relative links.
|
||||
- **“Should payments become a separate module?”** Explain boundary options and
|
||||
evidence without writing files or treating the discussion as implementation approval.
|
||||
- **“Fix the inventory badge color.”** Read applicable instructions and fix the
|
||||
requested UI behavior; do not generate AGENTS, CONTEXT, or ADR documents.
|
||||
- **“Audit module documentation.”** Use the existing read-only audit and semantic
|
||||
review; a missing local file is not by itself permission to create one.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- A mandatory AGENTS/CONTEXT pair for every directory or frontend page.
|
||||
- Duplicating global constraints, glossary definitions, contracts, or ADR rationale.
|
||||
- Treating documentation checks as proof of completed business implementation.
|
||||
- Installing hooks or asserting automatic nested-file loading without real harness wiring.
|
||||
|
||||
## Delivery
|
||||
|
||||
Report canonical record paths, their project/module scope, actual changes,
|
||||
verification evidence, and unresolved ownership questions. Module governance
|
||||
delivers working rules and knowledge entry points, not a complete DDD conversion.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `context-and-decisions`
|
||||
- `architecture-decision-records`
|
||||
- `contract-first`
|
||||
- `change-impact`
|
||||
- `living-docs-governance`
|
||||
- `docs-governance`
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Matt Pocock
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -10,7 +10,7 @@ Govern “what these business terms mean” separately from “why this option w
|
||||
|
||||
## Domain Context Artifact: Domain Language Only
|
||||
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. Only when no equivalent exists should you lazily create the default `CONTEXT.md` from `templates/context.example.md`.
|
||||
When terms require repeated explanation, one term has multiple meanings, or unclear language causes module-boundary errors, first reuse the repository's existing glossary/domain-model document or the `context` role in `.governance/docs-map.json`. If an existing context map identifies separate business contexts, follow its links to the relevant glossary; ask when the topic's owner is unclear. Different contexts may legitimately use the same word differently. Do not create a glossary for every directory or impose one meaning across unrelated contexts. Only when no equivalent exists and a term has been resolved should you propose the default `CONTEXT.md` from `templates/context.example.md`. Obtain the user's explicit approval to create that new artifact before writing it, and stay within the authorized documentation scope. General permission to update documentation does not itself approve a new artifact; editing an existing mapped glossary within the authorized scope does not require a new-artifact approval.
|
||||
|
||||
Record only:
|
||||
|
||||
@@ -21,11 +21,23 @@ Record only:
|
||||
|
||||
Do not include implementation details, current state, task schedules, full requirements, decision rationale, or history. Keep those in code/MAP, STATUS/Issue Tracker, Spec, ADR, and LOG respectively. Terms must be traceable to code, contracts, or business evidence. Mark unconfirmed items as `needs confirmation`; do not guess.
|
||||
|
||||
## Refine Language During the Conversation
|
||||
|
||||
This is an active modeling workflow, not a requirement to rewrite the glossary whenever another skill reads it.
|
||||
|
||||
- **Challenge contradictions.** Compare the user's wording with the glossary. If “cancellation” already means cancelling an entire order but the request permits cancelling one line, explain that distinction and ask which meaning is intended; do not silently redefine the term.
|
||||
- **Separate overloaded concepts.** For “account,” establish whether the speaker means the customer organization, a person's login, or a billing relationship. Propose a precise term for each distinct concept. Record rejected synonyms only where they really denote the same concept in this context.
|
||||
- **Probe relationships with concrete scenarios.** Ask, for example, whether one customer can have multiple logins, or what happens to an invoice when only part of an order is cancelled. Scenarios expose boundaries; they are questions, not invented business rules.
|
||||
- **Check claims against evidence.** Inspect relevant code, tests, contracts, and existing records. If the implementation cancels only whole orders, surface the mismatch with the proposed partial-cancellation rule. Code demonstrates current behavior, not the business owner's desired behavior; do not decide which is correct by yourself.
|
||||
- **Capture confirmed terms as they settle.** Within an authorized documentation edit, update the existing glossary at that point instead of deferring all definitions until closeout. Keep definitions to one or two sentences, with a source and context-specific avoided synonyms where useful. Do not rewrite code, contracts, or accepted decisions merely to match a proposed term. In discussion-only or read-only work, present the proposed wording without writing files.
|
||||
|
||||
Use the project's existing glossary format. For a new document, the [context template](templates/context.example.md) provides a compact starting point; omit sections without real content. Include business-specific concepts, not generic programming vocabulary such as timeouts or utility functions.
|
||||
|
||||
## ADR Handoff: Reuse the Existing Capability
|
||||
|
||||
This skill does not define a second ADR directory, numbering scheme, lifecycle, or template. When a decision is hard to reverse, would confuse a future reader without context, and involves a real tradeoff:
|
||||
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location and format.
|
||||
1. Read and execute `architecture-decision-records`, preserving the repository's existing ADR location, format, and explicit approval gates for initialization and writing.
|
||||
2. If no convention exists, let that skill choose the minimum viable location; do not create a separate system here.
|
||||
3. Link to the ADR from `CONTEXT.md`, the project map, or the relevant Spec/Issue instead of copying the decision body.
|
||||
4. When a decision is accepted, superseded, or deprecated, append an event to project history according to the living-document rules.
|
||||
@@ -38,14 +50,16 @@ Routine implementation details, easily reversible changes, and short-lived exper
|
||||
- The project needs a stable domain-language entry point but has no equivalent document.
|
||||
- Context work uncovers a durable decision that should be handed to `architecture-decision-records`.
|
||||
|
||||
Merely consuming established vocabulary during an ordinary bug fix does not require a modeling session or new documentation.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when stable domain language needs one evidence-backed home, or when context work reveals a hard-to-reverse decision that belongs in the repository's existing ADR system.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available.
|
||||
2. Record precise domain definitions, relationships, confirmed interpretations, and explicitly unconfirmed ambiguities without mixing in implementation or task state.
|
||||
1. Reuse the mapped context or glossary artifact when one exists; create `CONTEXT.md` only when no equivalent source of truth is available and the user has explicitly approved creating that new artifact.
|
||||
2. Challenge conflicting language, probe concrete edge cases, and cross-check claims against code and contracts before recording confirmed definitions and relationships. Keep unresolved interpretations explicit without mixing in implementation or task state.
|
||||
3. Route durable tradeoffs to `architecture-decision-records` and link the resulting ADR instead of copying its rationale into context documents.
|
||||
4. Keep schedules, blockers, and execution status in the existing issue tracker.
|
||||
|
||||
@@ -60,6 +74,8 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
- Copying current tasks, schedules, full requirements, or implementation details into `CONTEXT.md`.
|
||||
- Creating empty context files or a second ADR system for completeness.
|
||||
- Treating unconfirmed domain interpretations as facts, or copying ADR bodies into maps and logs.
|
||||
- Renaming distinct business concepts as synonyms, or forcing separate contexts into one universal glossary.
|
||||
- Treating a glossary update as permission to change implementation, or treating code as proof of the intended business rule.
|
||||
|
||||
## Related Skills
|
||||
|
||||
@@ -70,3 +86,7 @@ Use this skill when stable domain language needs one evidence-backed home, or wh
|
||||
## Scheduling and Task Boundary
|
||||
|
||||
Keep tasks, status, blockers, and schedules in the project's existing tracker, such as GitHub Issues or Linear, as the single source of truth. Use local `.scratch/` only when no external tracker exists and the project explicitly adopts it. Do not put schedules in `CONTEXT.md`, ADRs, PROJECT_STATUS, or the history index database.
|
||||
|
||||
## Attribution
|
||||
|
||||
The active terminology-refinement workflow adapts Matt Pocock's MIT-licensed `domain-modeling` skill. See [source and adaptation boundaries](references/source.md). ECC's existing ADR workflow remains the sole decision-record owner.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Source and adaptation
|
||||
|
||||
The active terminology-refinement workflow and compact glossary guidance are adapted from [Matt Pocock's domain-modeling skill](https://github.com/mattpocock/skills/tree/c55ee46073ed923f86ce59a5eb3b6d895095d1b7/skills/engineering/domain-modeling), pinned at `c55ee46073ed923f86ce59a5eb3b6d895095d1b7`, under the [MIT license](../LICENSE).
|
||||
|
||||
This is an in-place ECC adaptation, not another skill or an external runtime dependency. It preserves ECC's mapped glossary selection, evidence and authorization boundaries, and handoff to `architecture-decision-records`. It does not import a second ADR template, directory, numbering scheme, or lifecycle. The existing ECC context template is extended rather than replaced with a second glossary format.
|
||||
@@ -1,12 +1,14 @@
|
||||
# CONTEXT.md — Domain Language
|
||||
|
||||
> Create only when needed. Do not include implementation, status, scheduling, or decisions.
|
||||
> Create only after the first term is resolved, no equivalent glossary exists, and the user has explicitly approved creating this new artifact within the authorized documentation scope. General documentation-update permission alone is not creation approval. Reuse an existing glossary and its format instead of creating a duplicate; authorized edits to that existing glossary do not need a new-artifact approval. Omit sections without content. Do not include implementation, status, scheduling, or decision rationale.
|
||||
|
||||
{One or two sentences identifying the business context this vocabulary describes.}
|
||||
|
||||
## Core Terms
|
||||
|
||||
| Term | Precise definition | Evidence/source |
|
||||
|---|---|---|
|
||||
| `{term}` | `{what it means and how it differs from adjacent terms}` | `{business rule, contract, or code location}` |
|
||||
| Term | Precise definition | Avoid in this context | Evidence/source |
|
||||
|---|---|---|---|
|
||||
| `{canonical term}` | `{one or two sentences defining the concept and distinguishing adjacent concepts}` | `{confirmed synonyms to avoid; not names for distinct concepts}` | `{confirmed business source, contract, or code location}` |
|
||||
|
||||
## Concept Relationships
|
||||
|
||||
|
||||
@@ -51,7 +51,30 @@ verification step. Resolve the selected skill directory first, then invoke:
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope full
|
||||
```
|
||||
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, and `full`.
|
||||
Available scopes are `spine`, `context`, `adr`, `artifacts`, `modules`, and `full`.
|
||||
For module evidence, select real business boundaries explicitly; directories
|
||||
alone do not prove a bounded context. Repeat `--module` for several boundaries:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope modules --module src/inventory --module src/billing
|
||||
```
|
||||
|
||||
`modules` requires a selected directory. `full` also accepts `--module`; other
|
||||
scopes reject it rather than silently skipping the requested checks. The module
|
||||
check finds local `AGENTS.md` and `CONTEXT.md`, or reports the nearest ancestor
|
||||
and mapped project sources as shared candidates. Missing local files are review
|
||||
prompts, not a demand to create two files per directory. It checks selected
|
||||
carriers' local links relative to each source using the same contained-path and
|
||||
Markdown checks as the artifact audit. It never infers harness loading from
|
||||
file presence.
|
||||
|
||||
After mechanical checks, inspect the actual module code, callers, contracts and
|
||||
tests: compare responsibilities and import/call boundaries with documented
|
||||
rules, and terminology with the canonical context source. Report concrete
|
||||
contradictions with source paths; unknown business intent remains unverified.
|
||||
Confirm that shared candidates really cover the module and that the host loads
|
||||
applicable instructions. Reuse existing contract and ADR owners rather than
|
||||
copying them into `AGENTS.md`. Passing links is not passing this semantic review.
|
||||
|
||||
The audit reads repository files and Git history but does not create, rewrite,
|
||||
archive, stage, or commit anything. It reports:
|
||||
|
||||
@@ -66,6 +66,14 @@ The audit does not determine whether prose is true, whether a requirement is
|
||||
satisfied, whether a task is complete, or whether a change should be merged.
|
||||
Warnings explicitly require semantic review.
|
||||
|
||||
Module auditing is opt-in through explicit `--module` directories with scope
|
||||
`modules` or `full`. Local `AGENTS.md` and `CONTEXT.md` are conventional evidence
|
||||
locations, not mandatory per-directory artifacts. Ancestor and mapped sources
|
||||
are shared candidates whose applicability must be confirmed manually; the audit
|
||||
does not equate a file's existence with instruction loading or business coverage.
|
||||
Module responsibilities, code/contract boundaries and vocabulary require source
|
||||
and test evidence after the contained-path and source-relative link checks.
|
||||
|
||||
For link checks, any `scheme://` destination is external. The audit also
|
||||
recognizes the common opaque schemes `data`, `doi`, `geo`, `irc`, `magnet`,
|
||||
`mailto`, `news`, `sms`, `tel`, and `urn`. Other colon-shaped destinations are
|
||||
|
||||
@@ -16,6 +16,7 @@ from markdown_links import (
|
||||
normalize_link_target,
|
||||
without_fenced_code,
|
||||
)
|
||||
from module_audit import check_modules
|
||||
|
||||
DEFAULT_ROLES = {
|
||||
"constitution": "CLAUDE.md",
|
||||
@@ -242,10 +243,12 @@ def resolve_target(root: Path, source: Path, target: str) -> Path | None:
|
||||
return resolve_within_root(root, source.parent, target)
|
||||
|
||||
|
||||
def check_markdown_links(root: Path, files: list[Path], report: Report) -> None:
|
||||
def check_markdown_links(
|
||||
root: Path, files: list[Path], report: Report, *, skip_templates: bool = True
|
||||
) -> None:
|
||||
broken: list[str] = []
|
||||
for source in files:
|
||||
if "templates" in source.relative_to(root).parts:
|
||||
if skip_templates and "templates" in source.relative_to(root).parts:
|
||||
continue
|
||||
text = source.read_text(encoding="utf-8")
|
||||
for raw in markdown_link_targets(text):
|
||||
@@ -716,11 +719,19 @@ def main() -> int:
|
||||
parser.add_argument("--root", type=Path, default=Path.cwd())
|
||||
parser.add_argument(
|
||||
"--scope",
|
||||
choices=("spine", "context", "adr", "artifacts", "full"),
|
||||
choices=("spine", "context", "adr", "artifacts", "modules", "full"),
|
||||
default="full",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--module", action="append", default=[],
|
||||
help="Repository-relative module directory; repeat for several modules",
|
||||
)
|
||||
parser.add_argument("--log-threshold", type=int, default=200)
|
||||
args = parser.parse_args()
|
||||
if args.scope == "modules" and not args.module:
|
||||
parser.error("--scope modules requires at least one --module directory")
|
||||
if args.module and args.scope not in ("modules", "full"):
|
||||
parser.error("--module is supported only with --scope modules or full")
|
||||
|
||||
root = args.root.resolve()
|
||||
if not root.is_dir():
|
||||
@@ -728,6 +739,11 @@ def main() -> int:
|
||||
return 2
|
||||
report = Report()
|
||||
roles = load_roles(root, report)
|
||||
if args.module:
|
||||
check_modules(
|
||||
root, roles, args.module, report,
|
||||
resolved_within_root, read_utf8, check_markdown_links,
|
||||
)
|
||||
if args.scope in ("spine", "full"):
|
||||
check_spine(root, roles, report, args.log_threshold)
|
||||
if args.scope in ("context", "full"):
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Read-only evidence discovery for explicitly selected business modules."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
|
||||
class AuditReport(Protocol):
|
||||
def section(self, title: str) -> None: ...
|
||||
def ok(self, message: str) -> None: ...
|
||||
def warn(self, message: str) -> None: ...
|
||||
def fail(self, message: str) -> None: ...
|
||||
|
||||
|
||||
ReportT = TypeVar("ReportT", bound=AuditReport, contravariant=True)
|
||||
|
||||
|
||||
class LinkChecker(Protocol[ReportT]):
|
||||
def __call__(
|
||||
self, root: Path, files: list[Path], report: ReportT,
|
||||
*, skip_templates: bool = True,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
def carrier_candidates(root: Path, module: Path, name: str, mapped: str) -> list[Path]:
|
||||
directories = [module, *module.parents]
|
||||
directories = [root] if module == root else directories[:directories.index(root) + 1]
|
||||
candidates = [directory / name for directory in directories] + [root / mapped]
|
||||
return list(dict.fromkeys(candidates))
|
||||
|
||||
|
||||
def check_modules(
|
||||
root: Path,
|
||||
roles: dict[str, str],
|
||||
names: list[str],
|
||||
report: ReportT,
|
||||
resolve_source: Callable[[Path, Path], Path | None],
|
||||
read_source: Callable[[Path, ReportT, str], str | None],
|
||||
check_links: LinkChecker[ReportT],
|
||||
) -> None:
|
||||
"""Use the audit's existing containment, UTF-8 and Markdown checks unchanged."""
|
||||
report.section("modules")
|
||||
sources: set[Path] = set()
|
||||
for name in sorted(set(names)):
|
||||
requested = Path(name)
|
||||
module = resolve_source(root, root / requested)
|
||||
if requested.is_absolute() or module is None or not module.is_dir():
|
||||
report.fail(
|
||||
f"Module must be a repository-relative directory inside root: {name!r}"
|
||||
)
|
||||
continue
|
||||
report.ok(f"Selected module: {str(module.relative_to(root))!r}")
|
||||
for role, filename in (("agents", "AGENTS.md"), ("context", "CONTEXT.md")):
|
||||
found = False
|
||||
for candidate in carrier_candidates(root, module, filename, roles[role]):
|
||||
if not candidate.exists() and not candidate.is_symlink():
|
||||
continue
|
||||
resolved = resolve_source(root, candidate)
|
||||
if resolved is None or not resolved.is_file():
|
||||
report.fail(
|
||||
f"Module {role} source must be a repository-contained regular file: "
|
||||
f"{str(candidate.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if read_source(resolved, report, f"module {role} source") is None:
|
||||
found = True
|
||||
break
|
||||
sources.add(resolved)
|
||||
source_label = str(candidate.relative_to(root))
|
||||
if candidate.parent == module:
|
||||
report.ok(f"Local {role} source: {source_label!r}")
|
||||
else:
|
||||
report.warn(
|
||||
f"Shared {role} candidate: {source_label!r}; "
|
||||
f"confirm it covers module {str(module.relative_to(root))!r}"
|
||||
)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
report.warn(
|
||||
f"No {role} source found for module {str(module.relative_to(root))!r}; "
|
||||
"review existing rules or glossary before creating a file"
|
||||
)
|
||||
check_links(root, sorted(sources), report, skip_templates=False)
|
||||
report.warn(
|
||||
"Module responsibilities, instruction loading, terminology and code/contract "
|
||||
"consistency require semantic review; file presence is not proof of coverage"
|
||||
)
|
||||
@@ -97,6 +97,16 @@ History is append-oriented for traceability, but not immutable at the expense of
|
||||
- preserve a short sanitized note explaining the correction when safe;
|
||||
- do not silently rewrite a decision to make the past look cleaner.
|
||||
|
||||
### Module-local maintenance
|
||||
|
||||
For an authorized new business module or a confirmed responsibility change,
|
||||
use optional `module-governance`, when installed, to connect local harness instructions to the existing
|
||||
context, contract, and decision owners. Keep global rules at project scope;
|
||||
update only affected navigation and lifecycle records here. Ordinary fixes
|
||||
with unchanged boundaries do not require a new local AGENTS/CONTEXT pair.
|
||||
If unavailable, report the optional capability rather than installing it or
|
||||
copying its workflow into project instructions.
|
||||
|
||||
## Lightweight Adoption Template
|
||||
|
||||
Start with a role map, not four new files:
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: module-governance
|
||||
description: >-
|
||||
Establish or maintain a business module's local agent instructions, domain-context links, and interface ownership when implementing a new module or changing its responsibilities. Reuse project-level rules and existing ECC workflows; ordinary bug fixes, styling, and read-only design discussions do not start module documentation setup.
|
||||
metadata:
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Module Governance
|
||||
|
||||
Connect module-level working rules to existing domain language, contracts, and
|
||||
decisions. This is an orchestration skill, not a second DDD methodology or an
|
||||
automatic documentation generator.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- The user asks to implement a new business module, without needing to name this skill.
|
||||
- The user asks to document an existing module or maintain a confirmed responsibility change.
|
||||
- An authorized module change needs local instructions and canonical knowledge pointers.
|
||||
|
||||
Do not start setup for an ordinary bug, styling change, internal refactor with
|
||||
unchanged boundaries, or a new directory/UI component that simply inherits
|
||||
parent conventions. For design discussion or an audit alone, inspect and report
|
||||
without creating files. Intent-based selection is not a background hook and
|
||||
does not grant permission for unrelated code, commits, pushes, or business choices.
|
||||
|
||||
## How It Works
|
||||
|
||||
### 1. Discover the boundary before creating documents
|
||||
|
||||
Read applicable project and ancestor harness instructions, the existing map,
|
||||
requirements, relevant code, and contracts. Identify the module's responsibilities,
|
||||
non-responsibilities, state ownership, and actual consumers. Directory names alone
|
||||
do not establish a bounded context. Surface material ambiguities instead of
|
||||
inventing business rules.
|
||||
|
||||
For an existing boundary change, use `change-impact` first. Its read-only and
|
||||
authorization constraints still apply; do not run an unrelated development workflow.
|
||||
|
||||
### 2. Separate project-wide rules from local rules
|
||||
|
||||
The project-level instruction source owns global constraints, module navigation,
|
||||
and cross-module collaboration. A local `AGENTS.md` owns only additional module
|
||||
constraints: responsibilities, state ownership, public-interface pointers, and
|
||||
verification entry points. Local rules cannot waive project-wide constraints.
|
||||
|
||||
Reuse an established `CLAUDE.md` or other active harness surface instead of
|
||||
forcing a rename or maintaining two copies. If parent instructions already
|
||||
cover the module and no local rule is missing, link that source and do not create
|
||||
a redundant local file. Follow an existing explicit local-entrypoint requirement.
|
||||
Do not claim that a harness automatically loads every nested instruction file.
|
||||
|
||||
### 3. Reuse the existing knowledge owners
|
||||
|
||||
| Information | Existing ECC owner |
|
||||
|---|---|
|
||||
| Stable terminology and concept relationships | `context-and-decisions`, using the existing glossary or context role |
|
||||
| Hard-to-reverse decisions and rationale | `architecture-decision-records`, through the existing context handoff |
|
||||
| Cross-boundary machine-checkable interfaces | `contract-first` |
|
||||
| Documentation navigation and lifecycle | `living-docs-governance` |
|
||||
| Affected consumers and boundary-change evidence | `change-impact` |
|
||||
|
||||
Load the applicable skill when its work is needed; do not duplicate its method
|
||||
here. Keep terminology in the relevant existing `CONTEXT.md` or equivalent,
|
||||
not in AGENTS. Frontend and backend in the same business context can share that
|
||||
glossary; UI-specific working rules may be local without creating a glossary per
|
||||
page. Reuse existing contracts and ADRs by reference, not by copying their bodies.
|
||||
|
||||
### 4. Maintain only the authorized records
|
||||
|
||||
Within the user's requested module work, update the smallest missing local rules
|
||||
and confirmed context entries. Use `living-docs-governance` for affected navigation
|
||||
and lifecycle updates, retaining its approval requirement for new top-level
|
||||
artifacts. Do not create empty files to meet a two-file quota. When implementation
|
||||
does not yet exist, label the boundary as confirmed design with code unverified;
|
||||
do not declare documentation/code consistency from prose alone.
|
||||
|
||||
### 5. Verify both meaning and links
|
||||
|
||||
Compare each claimed owner, interface, and constraint with code, canonical
|
||||
contracts, and executable evidence. Report contradictions for owner resolution;
|
||||
do not rewrite the rules merely to match an accidental implementation.
|
||||
|
||||
Resolve relative links from **each referencing document's directory**, not the
|
||||
terminal working directory. Resolve the installed `docs-governance` skill path
|
||||
and run its existing read-only link check as an agreed verification step:
|
||||
|
||||
```bash
|
||||
python3 <docs-governance-skill>/scripts/audit-docs.py --root <repository> --scope artifacts
|
||||
```
|
||||
|
||||
Confirm that the audit scanned the changed local instructions, context, and
|
||||
navigation; check its link results and exit code. Fix broken references and rerun,
|
||||
without creating empty targets. If the script is unavailable or excludes a target,
|
||||
report that target as unverified. A passing business test does not prove links are
|
||||
correct, and an existing same-named file may still be the wrong knowledge owner.
|
||||
|
||||
## Examples
|
||||
|
||||
- **“Implement an inventory module.”** Inspect existing boundaries; add local
|
||||
rules only for genuinely new constraints; reuse the domain glossary and API
|
||||
contract; link them from existing navigation; verify code and source-relative links.
|
||||
- **“Should payments become a separate module?”** Explain boundary options and
|
||||
evidence without writing files or treating the discussion as implementation approval.
|
||||
- **“Fix the inventory badge color.”** Read applicable instructions and fix the
|
||||
requested UI behavior; do not generate AGENTS, CONTEXT, or ADR documents.
|
||||
- **“Audit module documentation.”** Use the existing read-only audit and semantic
|
||||
review; a missing local file is not by itself permission to create one.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- A mandatory AGENTS/CONTEXT pair for every directory or frontend page.
|
||||
- Duplicating global constraints, glossary definitions, contracts, or ADR rationale.
|
||||
- Treating documentation checks as proof of completed business implementation.
|
||||
- Installing hooks or asserting automatic nested-file loading without real harness wiring.
|
||||
|
||||
## Delivery
|
||||
|
||||
Report canonical record paths, their project/module scope, actual changes,
|
||||
verification evidence, and unresolved ownership questions. Module governance
|
||||
delivers working rules and knowledge entry points, not a complete DDD conversion.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `context-and-decisions`
|
||||
- `architecture-decision-records`
|
||||
- `contract-first`
|
||||
- `change-impact`
|
||||
- `living-docs-governance`
|
||||
- `docs-governance`
|
||||
@@ -17,6 +17,7 @@ const MIRRORED_FILES = [
|
||||
'references/artifact-role-contract.md',
|
||||
'scripts/audit-docs.py',
|
||||
'scripts/markdown_links.py',
|
||||
'scripts/module_audit.py',
|
||||
];
|
||||
const ROUTED_SKILLS = [
|
||||
'ai-regression-testing',
|
||||
@@ -42,11 +43,13 @@ function test(name, fn) {
|
||||
|
||||
console.log('\n=== Documentation governance surface tests ===\n');
|
||||
|
||||
test('Codex distribution files match the canonical skill', () => {
|
||||
for (const relativePath of MIRRORED_FILES) {
|
||||
const canonical = fs.readFileSync(path.join(CANONICAL, relativePath), 'utf8');
|
||||
const mirror = fs.readFileSync(path.join(CODEX_MIRROR, relativePath), 'utf8');
|
||||
assert.strictEqual(mirror, canonical, `${relativePath} mirror is stale`);
|
||||
test('Codex and Pi distribution files match the canonical skill', () => {
|
||||
for (const mirrorRoot of [CODEX_MIRROR, path.dirname(PI_CORE)]) {
|
||||
for (const relativePath of MIRRORED_FILES) {
|
||||
const canonical = fs.readFileSync(path.join(CANONICAL, relativePath), 'utf8');
|
||||
const mirror = fs.readFileSync(path.join(mirrorRoot, relativePath), 'utf8');
|
||||
assert.strictEqual(mirror, canonical, `${mirrorRoot}/${relativePath} mirror is stale`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { listInstallComponents, resolveInstallPlan } = require('../../scripts/lib/install-manifests');
|
||||
const { createManifestInstallPlan } = require('../../scripts/lib/install-executor');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '../..');
|
||||
const OWNERS = [
|
||||
'module-governance', 'context-and-decisions', 'architecture-decision-records',
|
||||
'contract-first', 'change-impact', 'living-docs-governance', 'docs-governance',
|
||||
];
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
function test(name, run) {
|
||||
try {
|
||||
run();
|
||||
passed += 1;
|
||||
console.log(` ✓ ${name}`);
|
||||
} catch (error) {
|
||||
failed += 1;
|
||||
console.error(` ✗ ${name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const target of ['claude', 'codex']) {
|
||||
test(`opt-in ${target} installation includes every delegated knowledge owner`, () => {
|
||||
const plan = resolveInstallPlan({
|
||||
repoRoot: ROOT, profileId: 'minimal',
|
||||
includeComponentIds: ['capability:module-governance'], target,
|
||||
});
|
||||
const paths = new Set(plan.selectedModules.flatMap(module => module.paths));
|
||||
for (const owner of OWNERS) {
|
||||
assert.ok(paths.has(`skills/${owner}`), `missing ${owner}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const target of ['claude', 'claude-project', 'cursor', 'antigravity', 'codex',
|
||||
'opencode', 'codebuddy', 'joycode', 'qwen', 'zed']) {
|
||||
test(`advertised ${target} capability produces copies of every delegated owner`, () => {
|
||||
assert.ok(listInstallComponents({ repoRoot: ROOT, target }).some(
|
||||
component => component.id === 'capability:module-governance'
|
||||
));
|
||||
const plan = createManifestInstallPlan({
|
||||
sourceRoot: ROOT, target, homeDir: '/private/tmp/ecc-module-plan-preview',
|
||||
projectRoot: '/private/tmp/ecc-module-plan-preview', profileId: 'minimal',
|
||||
includeComponentIds: ['capability:module-governance'],
|
||||
// This checks skill-copy planning; installer tests validate the compiled payload.
|
||||
exemptValidationCodes: ['opencode-plugin-not-built'],
|
||||
});
|
||||
for (const owner of OWNERS) {
|
||||
assert.ok(plan.operations.some(operation => operation.kind === 'copy-file'
|
||||
&& operation.sourceRelativePath.endsWith(`skills/${owner}/SKILL.md`)),
|
||||
`missing ${owner} copy on ${target}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const target of ['hermes', 'openclaw', 'kimi', 'adal', 'copilot']) {
|
||||
test(`does not advertise the incomplete ${target} dependency closure`, () => {
|
||||
assert.ok(!listInstallComponents({ repoRoot: ROOT, target }).some(
|
||||
component => component.id === 'capability:module-governance'
|
||||
), `module governance is not installable on ${target}`);
|
||||
const plan = createManifestInstallPlan({
|
||||
sourceRoot: ROOT, target, homeDir: '/private/tmp/ecc-module-plan-preview',
|
||||
projectRoot: '/private/tmp/ecc-module-plan-preview', profileId: 'minimal',
|
||||
includeComponentIds: ['capability:module-governance'],
|
||||
});
|
||||
assert.ok(plan.skippedModuleIds.includes('documentation-module-governance'));
|
||||
assert.ok(!plan.selectedModuleIds.includes('documentation-module-governance'));
|
||||
// agents-core may copy Codex metadata; that is not this capability's installation.
|
||||
assert.ok(!plan.operations.some(operation => operation.moduleId
|
||||
=== 'documentation-module-governance'));
|
||||
});
|
||||
}
|
||||
|
||||
test('Codex module workflow is the canonical implementation', () => {
|
||||
assert.strictEqual(
|
||||
fs.readFileSync(path.join(ROOT, 'skills/module-governance/SKILL.md'), 'utf8'),
|
||||
fs.readFileSync(path.join(ROOT, '.agents/skills/module-governance/SKILL.md'), 'utf8')
|
||||
);
|
||||
});
|
||||
|
||||
test('standalone Pi carries the workflow and its delegated knowledge owners', () => {
|
||||
for (const owner of OWNERS) {
|
||||
assert.ok(fs.existsSync(path.join(ROOT, `pi/core/skills/${owner}/SKILL.md`)), owner);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`\nPassed: ${passed}\nFailed: ${failed}`);
|
||||
process.exitCode = failed ? 1 : 0;
|
||||
@@ -17,10 +17,13 @@ def project(tmp_path: Path) -> Path:
|
||||
|
||||
|
||||
def run_audit(
|
||||
project: Path, scope: str, *, timeout: float | None = None
|
||||
project: Path, scope: str, *, timeout: float | None = None,
|
||||
modules: tuple[str, ...] = (),
|
||||
script: Path = SCRIPT,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
module_args = [argument for module in modules for argument in ("--module", module)]
|
||||
return subprocess.run(
|
||||
[sys.executable, str(SCRIPT), "--root", str(project), "--scope", scope],
|
||||
[sys.executable, str(script), "--root", str(project), "--scope", scope, *module_args],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
@@ -28,6 +31,185 @@ def run_audit(
|
||||
)
|
||||
|
||||
|
||||
def test_module_audit_checks_local_carriers_links_and_is_read_only(project: Path) -> None:
|
||||
module = project / "src" / "inventory"
|
||||
module.mkdir(parents=True)
|
||||
(module / "AGENTS.md").write_text("[terms](CONTEXT.md)\n", encoding="utf-8")
|
||||
(module / "CONTEXT.md").write_text("[contract](../../contract.md)\n", encoding="utf-8")
|
||||
(project / "contract.md").write_text("# Contract\n", encoding="utf-8")
|
||||
before = {path: path.read_bytes() for path in project.rglob("*") if path.is_file()}
|
||||
result = run_audit(project, "modules", modules=("src/inventory",))
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "Local agents source: 'src/inventory/AGENTS.md'" in result.stdout
|
||||
assert "Local context source: 'src/inventory/CONTEXT.md'" in result.stdout
|
||||
assert "All local Markdown links resolve" in result.stdout
|
||||
assert "semantic review" in result.stdout
|
||||
after = {path: path.read_bytes() for path in project.rglob("*") if path.is_file()}
|
||||
assert after == before
|
||||
|
||||
|
||||
@pytest.mark.parametrize("distribution", ("skills", ".agents/skills", "pi/core/skills"))
|
||||
@pytest.mark.parametrize("role,filename", (("agents", "AGENTS.md"), ("context", "CONTEXT.md")))
|
||||
@pytest.mark.parametrize("broken", (False, True))
|
||||
@pytest.mark.parametrize("module_path", (".", "src/inventory"))
|
||||
def test_module_audit_checks_root_carrier_before_mapped_source(
|
||||
project: Path, distribution: str, role: str, filename: str, broken: bool,
|
||||
module_path: str,
|
||||
) -> None:
|
||||
(project / module_path).mkdir(parents=True, exist_ok=True)
|
||||
(project / ".governance").mkdir()
|
||||
(project / "docs").mkdir()
|
||||
(project / ".governance/docs-map.json").write_text(
|
||||
json.dumps({"agents": "docs/rules.md", "context": "docs/glossary.md"}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
for mapped in ("rules", "glossary"):
|
||||
(project / f"docs/{mapped}.md").write_text("# Shared source\n", encoding="utf-8")
|
||||
if not broken:
|
||||
(project / "docs/target.md").write_text("# Link target\n", encoding="utf-8")
|
||||
(project / filename).write_text("[target](docs/target.md)\n", encoding="utf-8")
|
||||
before = {path: path.read_bytes() for path in project.rglob("*") if path.is_file()}
|
||||
result = run_audit(
|
||||
project, "modules", modules=(module_path,),
|
||||
script=ROOT / distribution / "docs-governance/scripts/audit-docs.py",
|
||||
)
|
||||
assert result.returncode == (1 if broken else 0), result.stdout + result.stderr
|
||||
source_label = f"Local {role} source" if module_path == "." else f"Shared {role} candidate"
|
||||
assert f"{source_label}: '{filename}'" in result.stdout
|
||||
assert (f"Broken Markdown link: '{filename}' -> 'docs/target.md'" in result.stdout) == broken
|
||||
after = {path: path.read_bytes() for path in project.rglob("*") if path.is_file()}
|
||||
assert after == before, "module audit must remain read-only in every distribution"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scope", ("modules", "full"))
|
||||
def test_module_audit_reports_nearest_shared_candidates(project: Path, scope: str) -> None:
|
||||
(project / "src" / "inventory").mkdir(parents=True)
|
||||
(project / "AGENTS.md").write_text("# Project rules\n", encoding="utf-8")
|
||||
(project / "src" / "AGENTS.md").write_text("# Shared module rules\n", encoding="utf-8")
|
||||
(project / "CONTEXT.md").write_text("# Shared language\n", encoding="utf-8")
|
||||
result = run_audit(project, scope, modules=("src/inventory",))
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "Shared agents candidate: 'src/AGENTS.md'" in result.stdout
|
||||
assert "Shared context candidate: 'CONTEXT.md'" in result.stdout
|
||||
assert "confirm it covers module 'src/inventory'" in result.stdout
|
||||
assert "Local agents source" not in result.stdout
|
||||
|
||||
|
||||
def test_module_audit_reuses_mapped_sources_without_creating_shells(project: Path) -> None:
|
||||
(project / "inventory").mkdir()
|
||||
(project / "docs").mkdir()
|
||||
(project / ".governance").mkdir()
|
||||
(project / ".governance/docs-map.json").write_text(
|
||||
json.dumps({"agents": "docs/rules.md", "context": "docs/glossary.md"}), encoding="utf-8"
|
||||
)
|
||||
for name in ("rules", "glossary"):
|
||||
(project / f"docs/{name}.md").write_text(f"# {name}\n", encoding="utf-8")
|
||||
result = run_audit(project, "modules", modules=("inventory",))
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "Shared agents candidate: 'docs/rules.md'" in result.stdout
|
||||
assert "Shared context candidate: 'docs/glossary.md'" in result.stdout
|
||||
assert list((project / "inventory").iterdir()) == []
|
||||
|
||||
|
||||
def test_module_audit_checks_selected_carriers_in_template_named_modules(project: Path) -> None:
|
||||
(project / "templates/inventory").mkdir(parents=True)
|
||||
(project / "templates/inventory/AGENTS.md").write_text("[missing](missing.md)\n", encoding="utf-8")
|
||||
result = run_audit(project, "modules", modules=("templates/inventory",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Broken Markdown link: 'templates/inventory/AGENTS.md'" in result.stdout
|
||||
|
||||
|
||||
def test_module_audit_missing_carriers_are_manual_review_not_failure(project: Path) -> None:
|
||||
(project / "inventory").mkdir()
|
||||
result = run_audit(project, "modules", modules=("inventory",))
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert "No agents source found for module 'inventory'" in result.stdout
|
||||
assert "No context source found for module 'inventory'" in result.stdout
|
||||
assert list((project / "inventory").iterdir()) == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("module", ("missing", "../", "/", "not-a-directory"))
|
||||
def test_module_audit_rejects_invalid_directories(project: Path, module: str) -> None:
|
||||
(project / "not-a-directory").write_text("not a module\n", encoding="utf-8")
|
||||
result = run_audit(project, "modules", modules=(module,))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Module must be a repository-relative directory inside root" in result.stdout
|
||||
assert "Traceback" not in result.stderr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("filename", ("AGENTS.md", "CONTEXT.md"))
|
||||
def test_module_audit_does_not_read_escaping_sources(project: Path, filename: str) -> None:
|
||||
module = project / "inventory"
|
||||
module.mkdir()
|
||||
outside = project.parent / f"outside-{filename}"
|
||||
outside.write_text("[secret](secret-missing.md)\n", encoding="utf-8")
|
||||
try:
|
||||
(module / filename).symlink_to(outside)
|
||||
result = run_audit(project, "modules", modules=("inventory",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "repository-contained regular file" in result.stdout
|
||||
assert "secret-missing" not in result.stdout
|
||||
finally:
|
||||
outside.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def test_module_audit_reports_invalid_utf8_carrier(project: Path) -> None:
|
||||
(project / "inventory").mkdir()
|
||||
(project / "inventory/AGENTS.md").write_bytes(b"\xff")
|
||||
result = run_audit(project, "modules", modules=("inventory",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Cannot read module agents source" in result.stdout
|
||||
|
||||
|
||||
def test_module_audit_reuses_link_parser_and_source_relative_resolution(project: Path) -> None:
|
||||
module = project / "src/inventory"
|
||||
module.mkdir(parents=True)
|
||||
(module / "AGENTS.md").write_text("[terms](.\\CONTEXT.md)\n", encoding="utf-8")
|
||||
(module / "CONTEXT.md").write_text("[contract](../contract.md)\n", encoding="utf-8")
|
||||
(project / "contract.md").write_text("# Wrong base\n", encoding="utf-8")
|
||||
result = run_audit(project, "modules", modules=("src/inventory",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Broken Markdown link: 'src/inventory/CONTEXT.md' -> '../contract.md'" in result.stdout
|
||||
assert "AGENTS.md' ->" not in result.stdout
|
||||
|
||||
|
||||
def test_module_audit_requires_explicit_scope_and_boundary(project: Path) -> None:
|
||||
missing = run_audit(project, "modules")
|
||||
ignored = run_audit(project, "context", modules=("inventory",))
|
||||
assert missing.returncode == ignored.returncode == 2
|
||||
assert "requires at least one --module" in missing.stderr
|
||||
assert "supported only with --scope modules or full" in ignored.stderr
|
||||
|
||||
|
||||
def test_module_audit_checks_each_selected_boundary_once(project: Path) -> None:
|
||||
for module in ("inventory", "billing"):
|
||||
(project / module).mkdir()
|
||||
result = run_audit(project, "modules", modules=("inventory", "billing", "inventory"))
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert result.stdout.count("Selected module: 'inventory'") == 1
|
||||
assert result.stdout.count("Selected module: 'billing'") == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kind", ("directory", "broken-symlink"))
|
||||
def test_module_audit_reports_non_file_carriers(project: Path, kind: str) -> None:
|
||||
(project / "inventory").mkdir()
|
||||
carrier = project / "inventory/AGENTS.md"
|
||||
if kind == "directory":
|
||||
carrier.mkdir()
|
||||
else:
|
||||
carrier.symlink_to(project / "missing.md")
|
||||
result = run_audit(project, "modules", modules=("inventory",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "repository-contained regular file" in result.stdout
|
||||
|
||||
|
||||
def test_module_audit_rejects_escaping_module_directory(project: Path) -> None:
|
||||
(project / "escape").symlink_to(project.parent, target_is_directory=True)
|
||||
result = run_audit(project, "modules", modules=("escape",))
|
||||
assert result.returncode == 1, result.stdout + result.stderr
|
||||
assert "Module must be a repository-relative directory inside root" in result.stdout
|
||||
|
||||
|
||||
def configure_git(project: Path) -> None:
|
||||
subprocess.run(["git", "init"], cwd=project, capture_output=True, check=True)
|
||||
subprocess.run(
|
||||
|
||||
Reference in new issue
Block a user