cyber-sdd 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +17 -0
- package/.codex-plugin/plugin.json +17 -0
- package/.plugin/plugin.json +17 -0
- package/README.md +159 -0
- package/agents/sdd-automaton.md +97 -0
- package/agents/sdd-impl-judge.md +214 -0
- package/agents/sdd-scanner.md +120 -0
- package/agents/sdd-spec-judge.md +224 -0
- package/agents/sdd-warden.md +101 -0
- package/package.json +24 -0
- package/skills/align-spec/README.md +20 -0
- package/skills/align-spec/SKILL.md +111 -0
- package/skills/align-spec/scripts/align-spec.mts +187 -0
- package/skills/architect-impl-governance/README.md +46 -0
- package/skills/architect-impl-governance/SKILL.md +45 -0
- package/skills/architect-spec-governance/README.md +48 -0
- package/skills/architect-spec-governance/SKILL.md +59 -0
- package/skills/blast-estimate/README.md +47 -0
- package/skills/blast-estimate/SKILL.md +133 -0
- package/skills/blast-estimate/scripts/blast-estimate.mts +583 -0
- package/skills/builder-impl-governance/README.md +47 -0
- package/skills/builder-impl-governance/SKILL.md +47 -0
- package/skills/builder-spec-governance/README.md +49 -0
- package/skills/builder-spec-governance/SKILL.md +36 -0
- package/skills/check-partition-quality/README.md +22 -0
- package/skills/check-partition-quality/SKILL.md +51 -0
- package/skills/check-partition-quality/scripts/check-partition-quality.mts +336 -0
- package/skills/check-plan-safety/README.md +17 -0
- package/skills/check-plan-safety/SKILL.md +60 -0
- package/skills/check-plan-safety/scripts/check-plan-safety.mts +145 -0
- package/skills/check-project-specs/README.md +19 -0
- package/skills/check-project-specs/SKILL.md +69 -0
- package/skills/check-project-specs/scripts/check-project-specs.mts +217 -0
- package/skills/check-scenario-overlap/README.md +19 -0
- package/skills/check-scenario-overlap/SKILL.md +74 -0
- package/skills/check-scenario-overlap/scripts/check-scenario-overlap.mts +249 -0
- package/skills/check-spec-structure/README.md +17 -0
- package/skills/check-spec-structure/SKILL.md +66 -0
- package/skills/check-spec-structure/scripts/check-spec-structure.mts +346 -0
- package/skills/collision-ladder/README.md +18 -0
- package/skills/collision-ladder/SKILL.md +83 -0
- package/skills/collision-ladder/scripts/collision-ladder.mts +657 -0
- package/skills/combat-log-governance/README.md +13 -0
- package/skills/combat-log-governance/SKILL.md +257 -0
- package/skills/concept-index/README.md +13 -0
- package/skills/concept-index/SKILL.md +38 -0
- package/skills/concept-index/scripts/concept-index.mts +245 -0
- package/skills/discover-plans/README.md +16 -0
- package/skills/discover-plans/SKILL.md +74 -0
- package/skills/discover-plans/scripts/discover-plans.mts +212 -0
- package/skills/discover-specs/README.md +15 -0
- package/skills/discover-specs/SKILL.md +76 -0
- package/skills/discover-specs/scripts/discover-specs.mts +396 -0
- package/skills/doctrine-loop/README.md +15 -0
- package/skills/doctrine-loop/SKILL.md +97 -0
- package/skills/formation-loop/README.md +17 -0
- package/skills/formation-loop/SKILL.md +140 -0
- package/skills/gate-validation-governance/README.md +12 -0
- package/skills/gate-validation-governance/SKILL.md +87 -0
- package/skills/impl-producer-governance/README.md +48 -0
- package/skills/impl-producer-governance/SKILL.md +85 -0
- package/skills/init/README.md +27 -0
- package/skills/init/SKILL.md +68 -0
- package/skills/init/scripts/wire-statusline.mts +276 -0
- package/skills/lifecycle-governance/README.md +11 -0
- package/skills/lifecycle-governance/SKILL.md +168 -0
- package/skills/manage/README.md +9 -0
- package/skills/manage/SKILL.md +62 -0
- package/skills/manage-ignore/README.md +19 -0
- package/skills/manage-ignore/SKILL.md +52 -0
- package/skills/manage-ignore/scripts/manage-ignore.mts +294 -0
- package/skills/manage-scenario-bridge/README.md +20 -0
- package/skills/manage-scenario-bridge/SKILL.md +60 -0
- package/skills/manage-scenario-bridge/scripts/manage-scenario-bridge.mts +156 -0
- package/skills/manage-spec-anchors/README.md +18 -0
- package/skills/manage-spec-anchors/SKILL.md +56 -0
- package/skills/manage-spec-anchors/scripts/manage-spec-anchors.mts +328 -0
- package/skills/mission-graph/README.md +15 -0
- package/skills/mission-graph/SKILL.md +67 -0
- package/skills/mission-graph/scripts/mission-graph.mts +844 -0
- package/skills/oracle-spec-governance/README.md +45 -0
- package/skills/oracle-spec-governance/SKILL.md +45 -0
- package/skills/ownership-governance/README.md +65 -0
- package/skills/ownership-governance/SKILL.md +104 -0
- package/skills/pause-mission/README.md +18 -0
- package/skills/pause-mission/SKILL.md +112 -0
- package/skills/place-node/README.md +12 -0
- package/skills/place-node/SKILL.md +47 -0
- package/skills/place-node/scripts/place-node.mts +157 -0
- package/skills/plan-retirement/README.md +32 -0
- package/skills/plan-retirement/SKILL.md +90 -0
- package/skills/plan-retirement/scripts/retire-plans.mts +196 -0
- package/skills/plugin-contract-governance/README.md +12 -0
- package/skills/plugin-contract-governance/SKILL.md +112 -0
- package/skills/remediation-governance/README.md +46 -0
- package/skills/remediation-governance/SKILL.md +78 -0
- package/skills/resolve-governances/README.md +18 -0
- package/skills/resolve-governances/SKILL.md +50 -0
- package/skills/resolve-governances/scripts/resolve-governances.mts +515 -0
- package/skills/resolve-tracking/SKILL.md +64 -0
- package/skills/resolve-tracking/scripts/resolve-tracking.mts +213 -0
- package/skills/resume-mission/README.md +12 -0
- package/skills/resume-mission/SKILL.md +53 -0
- package/skills/scaffold-project-spec/README.md +7 -0
- package/skills/scaffold-project-spec/SKILL.md +192 -0
- package/skills/sdd/README.md +7 -0
- package/skills/sdd/SKILL.md +92 -0
- package/skills/solution-producer-governance/README.md +9 -0
- package/skills/solution-producer-governance/SKILL.md +44 -0
- package/skills/spec-format-governance/README.md +73 -0
- package/skills/spec-format-governance/SKILL.md +114 -0
- package/skills/spec-gate/README.md +26 -0
- package/skills/spec-gate/SKILL.md +201 -0
- package/skills/spec-gate/scripts/check-spec-state.mts +601 -0
- package/skills/spec-gate/scripts/check-suite.mts +501 -0
- package/skills/spec-gate/scripts/classify-edit-class.mts +411 -0
- package/skills/spec-producer-governance/README.md +7 -0
- package/skills/spec-producer-governance/SKILL.md +86 -0
- package/skills/spec-structure-governance/README.md +40 -0
- package/skills/spec-structure-governance/SKILL.md +169 -0
- package/skills/ssa-lowering/README.md +26 -0
- package/skills/ssa-lowering/SKILL.md +181 -0
- package/skills/start-mission/README.md +7 -0
- package/skills/start-mission/SKILL.md +115 -0
- package/skills/suite-format-governance/README.md +75 -0
- package/skills/suite-format-governance/SKILL.md +299 -0
- package/skills/suite-format-governance/references/rubric.md +313 -0
- package/skills/touch-set-correction/README.md +16 -0
- package/skills/touch-set-correction/SKILL.md +67 -0
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +418 -0
- package/skills/verify-scenarios/README.md +17 -0
- package/skills/verify-scenarios/SKILL.md +109 -0
- package/skills/verify-scenarios/scripts/verify-scenarios.mts +386 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan-retirement
|
|
3
|
+
description: "Partial Skill: invoke by name only — the SDD Doctrine loop's last retro step — the gated, idempotent tracked deletion of a retired mission plan. Invoked by the doctrine-loop Scanner, not user-triggered; the clearance contract lives in the body + README."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
metadata:
|
|
6
|
+
internal: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SDD Plan Retirement
|
|
10
|
+
|
|
11
|
+
The Doctrine loop's **last retro step** (`sdd:doctrine-loop`; the provenance shape is
|
|
12
|
+
`sdd:combat-log-governance`). Because plans are **tracked** (committed with
|
|
13
|
+
the work, not gitignored), a retired plan leaves the tree by a deliberate **tracked deletion** —
|
|
14
|
+
never a gitignore side effect. This skill carries a self-contained `.mts` sweep that, for each
|
|
15
|
+
cleared `<cr-ref>`, deletes that CR's whole **transient artifact set**: the plan pair
|
|
16
|
+
(`<cr-ref>.plan.md` + `<cr-ref>.log.jsonl`) plus its **transient CR-level planning briefs**
|
|
17
|
+
(`<cr-ref>.design.md`, `<cr-ref>.operations.md`, `<cr-ref>.evidence.md`) from `.agents/plans` —
|
|
18
|
+
so a retired CR leaves no orphan behind.
|
|
19
|
+
|
|
20
|
+
## The transient briefs ride along, never gate
|
|
21
|
+
|
|
22
|
+
`design.md` / `operations.md` / `evidence.md` are optional, cr-ref-scoped planning briefs that
|
|
23
|
+
share the plan's lifetime. They retire alongside the plan pair but do not participate in the
|
|
24
|
+
retirement decision. Two boundaries:
|
|
25
|
+
|
|
26
|
+
- **They do not widen the distilled gate.** The gate keys on the combat log's presence only — a
|
|
27
|
+
cr-ref with briefs but no `log.jsonl` still retires without a distilling `strategy` entry. A
|
|
28
|
+
brief owes no distillation (its content was consumed by the mission itself).
|
|
29
|
+
- **They do not anchor presence.** `<cr-ref>.plan.md` is the sole presence signal. A brief
|
|
30
|
+
without a `plan.md` is left untouched.
|
|
31
|
+
|
|
32
|
+
## Distill and delete are decoupled
|
|
33
|
+
|
|
34
|
+
- **Distill (early).** At `→ implemented`, the Scanner reads the concluded combat log and distills
|
|
35
|
+
recurring `cause`s into the ledger's `strategy` lines (`sdd:doctrine-loop`).
|
|
36
|
+
- **Delete (late).** This sweep runs as a **separate, later** step, gated on source = `done`/merged
|
|
37
|
+
**and** the plan distilled. Never delete an un-distilled plan (the retro never ran).
|
|
38
|
+
|
|
39
|
+
## The clearance boundary — split by verifiability
|
|
40
|
+
|
|
41
|
+
The two gating signals split by what the sweep can check itself:
|
|
42
|
+
|
|
43
|
+
- **source = `done`/merged** — the **caller's judgment**: query the source natively (`github-NN` → GH
|
|
44
|
+
issue, `asana-<gid>` → Asana, `local-<slug>` → the local store); needs network/`gh`. The caller
|
|
45
|
+
passes the source-cleared set via `--retire`.
|
|
46
|
+
- **distilled** — **verified mechanically by the sweep**: a `strategy` entry with `distills ==
|
|
47
|
+
<cr-ref>` must exist in the project ledger (`--ledger`). The sweep keys on the structured
|
|
48
|
+
`distills` field, **never** a `<cr-ref>` that appears only in a strategy's `evidence`
|
|
49
|
+
cross-references, and an **unratified** distilling entry still counts
|
|
50
|
+
(`sdd:combat-log-governance`). Its absence is **fail-closed** — but only when a combat log
|
|
51
|
+
**exists**: a cr-ref whose `<cr-ref>.log.jsonl` was never written (a non-gated mission — hand-run,
|
|
52
|
+
chore-tracked, investigation — runs no gate cycle and emits no correction) has **nothing to
|
|
53
|
+
distill**, so it retires on clearance + presence alone. The fail-closed leaves an existing,
|
|
54
|
+
undistilled log's plan intact so its distillation can still be drafted.
|
|
55
|
+
|
|
56
|
+
Leaving the distilled half to the caller once let a plan + combat log be deleted before any
|
|
57
|
+
distillation existed (the evidence the distill was meant to preserve). Because the check is local,
|
|
58
|
+
the sweep does it itself. Only the genuinely non-local judgment (source status) stays with the caller.
|
|
59
|
+
|
|
60
|
+
## Run the sweep
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
node "<skill>/scripts/retire-plans.mts" \
|
|
64
|
+
--root .agents/plans \
|
|
65
|
+
--ledger .agents/specs/<project>/ledger \
|
|
66
|
+
--retire github-34,asana-7 [--dry-run]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- **`--ledger <dir>`** points at the project's ledger directory (the `ledger/` sibling of the root
|
|
70
|
+
`spec.md`). **Required for any deletion** — omit it (or an unreadable dir) and the sweep
|
|
71
|
+
fail-closes: nothing is deleted (the no-log branch only applies once a ledger is present to consult).
|
|
72
|
+
- Deletes the transient artifact set (`<cr-ref>.plan.md`, `<cr-ref>.log.jsonl`,
|
|
73
|
+
`<cr-ref>.design.md`, `<cr-ref>.operations.md`, `<cr-ref>.evidence.md`) only for a `<cr-ref>` that
|
|
74
|
+
is cleared (`--retire`) **and** present on disk (`<cr-ref>.plan.md` exists) **and** either
|
|
75
|
+
distilled (a `strategy` with `distills == <cr-ref>` in `--ledger`) **or** has no combat log to
|
|
76
|
+
distill (no `<cr-ref>.log.jsonl` on disk). Each brief is deleted only if present; an absent one is
|
|
77
|
+
a no-op, same as the missing log half.
|
|
78
|
+
- **Fail-closed** — a plan not named in `--retire`, or whose combat log **exists** but has no
|
|
79
|
+
distilling ledger entry, is never touched (and neither are its briefs).
|
|
80
|
+
- **Idempotent** — a cleared `<cr-ref>` with no plan on disk (already retired, or an open CR the
|
|
81
|
+
caller declined to clear) is a no-op, even if a brief for it exists; the sweep is safe to re-run.
|
|
82
|
+
- `--dry-run` prints the planned deletions without touching the tree.
|
|
83
|
+
|
|
84
|
+
When `node` is absent, an agent performs the same decision by hand: for each cleared `<cr-ref>`, if
|
|
85
|
+
its `<cr-ref>.log.jsonl` **exists**, **first confirm a `strategy` entry with `distills == <cr-ref>`
|
|
86
|
+
exists in the project ledger** (not a mere `evidence` mention; unratified still counts) — if none,
|
|
87
|
+
skip it. A cr-ref with **no** `log.jsonl` has nothing to distill and needs no such entry. Only then
|
|
88
|
+
delete `<cr-ref>.plan.md`, `<cr-ref>.log.jsonl`, `<cr-ref>.design.md`, `<cr-ref>.operations.md`, and
|
|
89
|
+
`<cr-ref>.evidence.md` if present, touching nothing else — and only if `<cr-ref>.plan.md` is
|
|
90
|
+
present in the first place (a brief alone never triggers deletion).
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Plan retirement — the Doctrine loop's last retro step (see sdd:combat-log-governance,
|
|
3
|
+
// "Plan retirement"). A retired plan leaves the tree by a deliberate, gated TRACKED
|
|
4
|
+
// DELETION, never a gitignore side effect: for each cleared <cr-ref>, the sweep deletes that
|
|
5
|
+
// CR's whole TRANSIENT ARTIFACT SET — the plan pair (<cr-ref>.plan.md + <cr-ref>.log.jsonl)
|
|
6
|
+
// plus its transient CR-level planning briefs (<cr-ref>.design.md, <cr-ref>.operations.md,
|
|
7
|
+
// <cr-ref>.evidence.md) — from `.agents/plans`.
|
|
8
|
+
//
|
|
9
|
+
// The two gating signals split by verifiability. Source = done/merged stays the CALLER's
|
|
10
|
+
// judgment: the source-status query (github-NN -> GH issue, asana-<gid> -> Asana,
|
|
11
|
+
// local-<slug> -> the local store) needs network/gh, so the Scanner (doctrine-loop delegate)
|
|
12
|
+
// determines it and passes the CLEARED set via --retire. Distilled is local and mechanically
|
|
13
|
+
// checkable, so the sweep VERIFIES IT ITSELF against the project ledger (--ledger <dir>): a
|
|
14
|
+
// `strategy` entry whose `distills` field equals the <cr-ref> must exist. The distilled gate
|
|
15
|
+
// only guards a cr-ref whose combat log EXISTS — there is something on disk it could still
|
|
16
|
+
// distill from, so retirement without a distilling entry would be data loss. A cr-ref whose
|
|
17
|
+
// <cr-ref>.log.jsonl was never written has nothing to distill from in the first place, so it
|
|
18
|
+
// retires on clearance + presence alone, no distilling entry required. This is the
|
|
19
|
+
// mechanical, fail-closed gate + filesystem act:
|
|
20
|
+
// - it deletes the transient artifact set for a cr-ref that is cleared AND present on disk
|
|
21
|
+
// AND (distilled per the ledger OR has no log.jsonl to distill from);
|
|
22
|
+
// - a missing/unreadable --ledger skips retirement for ALL cr-refs (the no-log branch only
|
|
23
|
+
// applies once a ledger is actually present to consult);
|
|
24
|
+
// - anything not cleared, not present, or already gone is a no-op (idempotent, safe to
|
|
25
|
+
// re-run);
|
|
26
|
+
// - it never touches a plan it was not explicitly cleared to retire (fail-closed).
|
|
27
|
+
//
|
|
28
|
+
// Two invariants on the transient briefs (design.md / operations.md / evidence.md), both
|
|
29
|
+
// deliberate:
|
|
30
|
+
// - THEY DO NOT WIDEN THE DISTILLED GATE. The gate keys on the combat log's presence only
|
|
31
|
+
// (discoverLogs / logPresent) — a cr-ref with briefs but no log.jsonl still retires
|
|
32
|
+
// without a distilling strategy. A brief owes no distillation (its content was consumed
|
|
33
|
+
// by the mission itself, not extracted into the ledger), so gating on one would re-strand
|
|
34
|
+
// the no-log mission class the no-log branch exists to rescue.
|
|
35
|
+
// - THEY DO NOT ANCHOR PRESENCE. discoverPlans (keyed on .plan.md) stays the sole presence
|
|
36
|
+
// signal. A cr-ref with a design.md but no plan.md is a no-op — the idempotency contract
|
|
37
|
+
// (a cleared cr-ref with no plan on disk deletes nothing) is the stronger guarantee.
|
|
38
|
+
//
|
|
39
|
+
// Pure functions are exported for node:test; running the file directly drives the CLI.
|
|
40
|
+
// No dependencies. Use --dry-run to print the planned deletions without touching the tree.
|
|
41
|
+
|
|
42
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'node:fs'
|
|
43
|
+
import { join } from 'node:path'
|
|
44
|
+
|
|
45
|
+
const PLAN_SUFFIX = '.plan.md'
|
|
46
|
+
const LOG_SUFFIX = '.log.jsonl'
|
|
47
|
+
const DESIGN_SUFFIX = '.design.md'
|
|
48
|
+
const OPERATIONS_SUFFIX = '.operations.md'
|
|
49
|
+
const EVIDENCE_SUFFIX = '.evidence.md'
|
|
50
|
+
|
|
51
|
+
// The full transient artifact set a retired cr-ref owns, in deletion order: the plan pair
|
|
52
|
+
// (plan.md, log.jsonl) then the optional transient CR-level planning briefs (design.md,
|
|
53
|
+
// operations.md, evidence.md). The briefs are optional — the per-file existsSync guard in
|
|
54
|
+
// main() no-ops any that are absent, which is what lets a cr-ref with only some briefs retire
|
|
55
|
+
// cleanly.
|
|
56
|
+
export function transientArtifactFiles(crRef: string): string[] {
|
|
57
|
+
return [
|
|
58
|
+
`${crRef}${PLAN_SUFFIX}`,
|
|
59
|
+
`${crRef}${LOG_SUFFIX}`,
|
|
60
|
+
`${crRef}${DESIGN_SUFFIX}`,
|
|
61
|
+
`${crRef}${OPERATIONS_SUFFIX}`,
|
|
62
|
+
`${crRef}${EVIDENCE_SUFFIX}`,
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Parse a --retire value (comma-separated cr-refs) into a clean, de-duplicated list.
|
|
67
|
+
export function parseCleared(value: string | undefined): string[] {
|
|
68
|
+
if (!value) return []
|
|
69
|
+
const seen = new Set<string>()
|
|
70
|
+
for (const raw of value.split(',')) {
|
|
71
|
+
const ref = raw.trim()
|
|
72
|
+
if (ref) seen.add(ref)
|
|
73
|
+
}
|
|
74
|
+
return [...seen]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The cr-refs that have a <cr-ref>.plan.md on disk under `root`.
|
|
78
|
+
export function discoverPlans(root: string): string[] {
|
|
79
|
+
let entries: string[]
|
|
80
|
+
try {
|
|
81
|
+
entries = readdirSync(root)
|
|
82
|
+
} catch {
|
|
83
|
+
return []
|
|
84
|
+
}
|
|
85
|
+
return entries.filter((f) => f.endsWith(PLAN_SUFFIX)).map((f) => f.slice(0, -PLAN_SUFFIX.length))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// The cr-refs that have a <cr-ref>.log.jsonl on disk under `root`.
|
|
89
|
+
export function discoverLogs(root: string): string[] {
|
|
90
|
+
let entries: string[]
|
|
91
|
+
try {
|
|
92
|
+
entries = readdirSync(root)
|
|
93
|
+
} catch {
|
|
94
|
+
return []
|
|
95
|
+
}
|
|
96
|
+
return entries.filter((f) => f.endsWith(LOG_SUFFIX)).map((f) => f.slice(0, -LOG_SUFFIX.length))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The cr-refs the project ledger records as DISTILLED — a `strategy` entry whose `distills`
|
|
100
|
+
// field equals the cr-ref exists in some *.jsonl shard under `ledgerDir`. Keys on the
|
|
101
|
+
// structured `distills` field only: a cr-ref named merely inside a strategy's `evidence`
|
|
102
|
+
// array (a cross-reference) does NOT count, and an unratified entry (`ratified: false`, the
|
|
103
|
+
// Scanner's default) still counts — the gate is about what was distilled, not sign-off.
|
|
104
|
+
// Malformed lines and a missing/unreadable ledger dir are tolerated: fail-closed means an
|
|
105
|
+
// unverifiable ledger yields an EMPTY set, never a thrown error.
|
|
106
|
+
export function distilledCrRefs(ledgerDir: string): Set<string> {
|
|
107
|
+
const distilled = new Set<string>()
|
|
108
|
+
let entries: string[]
|
|
109
|
+
try {
|
|
110
|
+
entries = readdirSync(ledgerDir)
|
|
111
|
+
} catch {
|
|
112
|
+
return distilled
|
|
113
|
+
}
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (!entry.endsWith('.jsonl')) continue
|
|
116
|
+
let text: string
|
|
117
|
+
try {
|
|
118
|
+
text = readFileSync(join(ledgerDir, entry), 'utf8')
|
|
119
|
+
} catch {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
for (const line of text.split('\n')) {
|
|
123
|
+
const trimmed = line.trim()
|
|
124
|
+
if (!trimmed) continue
|
|
125
|
+
let record: unknown
|
|
126
|
+
try {
|
|
127
|
+
record = JSON.parse(trimmed)
|
|
128
|
+
} catch {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if (record === null || typeof record !== 'object') continue
|
|
132
|
+
const { kind, distills } = record as { kind?: unknown; distills?: unknown }
|
|
133
|
+
if (kind === 'strategy' && typeof distills === 'string' && distills.length > 0) distilled.add(distills)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return distilled
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Fail-closed, idempotent decision: retire exactly the cr-refs that are cleared by the caller,
|
|
140
|
+
// present on disk, AND either distilled per the ledger OR have no combat log to distill from
|
|
141
|
+
// (log.jsonl absent). An uncleared or absent plan is never retired; a present plan whose log
|
|
142
|
+
// DOES exist still requires a distilling entry (data-loss guard, unchanged). Order follows the
|
|
143
|
+
// cleared list for stable reporting.
|
|
144
|
+
export function decideRetirements(
|
|
145
|
+
cleared: string[],
|
|
146
|
+
existing: string[],
|
|
147
|
+
distilled: Set<string>,
|
|
148
|
+
logPresent: Set<string>,
|
|
149
|
+
): string[] {
|
|
150
|
+
const present = new Set(existing)
|
|
151
|
+
const seen = new Set<string>()
|
|
152
|
+
const out: string[] = []
|
|
153
|
+
for (const ref of cleared) {
|
|
154
|
+
if (present.has(ref) && (distilled.has(ref) || !logPresent.has(ref)) && !seen.has(ref)) {
|
|
155
|
+
seen.add(ref)
|
|
156
|
+
out.push(ref)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function main(argv: string[]): number {
|
|
163
|
+
const root = argv.includes('--root') ? argv[argv.indexOf('--root') + 1] : '.agents/plans'
|
|
164
|
+
const ledgerArg = argv.includes('--ledger') ? argv[argv.indexOf('--ledger') + 1] : undefined
|
|
165
|
+
const cleared = parseCleared(argv.includes('--retire') ? argv[argv.indexOf('--retire') + 1] : undefined)
|
|
166
|
+
const dryRun = argv.includes('--dry-run')
|
|
167
|
+
|
|
168
|
+
let retiring: string[]
|
|
169
|
+
if (!ledgerArg || !existsSync(ledgerArg)) {
|
|
170
|
+
process.stdout.write(
|
|
171
|
+
`no verifiable ledger (${ledgerArg ? `--ledger ${ledgerArg} unreadable` : '--ledger not given'}): distillation cannot be checked, so retirement is skipped for all cr-refs\n`,
|
|
172
|
+
)
|
|
173
|
+
retiring = []
|
|
174
|
+
} else {
|
|
175
|
+
const distilled = distilledCrRefs(ledgerArg)
|
|
176
|
+
const logPresent = new Set(discoverLogs(root))
|
|
177
|
+
retiring = decideRetirements(cleared, discoverPlans(root), distilled, logPresent)
|
|
178
|
+
}
|
|
179
|
+
const deleted: string[] = []
|
|
180
|
+
|
|
181
|
+
for (const ref of retiring) {
|
|
182
|
+
for (const file of transientArtifactFiles(ref)) {
|
|
183
|
+
const path = join(root, file)
|
|
184
|
+
if (!existsSync(path)) continue // the log may be absent; delete what is there
|
|
185
|
+
if (!dryRun) unlinkSync(path)
|
|
186
|
+
deleted.push(path)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const verb = dryRun ? 'would delete' : 'deleted'
|
|
191
|
+
for (const path of deleted) process.stdout.write(`${verb} ${path}\n`)
|
|
192
|
+
process.stdout.write(`${dryRun ? 'dry-run: ' : ''}retired ${retiring.length} plan(s), ${deleted.length} file(s)\n`)
|
|
193
|
+
return 0
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (import.meta.main) process.exit(main(process.argv.slice(2)))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# plugin-contract-governance
|
|
2
|
+
|
|
3
|
+
Internal SDD governance (`user-invocable: false`). The **plugin contract** — what an SDD plugin must
|
|
4
|
+
implement: the five delegate roles (closed set), the per-role governance loadout (the Model-B
|
|
5
|
+
`(actor, gate)` bars + the fixed-universal set), and the `sdd-plugins[]` registry entry shape +
|
|
6
|
+
resolution by `artifact-type`.
|
|
7
|
+
|
|
8
|
+
A **single-owner** governance — its consumer family is the plugin/conductor resolution surface, so it
|
|
9
|
+
lives under `plugin/`, not `common-governances/`. Loaded by the conductor and by plugin authors. The
|
|
10
|
+
universal-plugin *format* is `plugin-design` (out of scope); resolution/composition mechanics are the
|
|
11
|
+
`resolve-governances` skill; the actor bars are the shipped
|
|
12
|
+
`sdd:{oracle,builder,architect}-{spec,impl}-governance` skills. Not triggered by users directly.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plugin-contract-governance
|
|
3
|
+
description: "Partial Skill: invoke by name only — the SDD plugin contract for what a plugin implements. Loaded by the conductor and by plugin authors, not user-triggered."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SDD Plugin Contract Governance
|
|
8
|
+
|
|
9
|
+
What an SDD plugin must implement and what each part loads. The conductor resolves delegates against
|
|
10
|
+
this contract; a plugin author builds to it. The universal-plugin format itself is `plugin-design`
|
|
11
|
+
(`governance show universal-plugin`); this skill is the SDD-role layer on top.
|
|
12
|
+
|
|
13
|
+
## The five delegate roles (closed set)
|
|
14
|
+
|
|
15
|
+
A plugin covers a set of artifact-types by providing agents for these role keys. Any role may be
|
|
16
|
+
`null` (degenerates to the SDD default) or omitted (falls back to the convention name
|
|
17
|
+
`<plugin>-<role>`). A **producer** role may also name a model-tuned agent to run at its own
|
|
18
|
+
model/effort — the model-tuning escape valve; naming any agent (plugin delegate or model-tuned)
|
|
19
|
+
means the conductor **spawns** it.
|
|
20
|
+
|
|
21
|
+
| Role key | Acts | SDD default |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `spec-producer` | writes the `spec.md` body + the `.feature` | conductor loads `spec-producer-governance`, authors inline (`sdd:automaton`) |
|
|
24
|
+
| `solution-producer` | writes `<unit>.solution.md` (the durable, ungated design fork) | conductor loads `solution-producer-governance`, authors inline (`sdd:automaton`) |
|
|
25
|
+
| `spec-judge` | judges `spec.md` + the `.feature` at the spec gate | `sdd-spec-judge` — spawned cold agent |
|
|
26
|
+
| `impl-producer` | builds the artifact **and** its verification | conductor loads `impl-producer-governance`, dispatches a generic builder (`sdd:automaton`) |
|
|
27
|
+
| `impl-judge` | runs the verification against the frozen `.feature` | `sdd-impl-judge` — spawned cold agent |
|
|
28
|
+
|
|
29
|
+
**Producers run inline (or via a mechanical builder), judges spawn cold** ("conductor writes, cold
|
|
30
|
+
judges grade"): an SDD-default spec/solution-producer is a governance the conductor loads and runs in
|
|
31
|
+
its own warm main-session context (recorded `produced-by.<role>: sdd:automaton`); the SDD-default
|
|
32
|
+
impl-producer is mechanical and spawned via a generic builder; an SDD-default judge is a cold agent
|
|
33
|
+
the conductor spawns, because a grader must not share the author's context. A plugin delegate — or a
|
|
34
|
+
model-tuned producer agent named for the slot — is always spawned.
|
|
35
|
+
|
|
36
|
+
Any of the spawns above may instead be realized through a general-purpose dispatch capability's
|
|
37
|
+
`subagent | channel` seam (ADR-0023, referenced by intent only) — an alternative realization of the
|
|
38
|
+
same spawn, not a change to which roles spawn or how they are graded.
|
|
39
|
+
|
|
40
|
+
> The legacy role key was `plan-producer` (writing `plan.md` + `tasks.md`); it is renamed
|
|
41
|
+
> **`solution-producer`** writing `<unit>.solution.md` (`sdd:combat-log-governance`). A live registry
|
|
42
|
+
> still carrying `plan-producer` is migrated on encounter.
|
|
43
|
+
|
|
44
|
+
## Which governances each role loads
|
|
45
|
+
|
|
46
|
+
Bars are the Model-B `(actor, gate)` governances (matched by the `resolve-governances` skill; the
|
|
47
|
+
actor bars are the shipped `sdd:{oracle,builder,architect}-{spec,impl}-governance` skills); a producer
|
|
48
|
+
self-aligns to exactly the bars its judge grades. The lens sets are spec gate `{oracle, builder,
|
|
49
|
+
architect}`, impl gate `{builder, architect}`, solution `{architect}` (ungated).
|
|
50
|
+
|
|
51
|
+
| Role | Loads |
|
|
52
|
+
|---|---|
|
|
53
|
+
| spec-producer | `spec-format`, `suite-format`, `ownership`, the resolved `oracle-spec` + `builder-spec` bars |
|
|
54
|
+
| solution-producer | `ownership`, the resolved `architect-spec` bar |
|
|
55
|
+
| spec-judge | `spec-format`, `suite-format`, `lifecycle`, `gate-validation`, the resolved `oracle-spec` + `builder-spec` + `architect-spec` bars |
|
|
56
|
+
| impl-producer | `ownership`, the resolved `builder-impl` + `architect-impl` bars |
|
|
57
|
+
| impl-judge | `ownership`, `gate-validation`, the resolved `builder-impl` + `architect-impl` bars |
|
|
58
|
+
|
|
59
|
+
For an **SDD-default producer** role, the conductor additionally loads the matching
|
|
60
|
+
`spec-producer-governance` / `solution-producer-governance` / `impl-producer-governance` — the
|
|
61
|
+
procedure it runs — which itself references the bars above; a plugin delegate carries its own
|
|
62
|
+
procedure and loads these bars directly.
|
|
63
|
+
|
|
64
|
+
The `sdd` gateway loads **no** governance (it only classifies and routes). The gate skill
|
|
65
|
+
`spec-gate` loads `lifecycle`, `ownership`, `gate-validation`, `combat-log` (+ `spec-format` /
|
|
66
|
+
`suite-format` at the spec gate). A plugin's agents inherit the universal loads — e.g. `aced`/`quill`
|
|
67
|
+
spec-producers load `sdd:spec-format-governance` + `sdd:ownership-governance`, and their judges load
|
|
68
|
+
`sdd:gate-validation-governance`.
|
|
69
|
+
|
|
70
|
+
## Registry shape
|
|
71
|
+
|
|
72
|
+
The conductor reads **only** `.agents/universal-plugin.json` (top-level `sdd-plugins[]`) — it does
|
|
73
|
+
not scan plugin directories. Each entry:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"name": "<plugin>",
|
|
78
|
+
"version": "<semver>",
|
|
79
|
+
"squads": [
|
|
80
|
+
{
|
|
81
|
+
"artifact-types": ["<artifact-type>", "..."],
|
|
82
|
+
"roles": {
|
|
83
|
+
"spec-producer": "<agent | null>",
|
|
84
|
+
"solution-producer": "<agent | null>",
|
|
85
|
+
"spec-judge": "<agent | null>",
|
|
86
|
+
"impl-producer": "<agent | null>",
|
|
87
|
+
"impl-judge": "<agent | null>"
|
|
88
|
+
},
|
|
89
|
+
"governances": {
|
|
90
|
+
"oracle-spec": "<name | null>",
|
|
91
|
+
"builder-spec": "<name | null>",
|
|
92
|
+
"builder-impl": "<name | null>",
|
|
93
|
+
"architect-spec": "<name | null>",
|
|
94
|
+
"architect-impl": "<name | null>"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A plugin declares one or more **squads**, each serving a **set of artifact-types** with one
|
|
102
|
+
production chain; a type appears in at most one squad per plugin (the plugin's served set = the union
|
|
103
|
+
of its squads' `artifact-types`).
|
|
104
|
+
|
|
105
|
+
Resolution: match each file's **`artifact-type`** (the squad key, **not** the folder name) against
|
|
106
|
+
each plugin's `squads[]` — the squad whose `artifact-types` contains it serves the file (e.g. ACED's
|
|
107
|
+
one squad covers `skill`, `subagent`, `command`, `agents-section`). An absent or unmatched
|
|
108
|
+
`artifact-type` → all roles degenerate to SDD defaults. One matching squad → resolve each role and
|
|
109
|
+
governance key (name = use it; `null` = SDD default; missing role key = `<plugin>-<role>`). Two or
|
|
110
|
+
more plugins claiming the type → return `STATUS: needs-input` for the skill to ask which plugin owns
|
|
111
|
+
it; the choice is recorded as `.agents/sdd/` resolution state (**distinct from `produced-by`**),
|
|
112
|
+
decisive on resume.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# remediation-governance
|
|
2
|
+
|
|
3
|
+
This is an internal SDD governance about **what a producer does when a gate sends work back**.
|
|
4
|
+
|
|
5
|
+
A gate returns one of three verdicts. `approve` and `reject` end the round. **`change`** sends the
|
|
6
|
+
work back with findings — and this bar governs what happens next.
|
|
7
|
+
|
|
8
|
+
Its single claim: **the findings are evidence, not a work order.** The tempting response is to read
|
|
9
|
+
them as a task list and edit each cited line. That fixes the lines and leaves the defect, because a
|
|
10
|
+
judge can only ever name the instances it happened to see.
|
|
11
|
+
|
|
12
|
+
## What it requires — the four rules
|
|
13
|
+
|
|
14
|
+
| Rule | What it means |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| **Substantiate first** | A finding is a claim about the artifact, and claims can be wrong. Check it before changing anything. One that does not hold is **contested** — send back the evidence and change nothing. |
|
|
17
|
+
| **State the rule, then sweep** | The finding names one instance; the actual defect is the rule it breaks. Name that rule, then search for every other place it is broken — in a script, so anyone can re-run it. Report what you looked at and **ruled out**, not only what you found. |
|
|
18
|
+
| **Re-derive against the governing rule** | Check the fix against the rule that governs the artifact, not just against the finding. "Does it still trip the finding?" is weak. "Is what it now says **true**?" is the question — a fix that satisfies the finding while breaking a rule the artifact is bound by is worse than the original defect. |
|
|
19
|
+
| **Account for provenance** | For each finding, ask whether the artifact it names was changed by the **last** round of fixes. If so it is a **regression** — the fixes are creating work rather than finishing it, and the loop stops for a re-plan instead of running again. |
|
|
20
|
+
|
|
21
|
+
## Sweeping is not string-matching
|
|
22
|
+
|
|
23
|
+
The most common way rule 2 goes wrong is a blanket search. A term retired in one place may be
|
|
24
|
+
correct in another, and the same word may name a different thing in a sibling project. Three
|
|
25
|
+
separations matter: **use vs mention** (deploying a retired term versus naming it *to say* it is
|
|
26
|
+
deprecated), **scope** (the live spec is bound; ADRs and ledger lines are history and are never
|
|
27
|
+
rewritten), and **word boundaries** (a substring is not an instance).
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
- **spec-producer:** responding to a `change` verdict at the **spec gate**
|
|
32
|
+
- **impl-producer:** responding to a `change` verdict at the **impl gate**
|
|
33
|
+
|
|
34
|
+
Both return the trace — rule, sweep hits, ruled-out candidates, provenance — in their `Output`, which
|
|
35
|
+
is what lets the next judge check the remediation instead of taking it on trust.
|
|
36
|
+
|
|
37
|
+
## Related governances
|
|
38
|
+
|
|
39
|
+
- **`spec-producer-governance`** / **`impl-producer-governance`** — the producers that load this bar;
|
|
40
|
+
they own *how the work is done*, this bar owns *how a returned verdict is answered*.
|
|
41
|
+
- **`gate-validation-governance`** — which gate states are legal; this bar is silent on state and
|
|
42
|
+
governs only the response.
|
|
43
|
+
- **`suite-format-governance`** / **`spec-format-governance`** — the rules a correction is re-derived
|
|
44
|
+
against under rule 3.
|
|
45
|
+
|
|
46
|
+
Internal SDD governance (`user-invocable: false`). Not triggered by users directly.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: remediation-governance
|
|
3
|
+
description: "Partial Skill: invoke by name only — the SDD remediation bar: how a producer responds to a `change` verdict at either gate. Loaded by the spec-producer and the impl-producer, not user-triggered."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Remediation Governance — responding to a `change` verdict
|
|
8
|
+
|
|
9
|
+
A gate verdict's findings are **evidence to reason from**, never a task list to execute. Working down
|
|
10
|
+
the list edit-by-edit fixes cited lines while leaving the defect, and can introduce defects the next
|
|
11
|
+
round then reports.
|
|
12
|
+
|
|
13
|
+
This bar applies at **both** gates — the spec gate and the impl gate — and is loaded by whichever
|
|
14
|
+
producer is responding.
|
|
15
|
+
|
|
16
|
+
## The four rules
|
|
17
|
+
|
|
18
|
+
1. **Substantiate each finding first.** A finding is a **hypothesis**. Verify it against the artifact
|
|
19
|
+
before touching anything. One you cannot substantiate is **contested** — return your evidence and
|
|
20
|
+
edit nothing. Fixing an unverified finding is how a vague line becomes a wrong one.
|
|
21
|
+
2. **State the rule, then sweep.** A judge names an **instance**; the defect is the **rule**. Name
|
|
22
|
+
the rule the finding instantiates and sweep for every other instance — in a script, so the result
|
|
23
|
+
is reproducible. Return the sweep's **negative** half too: the candidates inspected and excluded,
|
|
24
|
+
so the next reader need not re-run it.
|
|
25
|
+
3. **Re-derive the correction against the rule governing the artifact**, not merely against the
|
|
26
|
+
finding. "Does this still trip the finding?" is the weak question. "Is what it now says **true**?"
|
|
27
|
+
is the one that matters — a correction that clears the finding while contradicting a governance
|
|
28
|
+
the artifact is bound by is a worse defect than the one it replaced.
|
|
29
|
+
4. **Account for each finding's provenance.** A finding is a **regression** when the artifact it
|
|
30
|
+
names was changed by the **previous remediation round's commits**; it is **pre-existing** when the
|
|
31
|
+
artifact predates them. Any regression means the loop is **no longer converging**: stop, report
|
|
32
|
+
it, and re-plan. Do not open another remediation round on a regressing loop.
|
|
33
|
+
|
|
34
|
+
## A sweep is scope-aware, never a blanket match
|
|
35
|
+
|
|
36
|
+
Rule 2's sweep answers "every instance of the rule", which is **not** "every occurrence of a string".
|
|
37
|
+
Before acting on a sweep, separate:
|
|
38
|
+
|
|
39
|
+
- **use vs mention** — a retired term deployed as if current is the defect; the same term named *in
|
|
40
|
+
order to* mark it deprecated, or preserved in an append-only record, is correct.
|
|
41
|
+
- **scope** — the live project spec is bound by the rule; ADRs and ledger lines are history and are
|
|
42
|
+
never rewritten to match current vocabulary; a sibling tree may use the same word for a different
|
|
43
|
+
concept.
|
|
44
|
+
- **word boundaries** — a substring hit is not an instance.
|
|
45
|
+
|
|
46
|
+
Report the excluded candidates with the reason each was excluded. A sweep that reports only its hits
|
|
47
|
+
cannot be checked, and invites the next producer to re-run it.
|
|
48
|
+
|
|
49
|
+
## What the producer returns
|
|
50
|
+
|
|
51
|
+
Remediation is **verifiable only if it leaves a trace**. Each finding answered carries, in the
|
|
52
|
+
producer's `Output`:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
REMEDIATION:
|
|
56
|
+
<finding>: verdict=<remediated | contested>
|
|
57
|
+
rule=<the rule the finding instantiates>
|
|
58
|
+
swept=<the other instances found, or none>
|
|
59
|
+
ruled-out=<candidates inspected and excluded, with the reason>
|
|
60
|
+
provenance=<pre-existing | regression>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
A `contested` finding carries the evidence against it and **no edit** to the artifact it named.
|
|
64
|
+
|
|
65
|
+
## Key points (read-check)
|
|
66
|
+
|
|
67
|
+
1. **A verdict is evidence, not a work order** — remediating cited lines one at a time is the defect
|
|
68
|
+
this bar exists to prevent.
|
|
69
|
+
2. **Substantiate before acting** — an unsubstantiated finding is contested with evidence, not edited
|
|
70
|
+
away.
|
|
71
|
+
3. **A finding names an instance; the defect is the rule** — sweep for every instance, and report the
|
|
72
|
+
ruled-out candidates as well as the hits.
|
|
73
|
+
4. **A sweep is scope-aware** — use vs mention, scope, and word boundaries; a string match is not an
|
|
74
|
+
instance.
|
|
75
|
+
5. **Re-derive the correction against the rule governing the artifact**, not against the finding
|
|
76
|
+
alone.
|
|
77
|
+
6. **Provenance is derived from the diff** — an artifact changed by the previous round's commits
|
|
78
|
+
makes its finding a **regression**, which stops the loop for a re-plan rather than another round.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# resolve-governances
|
|
2
|
+
|
|
3
|
+
The concrete engine for **SDD governance resolution**.
|
|
4
|
+
A non-user-invocable skill carrying a self-contained `.mts` script that, for a touched file's
|
|
5
|
+
artifact-type, resolves each production-chain role to its agent plus the resolved-actor bars it loads
|
|
6
|
+
— matching candidates across the project's `.agents/governances/` anchors, the matched plugin squad,
|
|
7
|
+
and the sdd defaults.
|
|
8
|
+
|
|
9
|
+
- **Skill contract:** [`SKILL.md`](./SKILL.md)
|
|
10
|
+
- **Script:** [`scripts/resolve-governances.mts`](./scripts/resolve-governances.mts)
|
|
11
|
+
- **Tests:** [`scripts/resolve-governances.test.mts`](./scripts/resolve-governances.test.mts) (`node:test`)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
node scripts/resolve-governances.mts --root . --artifact-type skill
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Consumed by the conductor (`start-mission`) and the cold judges (`sdd-spec-judge`, `sdd-impl-judge`)
|
|
18
|
+
to load the right bars without hand-enumerating.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: resolve-governances
|
|
3
|
+
description: "Partial Skill: invoke by name only — the SDD governance matcher, resolving which actor-bar governances apply to a touched file — run by the conductor and the cold spec/impl judges, not triggered by users directly."
|
|
4
|
+
user-invocable: false
|
|
5
|
+
metadata:
|
|
6
|
+
internal: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Resolve Governances
|
|
10
|
+
|
|
11
|
+
The concrete engine for **SDD governance resolution**. For a
|
|
12
|
+
touched file's **artifact-type** it names, per production-chain role, **which agent runs it** and
|
|
13
|
+
**which resolved-actor bar candidates it loads** — matching governances across the caller-passed
|
|
14
|
+
project anchors, the matched plugin squad (from the project registry
|
|
15
|
+
`.agents/universal-plugin.json`), and the sdd defaults. It is a **dumb matcher**: it returns each
|
|
16
|
+
bar's candidates **bucketed by tier** and does **not** order by precedence or apply `compose` — the
|
|
17
|
+
consuming agent composes. The conductor and the cold judges run it so they **never hand-enumerate**
|
|
18
|
+
the bars. It carries a self-contained `.mts` script (the repo's node-≥23.6 / no-deps convention).
|
|
19
|
+
|
|
20
|
+
## Run the resolution
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node "<skill>/scripts/resolve-governances.mts" --root . --artifact-type <type> --project <path> [--project-root <path>]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `--root` is the **registry** location (default `.`) — `.agents/universal-plugin.json`.
|
|
27
|
+
- `--project <path>` is the file's own project anchor (defaults to `--root`); `--project-root <path>`
|
|
28
|
+
is the outer shared layer in a **monorepo** (omit for a single-project repo). Anchors are
|
|
29
|
+
**caller-passed**, never discovered — the conductor knows the project from `discover-specs`'
|
|
30
|
+
`project-path` or context.
|
|
31
|
+
- `--artifact-type <type>` emits the per-role plan as JSON: each role carries its resolved `agent`
|
|
32
|
+
and the **resolved-actor `bars`** only. Each bar's `candidates` are **bucketed by tier** —
|
|
33
|
+
`project` / `project-root` (direct-read file paths) and `plugin` / `sdd` (`<plugin>:<bar>` /
|
|
34
|
+
`sdd:<name>` harness-load refs). The **fixed-universal** governances are invariant per role and
|
|
35
|
+
stay declared in the role/agent definition — the matcher does not emit them.
|
|
36
|
+
- `--path <file>` (no `--artifact-type`) consults the optional tiebreaker map
|
|
37
|
+
`.agents/sdd/artifact-types.toml`; a no-match prints a classify-by-convention note.
|
|
38
|
+
- No `--artifact-type` and no `--path` → validates the registry is well-formed + unambiguous
|
|
39
|
+
(`governance registry OK`, or per-line violations).
|
|
40
|
+
|
|
41
|
+
When `node` is absent, an agent performs the same matching by hand: read the registry, match each
|
|
42
|
+
touched file's artifact-type to a squad, and name each role's agent + bar candidates per tier.
|
|
43
|
+
|
|
44
|
+
## Boundaries
|
|
45
|
+
|
|
46
|
+
It owns no lifecycle state and writes nothing — it **names** candidates; the consuming agent loads
|
|
47
|
+
each (direct-read for project files, harness-load for plugin/sdd skills), reads each governance's own
|
|
48
|
+
`compose`, and composes by precedence `sdd-default < plugin < project-root < project` (most-specific
|
|
49
|
+
wins; `replace` supersedes). Registry matching is deterministic; **disambiguating** an artifact-type
|
|
50
|
+
claimed by two plugins is the consumer's agentic step (the plan returns `status: needs-input`).
|