shapeup-sdlc 1.6.3 → 3.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/settings.local.example.json +5 -5
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +54 -107
- package/README.md +165 -151
- package/SECURITY.md +49 -27
- package/bin/init.mjs +93 -108
- package/bin/lib/grant.mjs +145 -0
- package/commands/build.md +20 -0
- package/commands/eval.md +5 -4
- package/commands/scopes.md +5 -4
- package/commands/shape.md +1 -1
- package/commands/ship.md +53 -7
- package/commands/wire.md +1 -1
- package/hooks/dispatch-receipt.mjs +195 -0
- package/hooks/gate-intake.mjs +16 -17
- package/hooks/gate-zerowork.mjs +107 -25
- package/hooks/hooks.json +9 -48
- package/hooks/lib/decision.mjs +38 -19
- package/hooks/safety-spine.mjs +4 -4
- package/hooks/sandbox-guard.mjs +130 -50
- package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +344 -55
- package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +69 -26
- package/kernel/harness.mjs +134 -0
- package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +25 -22
- package/kernel/init/run.mjs +489 -0
- package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
- package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +261 -17
- package/kernel/lib/paths.mjs +491 -0
- package/kernel/probe/concurrency.mjs +510 -0
- package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +10 -11
- package/kernel/probe/eval.mjs +77 -0
- package/kernel/probe/leg.mjs +125 -0
- package/kernel/probe/resume.mjs +528 -0
- package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +102 -21
- package/kernel/probe/t0.mjs +66 -0
- package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +104 -28
- package/kernel/reduce/graph.mjs +437 -0
- package/kernel/reduce/hill.mjs +152 -0
- package/kernel/reduce/ingest.mjs +633 -0
- package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
- package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +93 -14
- package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +22 -21
- package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +14 -8
- package/kernel/report/export.mjs +325 -0
- package/kernel/report/facts.mjs +347 -0
- package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +22 -25
- package/kernel/verify/dispatch.mjs +114 -0
- package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
- package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
- package/kernel/verify/skills.mjs +125 -0
- package/kernel/verify/spec.mjs +559 -0
- package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +138 -28
- package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +28 -13
- package/oracles/_shared.mjs +1 -1
- package/oracles/http-oracle.mjs +2 -2
- package/oracles/index.mjs +1 -1
- package/oracles/process-oracle.mjs +2 -2
- package/oracles/snapshot-oracle.mjs +2 -2
- package/oracles/test-oracle.mjs +2 -2
- package/package.json +11 -13
- package/skills/ba-pitch-analyzer/SKILL.md +24 -19
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +3 -1
- package/skills/ba-pitch-analyzer/references/task-generation.md +8 -8
- package/skills/ba-pitch-analyzer/references/test-surface.md +1 -1
- package/skills/coach/SKILL.md +3 -3
- package/skills/orient/SKILL.md +2 -1
- package/skills/qa-edge-hunter/SKILL.md +15 -41
- package/skills/scope-architect/SKILL.md +57 -18
- package/skills/scope-hammer/SKILL.md +5 -5
- package/skills/shapeup/SKILL.md +3 -3
- package/skills/shapeup/resources/context-compaction.md +4 -3
- package/skills/solution-architect/SKILL.md +37 -15
- package/skills/spec-evaluator/SKILL.md +24 -7
- package/skills/spec-evaluator/references/dimensions/_registry.md +2 -2
- package/skills/spec-evaluator/references/dimensions/completeness.md +1 -1
- package/skills/spec-evaluator/references/dimensions/integration.md +98 -67
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +4 -4
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +1 -1
- package/skills/spec-evaluator/references/probing.md +42 -23
- package/skills/spec-evaluator/references/report-schema.md +2 -2
- package/skills/task-executor/SKILL.md +32 -18
- package/skills/tech-lead/SKILL.md +127 -438
- package/skills/tech-lead/references/gates.md +140 -49
- package/skills/tech-lead/references/protocol.md +832 -0
- package/skills/tech-lead/schemas/domain.schema.json +645 -198
- package/skills/tech-lead/schemas/gate-answers.schema.json +2 -2
- package/skills/tech-lead/schemas/work-order.schema.json +11 -2
- package/skills/tech-lead/schemas/work-result.schema.json +56 -18
- package/skills/tech-lead/workflows/shapeup-run.js +1474 -0
- package/skills/translator/SKILL.md +2 -2
- package/hooks/anti-rationalization.mjs +0 -244
- package/hooks/compact-snapshot.mjs +0 -47
- package/hooks/gate-deadline.mjs +0 -151
- package/hooks/gate-l2.mjs +0 -161
- package/hooks/session-rehydrate.mjs +0 -109
- package/skills/advisor-protocol/SKILL.md +0 -171
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
- package/skills/spec-evaluator/README.md +0 -93
- package/skills/tech-lead/README.md +0 -71
- package/skills/tech-lead/references/delegation.md +0 -254
- package/skills/tech-lead/references/invocation.md +0 -45
- package/skills/tech-lead/references/ledger-schema.md +0 -214
- package/skills/tech-lead/references/round-protocol.md +0 -184
- package/skills/tech-lead/references/state-model.md +0 -66
- package/skills/tech-lead/scripts/ingest-result.mjs +0 -270
- package/skills/tech-lead/scripts/init-run.mjs +0 -326
- package/skills/tech-lead/scripts/lib/is-main.mjs +0 -82
- package/skills/tech-lead/scripts/lib/paths.mjs +0 -278
- package/skills/translator/README.md +0 -66
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Session rehydrate — SessionStart hook, matcher "startup|compact|resume|clear".
|
|
3
|
-
//
|
|
4
|
-
// Fires when a session begins with a harness run already in flight, and injects the RunSnapshot's
|
|
5
|
-
// rehydrate_hint as additionalContext: re-derive round/attempt/hill from the files, never from
|
|
6
|
-
// memory. This is the reflex that makes the file-first run-state load-bearing at the exact moment
|
|
7
|
-
// the in-context copy is absent or degraded: without it the orchestrator can re-dispatch an
|
|
8
|
-
// already-ingested order, miscount attempts off a lossy summary, or — measured, below — re-open a
|
|
9
|
-
// run that is already open and rebuild the whole pipeline from phase 1.
|
|
10
|
-
//
|
|
11
|
-
// v1.4.1 — `startup` ADDED TO THE MATCHER, and this is a measured correction, not a widening.
|
|
12
|
-
//
|
|
13
|
-
// The matcher was `compact|resume`. Both of those are continuations of a conversation that still
|
|
14
|
-
// exists. The commonest continuity event in real use has neither: you close the terminal and come
|
|
15
|
-
// back tomorrow, or a teammate picks the work up in a fresh checkout. The CLI reports that as
|
|
16
|
-
// `SessionStart:startup`, and the reflex whose entire purpose is "trust the files, not your
|
|
17
|
-
// memory" did not fire in the one case where there IS no memory to distrust.
|
|
18
|
-
//
|
|
19
|
-
// The SDD harness benchmark measured the cost. Its F4 handoff design is exactly this scenario — a
|
|
20
|
-
// fresh `claude -p` in a workspace where a prior session was cut mid-build — and across three
|
|
21
|
-
// Sonnet rows every one recorded `hooks_fired: ["SessionStart:startup"]` (the plugin's load echo,
|
|
22
|
-
// not this hook). With no pointer to the run in flight, the orchestrator re-entered at phase 1:
|
|
23
|
-
// 82–120 turns before its first write, $4.57–$10.36 for the session, and **0/3 of the gap closed**
|
|
24
|
-
// while the run receipt and board sat on disk the whole time. One row reached GATE L4 — ship
|
|
25
|
-
// sign-off — having advanced the deliverable by zero criteria.
|
|
26
|
-
//
|
|
27
|
-
// Firing on `startup` is free when there is nothing to say: `findRun` returns a run only for an
|
|
28
|
-
// `active-scope` pointer or a `harness-run.md` whose status is mid-run, so an ordinary session in a
|
|
29
|
-
// repo with no run — or with a shipped one — gets silence and exit 0, exactly as before.
|
|
30
|
-
//
|
|
31
|
-
// `clear` is included for the same reason: it discards the conversation and keeps the workspace,
|
|
32
|
-
// which is the same problem wearing a different name.
|
|
33
|
-
//
|
|
34
|
-
// Derivation is always FRESH (files beat any persisted copy); the run-snapshot.json written
|
|
35
|
-
// by hooks/compact-snapshot.mjs is only the fallback if live derivation throws. Fail-open:
|
|
36
|
-
// no active run, or any error → silent exit 0.
|
|
37
|
-
//
|
|
38
|
-
// Contract: SessionStart stdin JSON { cwd, source: "startup"|"resume"|"clear"|"compact" }.
|
|
39
|
-
// Inject via { hookSpecificOutput: { hookEventName: "SessionStart", additionalContext } }.
|
|
40
|
-
|
|
41
|
-
import { readFileSync } from "node:fs";
|
|
42
|
-
import { deriveSnapshot, snapshotPath } from "../skills/tech-lead/scripts/run-snapshot.mjs";
|
|
43
|
-
import { isMain } from "../skills/tech-lead/scripts/lib/is-main.mjs";
|
|
44
|
-
import { runHook, readStdin } from "./lib/decision.mjs";
|
|
45
|
-
import { activeScope } from "../skills/tech-lead/scripts/lib/paths.mjs";
|
|
46
|
-
|
|
47
|
-
// The snapshot's own hint says "trust the files, not the conversation summary" — correct after a
|
|
48
|
-
// compaction, and slightly wrong on a cold start, where there is no summary and no conversation
|
|
49
|
-
// either. The two sources also fail in DIFFERENT directions, so they need different first sentences:
|
|
50
|
-
//
|
|
51
|
-
// compact / resume — the risk is acting on a lossy summary of work you remember doing.
|
|
52
|
-
// startup / clear — the risk is not knowing the run exists, and OPENING IT AGAIN. That is the
|
|
53
|
-
// failure the benchmark measured: a fresh session re-entered at phase 1 and
|
|
54
|
-
// spent 82–120 turns rebuilding a pipeline that was already on disk.
|
|
55
|
-
//
|
|
56
|
-
// Naming the right failure is the whole value of the injection. A generic pointer to the files is
|
|
57
|
-
// what a competent agent finds anyway; "there is a run open, do not re-open it" is not.
|
|
58
|
-
function lead(source) {
|
|
59
|
-
if (source === "startup" || source === "clear") {
|
|
60
|
-
return "A shapeup-sdlc run is ALREADY OPEN in this workspace and you have no memory of it. " +
|
|
61
|
-
"Do NOT open a new run, re-run intake, or restart the pipeline from phase 1 — the receipt, " +
|
|
62
|
-
"the board and the ledger already exist. RESUME from the phase the files say the run is in. ";
|
|
63
|
-
}
|
|
64
|
-
return "";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function main() {
|
|
68
|
-
await runHook("session-rehydrate", async () => {
|
|
69
|
-
const raw = await readStdin();
|
|
70
|
-
let p;
|
|
71
|
-
try { p = JSON.parse(raw || "{}"); }
|
|
72
|
-
catch (e) { return { verdict: "error", event: "SessionStart", reason: `unparseable payload: ${e.message}` }; }
|
|
73
|
-
const cwd = p.cwd || process.cwd();
|
|
74
|
-
|
|
75
|
-
let snapshot = null;
|
|
76
|
-
let source = "live";
|
|
77
|
-
try {
|
|
78
|
-
snapshot = deriveSnapshot(cwd);
|
|
79
|
-
} catch {
|
|
80
|
-
// Live derivation failed → fall back to the pre-compaction anchor, best effort.
|
|
81
|
-
source = "anchor";
|
|
82
|
-
try {
|
|
83
|
-
const pointer = JSON.parse(readFileSync(activeScope(cwd), "utf8"));
|
|
84
|
-
if (pointer?.slug) snapshot = JSON.parse(readFileSync(snapshotPath(cwd, pointer.slug), "utf8"));
|
|
85
|
-
} catch { /* no anchor either → stay silent */ }
|
|
86
|
-
}
|
|
87
|
-
if (!snapshot?.rehydrate_hint) {
|
|
88
|
-
return { verdict: "allow", event: "SessionStart", cwd, subject: p.source ?? null, reason: "no active run — nothing to rehydrate" };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
verdict: "allow", event: "SessionStart", cwd, rule: `injected:${source}`,
|
|
93
|
-
subject: snapshot.slug ?? p.source ?? null,
|
|
94
|
-
reason: `rehydrate_hint injected on SessionStart:${p.source ?? "?"}`,
|
|
95
|
-
// additionalContext is not a deny, but it IS output, so it rides the same channel.
|
|
96
|
-
payload: {
|
|
97
|
-
hookSpecificOutput: {
|
|
98
|
-
hookEventName: "SessionStart",
|
|
99
|
-
additionalContext: `${lead(p.source)}${snapshot.rehydrate_hint}\n${JSON.stringify(snapshot)}`,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
emit: true,
|
|
103
|
-
};
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (isMain(import.meta.url)) {
|
|
108
|
-
main();
|
|
109
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: advisor-protocol
|
|
3
|
-
description: "Use this skill whenever a harness worker (task-executor, scope-architect) hits a decision it cannot make alone during Build Vertically — a design decision, a spec ambiguity, or a request to write outside its scope's substrate — and needs it adjudicated under a budget instead of guessed or asked ad hoc. Trigger on: \"escalate this decision\", \"ESCALATE\", \"adjudicate this ambiguity\", \"ask the advisor\", \"substrate expansion request\", \"resolve this design decision within budget\", \"how many escalations does this scope have left\". Also triggers when tech-lead needs to answer a worker's structured ESCALATE return during a build round."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Advisor Protocol (ESCALATE grammar + budgets)
|
|
7
|
-
|
|
8
|
-
The mechanism behind Shape Up's "advisor, not micromanager" posture (design spec v1.1 DD-1,
|
|
9
|
-
DD-8). A worker mid-attempt hits something it genuinely cannot decide alone — not "what should
|
|
10
|
-
I name this variable" (that's Principle A, resolved silently or at the worker's own gate), but
|
|
11
|
-
a decision whose wrong guess is expensive: a design trade-off the spec left open, a spec
|
|
12
|
-
ambiguity that changes behavior, or a file the worker needs to touch outside its scope's
|
|
13
|
-
`allowed_file_substrate`. Instead of guessing or opening an unbounded chat with the PO, the
|
|
14
|
-
worker emits one structured `ESCALATE` return and this skill adjudicates it.
|
|
15
|
-
|
|
16
|
-
**Why this exists as its own skill, not inline PO chat.** Zero-memory handoff (design spec
|
|
17
|
-
§3.6) means every attempt gets a *fresh* context — no chat history. An answer given once must
|
|
18
|
-
survive every future reset for that scope. This skill is what turns a transient Q&A into a
|
|
19
|
-
persisted decision: it writes the answer to the round-ledger (committed, Tier A) the moment
|
|
20
|
-
it's made, and every future brief for that scope reads it back. Escalation memory lives in
|
|
21
|
-
files, not in chat (DD-8).
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## The ESCALATE grammar
|
|
26
|
-
|
|
27
|
-
A worker never asks a free-form question mid-attempt. It returns a structured block and stops
|
|
28
|
-
work on the blocked AC/file until an answer comes back:
|
|
29
|
-
|
|
30
|
-
```yaml
|
|
31
|
-
ESCALATE:
|
|
32
|
-
scope_id: cart-creation
|
|
33
|
-
round: 2
|
|
34
|
-
attempt: 3
|
|
35
|
-
kind: design-decision | spec-ambiguity | substrate-expansion
|
|
36
|
-
question: "Contract leaves cart-total rounding unspecified — round half-up or banker's rounding?"
|
|
37
|
-
options: ["round half-up (matches existing order-total code)", "banker's rounding (matches contract's other money fields)"]
|
|
38
|
-
default_if_silent: "round half-up" # optional — see --unattended below
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Three kinds, three different resolution paths (below). `default_if_silent` is optional — a
|
|
42
|
-
worker proposes it when it has a reasonable, low-risk fallback; the advisor is never forced to
|
|
43
|
-
accept it.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Budget
|
|
48
|
-
|
|
49
|
-
**≤3 ESCALATEs per scope per round** (design spec §3.3). Counted per `scope_id` + `round`, reset
|
|
50
|
-
each new round. The budget exists so a struggling scope surfaces as *stuck* (routed to the
|
|
51
|
-
hill's stuck-split rule, ≥3 rounds at the same position → forced scope-architect split order) rather than
|
|
52
|
-
draining PO attention one question at a time.
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
1st–3rd ESCALATE this scope/round → adjudicate normally (below).
|
|
56
|
-
4th+ → do NOT ask again. Apply the most conservative resolution
|
|
57
|
-
(narrowest scope, no substrate expansion, closest precedent)
|
|
58
|
-
and flag it as a GATE-H hammer proposal: "scope exceeded its
|
|
59
|
-
escalation budget — question N unresolved, applied [X]".
|
|
60
|
-
The worker's attempt continues with that resolution.
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Exceeding budget is never a hard stop — Build Vertically must keep moving; it's a signal that
|
|
64
|
-
this scope needs GATE H's attention, not a excuse to freeze the round.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## Adjudication (in order — cheapest resolution wins)
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
1. Precedent check (free, no PO time):
|
|
72
|
-
Search this run's round-ledger.md "Decisions" section (this scope AND sibling scopes) and
|
|
73
|
-
shapeup/knowledge-base/*.md for an already-answered question that is the same or a
|
|
74
|
-
clear superset of this one. Found → reuse verbatim, log "resolved by precedent: [ref]",
|
|
75
|
-
do NOT count against budget (a repeat question is a zero-memory artifact, not a new decision).
|
|
76
|
-
|
|
77
|
-
2. kind: substrate-expansion:
|
|
78
|
-
Never silently approved. Present the requested path(s) + why the worker says it needs them.
|
|
79
|
-
PO/TL approves → dispatch a scope-architect remap order to add the path to the scope contract's
|
|
80
|
-
`shared_substrate` (never hand-edit the contract — `ba` is its sole writer, F.5). PO/TL
|
|
81
|
-
declines → worker re-plans within its existing substrate; log the decline as a decision too
|
|
82
|
-
(prevents re-asking).
|
|
83
|
-
|
|
84
|
-
3. kind: design-decision | spec-ambiguity, interactive/--auto:
|
|
85
|
-
Print the ESCALATE block verbatim + budget remaining for this scope/round. Ask (max 1):
|
|
86
|
-
pick an option / provide a different answer / defer to default_if_silent if offered.
|
|
87
|
-
Never answer on the PO's behalf — a wrong silent guess here is exactly what zero-memory
|
|
88
|
-
handoff makes expensive to unwind later.
|
|
89
|
-
|
|
90
|
-
4. --unattended (no PO available):
|
|
91
|
-
default_if_silent present → apply it, log "auto-resolved (default, unattended)".
|
|
92
|
-
No default offered → apply the most conservative option (narrowest scope / least
|
|
93
|
-
irreversible) and flag it as a GATE-H hammer proposal for PO review before ship — the
|
|
94
|
-
run must not block, but an unattended guess is never allowed to hide from the PO forever.
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Persistence — the decision must survive a reset
|
|
100
|
-
|
|
101
|
-
The instant an ESCALATE is answered (any path above), append one row to the run's committed
|
|
102
|
-
`round-ledger.md` "Decisions" section — never batch it for later, never keep it only in the
|
|
103
|
-
current session (a crash before the next promotion point would lose it):
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
| Round | Scope | Kind | Question | Answer | Resolved by |
|
|
107
|
-
|-------|-------|------|----------|--------|-------------|
|
|
108
|
-
| 2 | cart-creation | design-decision | cart-total rounding | round half-up | PO (interactive) |
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Every subsequent isolated brief for that scope (task-executor's zero-memory handoff, design
|
|
112
|
-
spec §3.6) includes the full decisions table for its `scope_id` — this is how an answer given
|
|
113
|
-
once in round 2 is still known in round 5's fresh-context attempt without replaying any chat
|
|
114
|
-
history.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Envelope contract — the domain layer
|
|
119
|
-
|
|
120
|
-
Orchestrated, this skill is dispatched like every worker: a **WorkOrder** in (`--order <path>`,
|
|
121
|
-
operation `adjudicate`), a **WorkResult** out. The standalone flags below map 1:1 onto the
|
|
122
|
-
payload fields registered for this worker in the central domain registry
|
|
123
|
-
(`skills/tech-lead/schemas/domain.schema.json`, `x-payload-by-worker`):
|
|
124
|
-
|
|
125
|
-
| Payload field | Standalone flag | Meaning |
|
|
126
|
-
|---|---|---|
|
|
127
|
-
| `payload.ledger` | `--ledger` | The run's committed `round-ledger.md` — every answer persists here |
|
|
128
|
-
| `payload.escalate` | `--escalate` | The worker's ESCALATE block to adjudicate (the `EscalateBlock` grammar above) |
|
|
129
|
-
| `payload.scope_id` | `--scope` | The scope the budget is counted against |
|
|
130
|
-
| `payload.round` | `--round` | The round the budget/decision is counted against |
|
|
131
|
-
|
|
132
|
-
The WorkResult may carry only `files_touched`, `artifacts`, `assumptions`, `deviations`
|
|
133
|
-
(`x-result-by-worker`): the adjudicated Decision itself persists to the committed round-ledger,
|
|
134
|
-
never to the envelope, so it survives every zero-memory reset.
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## Invocation
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
# Adjudicate one ESCALATE return from a worker (typical: tech-lead calls this mid-round)
|
|
142
|
-
/advisor-protocol --ledger shapeup/checkout-vnpay/round-ledger.md --escalate '<ESCALATE yaml>'
|
|
143
|
-
|
|
144
|
-
# Check remaining budget for a scope this round (worker or tech-lead, before emitting ESCALATE)
|
|
145
|
-
/advisor-protocol --budget --scope cart-creation --round 2 --ledger shapeup/checkout-vnpay/round-ledger.md
|
|
146
|
-
|
|
147
|
-
# Headless run — no PO available; apply defaults / conservative fallback, flag the rest for GATE H
|
|
148
|
-
/advisor-protocol --unattended --ledger ... --escalate '<ESCALATE yaml>'
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Flags
|
|
152
|
-
| Flag | Effect |
|
|
153
|
-
|------|--------|
|
|
154
|
-
| `--ledger <path>` | The run's committed `round-ledger.md` (decisions persist here) |
|
|
155
|
-
| `--escalate <yaml\|path>` | The worker's ESCALATE block to adjudicate |
|
|
156
|
-
| `--budget --scope <id> --round <N>` | Report remaining ESCALATE budget for a scope this round, no adjudication |
|
|
157
|
-
| `--unattended` | No PO available — apply defaults/conservative fallback per rule 4 above |
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Hard Rules (never override without explicit user instruction)
|
|
162
|
-
|
|
163
|
-
| Rule | Rationale |
|
|
164
|
-
|------|-----------|
|
|
165
|
-
| A worker never guesses a design decision or spec ambiguity silently | The whole point of ESCALATE — wrong silent guesses are the expensive failure mode zero-memory handoff amplifies |
|
|
166
|
-
| Substrate-expansion is never auto-approved | PA3 (cross-scope contamination) exists precisely because agents self-justify "just this once" writes |
|
|
167
|
-
| Budget is ≤3/scope/round; exceeding it never blocks the round | Signals a stuck scope to GATE L2's stuck-split rule instead of freezing Build Vertically |
|
|
168
|
-
| Every answer is persisted to the committed round-ledger the instant it's given | Zero-memory handoff means the *only* place a decision survives is a file, not a session |
|
|
169
|
-
| Precedent reuse never counts against budget | A repeat question is a zero-memory artifact, not a new decision — don't penalize the worker for context loss |
|
|
170
|
-
| An unattended conservative resolution is always flagged for GATE H | An automated guess must stay visible to the PO before ship, never silently absorbed |
|
|
171
|
-
| This skill never designs, builds, or judges | Advisor, not a fifth worker — it adjudicates, it does not decide scope or verify code |
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Spec lint (pure-skill architecture v1.0, plan §8.2).
|
|
3
|
-
//
|
|
4
|
-
// The mechanical half of the old ba-pitch-analyzer Phase 7a self-audit + Phase 7c parse steps
|
|
5
|
-
// + Phase 6b PA1/PA2 lints — checkbox walking and glob checks a model should never grade on
|
|
6
|
-
// its own output (a worker grading itself was always a judge-purity smell):
|
|
7
|
-
//
|
|
8
|
-
// PA1 a scope substrate aligned 1:1 with a single top-level directory (directory-thinking)
|
|
9
|
-
// PA2 a scope substrate resolving to more than the size cap (~15 files)
|
|
10
|
-
// DISJOINT a path matched by two scopes' allowed_file_substrate without BOTH declaring it
|
|
11
|
-
// in shared_substrate
|
|
12
|
-
// STRUCTURE spec tree completeness (usecases/ ≥1 UC, domain-model, UC ## Steps),
|
|
13
|
-
// unresolved wikilinks, task frontmatter completeness, unlocks edge-symmetry,
|
|
14
|
-
// depends_on referencing unknown tasks
|
|
15
|
-
// TIER-DIRECTION a committed (SHARED) spec doc wikilinking the LOCAL board ([[tasks/...]]).
|
|
16
|
-
// Persisted links flow LOCAL→SHARED only: task ids are machine-local (boards
|
|
17
|
-
// regenerate and renumber) and .shapeup/ is gitignored — a committed task
|
|
18
|
-
// link dangles on every fresh clone. Cite the UC or scope_id instead.
|
|
19
|
-
// UC-ANCHOR a task whose use_case_refs is empty or names a UC with no usecases/UC-*.md —
|
|
20
|
-
// the LOCAL→SHARED anchor must be complete (single-anchor rule; SPIKE/CHORE/
|
|
21
|
-
// DOCS/MIGRATION tasks anchor elsewhere and are exempt)
|
|
22
|
-
//
|
|
23
|
-
// Zero dependencies (glob matcher inlined from hooks/sandbox-guard.mjs). Judgment stays in the skill
|
|
24
|
-
// (gap severity, lens choice); this script only reports facts.
|
|
25
|
-
//
|
|
26
|
-
// Usage: node skills/ba-pitch-analyzer/scripts/spec-lint.mjs --slug <slug> [--cwd <dir>]
|
|
27
|
-
// Prints a JSON report. Exit 0 = no red findings, 1 = at least one red.
|
|
28
|
-
|
|
29
|
-
import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
30
|
-
import { resolve, join, relative } from "node:path";
|
|
31
|
-
import { parseBoard, deriveUnlocks } from "./board-derive.mjs";
|
|
32
|
-
import { isMain } from "../../tech-lead/scripts/lib/is-main.mjs";
|
|
33
|
-
import { runArgs } from "../../tech-lead/scripts/lib/argv.mjs";
|
|
34
|
-
import { LOCAL } from "../../tech-lead/scripts/lib/paths.mjs";
|
|
35
|
-
import { specDir, scopesDir, tasksDir } from "../../tech-lead/scripts/lib/paths.mjs";
|
|
36
|
-
import { readAllContracts, unreadableReason, SCOPE_CONTRACT } from "../../tech-lead/scripts/lib/contract-md.mjs";
|
|
37
|
-
|
|
38
|
-
// Inlined from hooks/sandbox-guard.mjs so this skill ships self-contained (a skill's scripts
|
|
39
|
-
// must not reach outside its own folder — channels that copy only skills/ would dangle).
|
|
40
|
-
/**
|
|
41
|
-
* Compile a substrate glob into an anchored RegExp (inlined from sandbox-guard so the skill ships
|
|
42
|
-
* self-contained). Supports single-star, double-star, and double-star-slash segment wildcards.
|
|
43
|
-
* @param {string} glob - The glob pattern.
|
|
44
|
-
* @returns {RegExp} A full-string (`^…$`) matcher for repo-relative paths.
|
|
45
|
-
*/
|
|
46
|
-
export function globToRegExp(glob) {
|
|
47
|
-
let re = "";
|
|
48
|
-
for (let i = 0; i < glob.length; i++) {
|
|
49
|
-
const c = glob[i];
|
|
50
|
-
if (c === "*") {
|
|
51
|
-
if (glob[i + 1] === "*") {
|
|
52
|
-
re += glob[i + 2] === "/" ? "(?:[^/]+/)*" : ".*";
|
|
53
|
-
i += glob[i + 2] === "/" ? 2 : 1;
|
|
54
|
-
} else re += "[^/]*";
|
|
55
|
-
} else if ("\\^$.|?+()[]{}".includes(c)) re += "\\" + c;
|
|
56
|
-
else re += c;
|
|
57
|
-
}
|
|
58
|
-
return new RegExp(`^${re}$`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const SIZE_CAP = 15;
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Recursively list repo-relative file paths under a root, skipping .git/node_modules/.shapeup.
|
|
65
|
-
* @param {string} root - The base the results are made relative to.
|
|
66
|
-
* @param {string} [dir=root] - Current directory being walked (callers omit it).
|
|
67
|
-
* @param {string[]} [acc=[]] - Accumulator (callers omit it).
|
|
68
|
-
* @returns {string[]} Repo-relative paths of every file found.
|
|
69
|
-
*/
|
|
70
|
-
function walkFiles(root, dir = root, acc = []) {
|
|
71
|
-
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
72
|
-
if (e.name === ".git" || e.name === "node_modules" || e.name === LOCAL) continue;
|
|
73
|
-
const p = join(dir, e.name);
|
|
74
|
-
if (e.isDirectory()) walkFiles(root, p, acc);
|
|
75
|
-
else acc.push(relative(root, p));
|
|
76
|
-
}
|
|
77
|
-
return acc;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Lint scope contracts for PA1 (directory-thinking), PA2 (size cap), and DISJOINT substrate overlap.
|
|
82
|
-
* @param {Array<{scope_id:string, topology_type?:string, allowed_file_substrate?:string[],
|
|
83
|
-
* shared_substrate?:string[]}>} scopes - The scope contracts.
|
|
84
|
-
* @param {string[]} repoFiles - Repo-relative file list the substrate globs resolve against.
|
|
85
|
-
* @returns {Array<{rule:string, level:("red"|"warn"), scope:string, detail:string}>} Findings; [] when clean.
|
|
86
|
-
*/
|
|
87
|
-
export function lintScopes(scopes, repoFiles) {
|
|
88
|
-
const findings = [];
|
|
89
|
-
/**
|
|
90
|
-
* Resolve a list of substrate globs to the repo files they match.
|
|
91
|
-
* @param {(string[]|undefined)} globs - Glob patterns (undefined → none).
|
|
92
|
-
* @returns {string[]} The repo-relative files matched by any glob (deduplication is the caller's).
|
|
93
|
-
*/
|
|
94
|
-
const resolveGlobs = (globs) => {
|
|
95
|
-
const res = (globs || []).map((g) => globToRegExp(g));
|
|
96
|
-
return repoFiles.filter((f) => res.some((r) => r.test(f)));
|
|
97
|
-
};
|
|
98
|
-
for (const s of scopes) {
|
|
99
|
-
const allowed = s.allowed_file_substrate || [];
|
|
100
|
-
// PA1 — directory-thinking: every glob confined to ONE layer directory (e.g. all of it
|
|
101
|
-
// under apps/web/). A flow slice crosses layers (apps/web/cart + apps/api/cart passes).
|
|
102
|
-
const layers = new Set(allowed.map((g) => g.split("/").slice(0, 2).join("/")));
|
|
103
|
-
if (allowed.length && layers.size === 1 && s.topology_type !== "CHOWDER") {
|
|
104
|
-
findings.push({ rule: "PA1", level: "red", scope: s.scope_id, detail: `substrate aligns 1:1 with '${[...layers][0]}/' — slice by flow, not by directory` });
|
|
105
|
-
}
|
|
106
|
-
// PA2 — resolved file count over the cap (chowder absorbs true strays).
|
|
107
|
-
const files = resolveGlobs(allowed);
|
|
108
|
-
if (files.length > SIZE_CAP && s.topology_type !== "CHOWDER") {
|
|
109
|
-
findings.push({ rule: "PA2", level: "warn", scope: s.scope_id, detail: `substrate resolves to ${files.length} files (cap ~${SIZE_CAP}) — consider splitting` });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// DISJOINT — pairwise overlap not covered by BOTH scopes' shared_substrate.
|
|
113
|
-
for (let i = 0; i < scopes.length; i++) {
|
|
114
|
-
for (let j = i + 1; j < scopes.length; j++) {
|
|
115
|
-
const a = scopes[i], b = scopes[j];
|
|
116
|
-
const filesA = new Set(resolveGlobs(a.allowed_file_substrate));
|
|
117
|
-
const overlap = resolveGlobs(b.allowed_file_substrate).filter((f) => filesA.has(f));
|
|
118
|
-
const sharedA = (a.shared_substrate || []).map(globToRegExp);
|
|
119
|
-
const sharedB = (b.shared_substrate || []).map(globToRegExp);
|
|
120
|
-
for (const f of overlap) {
|
|
121
|
-
const declared = sharedA.some((r) => r.test(f)) && sharedB.some((r) => r.test(f));
|
|
122
|
-
if (!declared) findings.push({ rule: "DISJOINT", level: "red", scope: `${a.scope_id}+${b.scope_id}`, detail: `${f} is in both substrates but not in both shared_substrate lists — PA3 waiting to happen` });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return findings;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Lint spec-tree completeness, wikilink resolution, tier-direction, task frontmatter/graph
|
|
131
|
-
* integrity (edge symmetry, dependency existence), and UC-anchor completeness.
|
|
132
|
-
* @param {{specDir:string, tasks:Array<object>}} input - The SHARED spec dir and the parsed board.
|
|
133
|
-
* @returns {Array<{rule:string, level:("red"|"warn"), detail:string}>} Findings; [] when clean.
|
|
134
|
-
*/
|
|
135
|
-
export function lintStructure({ specDir, tasks }) {
|
|
136
|
-
const findings = [];
|
|
137
|
-
const ucDir = join(specDir, "usecases");
|
|
138
|
-
if (!existsSync(join(specDir, "domain-model.md"))) findings.push({ rule: "STRUCTURE", level: "red", detail: "domain-model.md missing" });
|
|
139
|
-
const ucs = existsSync(ucDir) ? readdirSync(ucDir).filter((f) => /^UC-.*\.md$/.test(f)) : [];
|
|
140
|
-
if (!ucs.length) findings.push({ rule: "STRUCTURE", level: "red", detail: "usecases/ has no UC-*.md — nothing to build or grade against" });
|
|
141
|
-
for (const f of ucs) {
|
|
142
|
-
const body = readFileSync(join(ucDir, f), "utf8");
|
|
143
|
-
if (!/^##\s+Steps/m.test(body)) findings.push({ rule: "STRUCTURE", level: "warn", detail: `${f} has no ## Steps section` });
|
|
144
|
-
}
|
|
145
|
-
// Wikilinks in spec docs must resolve within the spec dir — and never cross the tier
|
|
146
|
-
// boundary: a SHARED doc linking the LOCAL board is the wrong direction by construction.
|
|
147
|
-
const specFiles = existsSync(specDir) ? walkFiles(specDir) : [];
|
|
148
|
-
const names = new Set(specFiles.map((f) => f.replace(/\.md$/, "")));
|
|
149
|
-
for (const f of specFiles.filter((x) => x.endsWith(".md"))) {
|
|
150
|
-
const body = readFileSync(join(specDir, f), "utf8");
|
|
151
|
-
for (const m of body.matchAll(/\[\[([^\]#|]+)/g)) {
|
|
152
|
-
const target = m[1].trim().replace(/\.md$/, "");
|
|
153
|
-
if (target.startsWith("tasks/")) {
|
|
154
|
-
findings.push({ rule: "TIER-DIRECTION", level: "red", detail: `${f} → [[${m[1].trim()}]] links the LOCAL board from a committed doc — links flow LOCAL→SHARED only; cite the UC or scope_id instead (task ids renumber per machine)` });
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (!names.has(target) && ![...names].some((n) => n.endsWith(`/${target}`) || n === target)) {
|
|
158
|
-
findings.push({ rule: "WIKILINK", level: "warn", detail: `${f} → [[${m[1].trim()}]] unresolved in spec dir` });
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
// Task frontmatter + graph integrity (edge symmetry = the mechanized KB-BA-001 check).
|
|
163
|
-
const ids = new Set(tasks.map((t) => t.id));
|
|
164
|
-
const derived = deriveUnlocks(tasks);
|
|
165
|
-
for (const t of tasks) {
|
|
166
|
-
for (const k of ["id", "status"]) if (!t[k] || t[k] === "unknown") findings.push({ rule: "TASK", level: "red", detail: `${t.file} missing frontmatter ${k}` });
|
|
167
|
-
for (const d of t.depends_on) if (!ids.has(d)) findings.push({ rule: "TASK", level: "red", detail: `${t.id} depends_on ${d} which does not exist` });
|
|
168
|
-
if (JSON.stringify([...t.unlocks].sort()) !== JSON.stringify(derived[t.id] || [])) {
|
|
169
|
-
findings.push({ rule: "EDGE-SYMMETRY", level: "red", detail: `${t.id} unlocks ${JSON.stringify(t.unlocks)} ≠ derived inverse ${JSON.stringify(derived[t.id])} — run board-derive.mjs --write` });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// UC-ANCHOR — the LOCAL→SHARED anchor must be complete: every implementation task names
|
|
173
|
-
// ≥1 UC (single-anchor rule, task-generation.md) and each named UC exists on disk.
|
|
174
|
-
// SPIKE/CHORE/DOCS/MIGRATION tasks anchor elsewhere (api_ref / linked_docs) — exempt.
|
|
175
|
-
const ucIds = new Set(ucs.map((f) => f.replace(/\.md$/, "")));
|
|
176
|
-
const anchorExempt = new Set(["spike", "chore", "docs", "migration"]);
|
|
177
|
-
for (const t of tasks) {
|
|
178
|
-
if (anchorExempt.has((t.type || "").toLowerCase())) continue;
|
|
179
|
-
const refs = t.use_case_refs || [];
|
|
180
|
-
if (!refs.length) {
|
|
181
|
-
findings.push({ rule: "UC-ANCHOR", level: "red", detail: `${t.id} has empty use_case_refs — every task must anchor into the committed spec (LOCAL→SHARED, single-anchor rule)` });
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
for (const r of refs) {
|
|
185
|
-
const ucId = r.replace(/^\[\[|\]\]$/g, "").replace(/^usecases\//, "");
|
|
186
|
-
if (!ucIds.has(ucId)) findings.push({ rule: "UC-ANCHOR", level: "red", detail: `${t.id} use_case_refs "${r}" does not resolve to usecases/${ucId}.md` });
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return findings;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Run the full spec lint (scopes + structure) for a slug.
|
|
194
|
-
* @param {{cwd:string, slug:string}} opts - Working root and feature slug.
|
|
195
|
-
* @returns {{slug:string, scopes:number, tasks:number, red:number, warn:number,
|
|
196
|
-
* findings:Array<object>}} Counts and the combined findings from {@link lintScopes} and
|
|
197
|
-
* {@link lintStructure}.
|
|
198
|
-
*/
|
|
199
|
-
export function lint({ cwd, slug }) {
|
|
200
|
-
const specRoot = specDir(cwd, slug);
|
|
201
|
-
const contracts = readAllContracts(scopesDir(cwd, slug), SCOPE_CONTRACT);
|
|
202
|
-
const scopes = contracts.map((c) => c.contract);
|
|
203
|
-
const tasks = parseBoard(tasksDir(cwd, slug));
|
|
204
|
-
const repoFiles = walkFiles(cwd);
|
|
205
|
-
const findings = [
|
|
206
|
-
// HD-001. A contract whose table this parser cannot see reads as a contract that declared no
|
|
207
|
-
// table, and every rule below then passes for the part it could not read. Loud, not empty.
|
|
208
|
-
...contracts
|
|
209
|
-
.map(({ contract, path }) => ({ reason: unreadableReason(contract), scope: contract.scope_id || path }))
|
|
210
|
-
.filter((x) => x.reason)
|
|
211
|
-
.map((x) => ({ rule: "CONTRACT-UNREADABLE", level: "red", scope: x.scope, detail: `${x.reason} — the rules below could not check what they could not read` })),
|
|
212
|
-
...lintScopes(scopes, repoFiles),
|
|
213
|
-
...lintStructure({ specDir: specRoot, tasks }),
|
|
214
|
-
];
|
|
215
|
-
return {
|
|
216
|
-
slug,
|
|
217
|
-
scopes: scopes.length,
|
|
218
|
-
tasks: tasks.length,
|
|
219
|
-
red: findings.filter((f) => f.level === "red").length,
|
|
220
|
-
warn: findings.filter((f) => f.level === "warn").length,
|
|
221
|
-
findings,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** The typed argv contract (see `skills/tech-lead/scripts/lib/argv.mjs`). */
|
|
226
|
-
export const ARGV_SPEC = {
|
|
227
|
-
usage: "spec-lint.mjs --slug <slug> [--cwd <dir>]",
|
|
228
|
-
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
229
|
-
slug: { type: "str", required: true },
|
|
230
|
-
cwd: { type: "path" },
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const isMainModule = isMain(import.meta.url);
|
|
234
|
-
if (isMainModule) {
|
|
235
|
-
const args = runArgs(ARGV_SPEC);
|
|
236
|
-
const report = lint({ cwd: resolve(args.cwd || process.cwd()), slug: args.slug });
|
|
237
|
-
console.log(JSON.stringify(report, null, 2));
|
|
238
|
-
process.exit(report.red > 0 ? 1 : 0);
|
|
239
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# spec-evaluator
|
|
2
|
-
|
|
3
|
-
The **judge** in a planner → generator → evaluator harness. Pairs with `task-executor`
|
|
4
|
-
(generator) and `ba-pitch-analyzer` (planner). Reads the same spec tree, exercises the
|
|
5
|
-
**running** app, returns a hard-threshold verdict + file:line bug list, and hands bugs back
|
|
6
|
-
to the generator. Skeptical by default; never marks a task `done`.
|
|
7
|
-
|
|
8
|
-
v0.1 evaluates one dimension only — **spec-conformance** (AC + Done-when + contract shapes +
|
|
9
|
-
non-go). Security / performance ship disabled, injectable later with zero core changes.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Resource map
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
spec-evaluator/
|
|
17
|
-
├── SKILL.md # entry point — frontmatter, GATE V0–V3, flags, hard rules
|
|
18
|
-
├── README.md # this file
|
|
19
|
-
└── references/
|
|
20
|
-
├── dimension-contract.md # ★ the injection interface every dimension implements
|
|
21
|
-
├── anti-leniency.md # skeptical posture — read before any verdict (GATE V2)
|
|
22
|
-
├── probing.md # Phase A — Playwright CLI + per-variant probe strategy
|
|
23
|
-
├── report-schema.md # Phase B — the EVAL-TASK-NNN.md handoff file
|
|
24
|
-
└── dimensions/
|
|
25
|
-
├── _registry.md # which dimensions are active + how to add one
|
|
26
|
-
├── spec-conformance.md # ✅ the only enabled dimension (correctness)
|
|
27
|
-
├── security.md # ⛔ disabled stub — worked example of the contract
|
|
28
|
-
└── performance.md # ⛔ disabled stub — worked example of the contract
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
| Resource | Loaded at | Purpose |
|
|
32
|
-
|----------|-----------|---------|
|
|
33
|
-
| `SKILL.md` | always | gate pipeline, invocation, authority rules |
|
|
34
|
-
| `references/dimension-contract.md` | GATE V0.5 | the interface; validates each dimension before running it |
|
|
35
|
-
| `references/dimensions/_registry.md` | GATE V0.5 | active set resolution (`--dimensions` overrides) |
|
|
36
|
-
| `references/dimensions/spec-conformance.md` | per task | the default correctness dimension |
|
|
37
|
-
| `references/dimensions/{security,performance}.md` | only if enabled | injectable stubs, off by default |
|
|
38
|
-
| `references/anti-leniency.md` | before GATE V2 | absence of evidence = FAIL; banned phrases |
|
|
39
|
-
| `references/probing.md` | Phase A | how to collect evidence (CLI > MCP, per variant) |
|
|
40
|
-
| `references/report-schema.md` | Phase B | the file the generator reads next |
|
|
41
|
-
|
|
42
|
-
★ = the load-bearing file for "spec now, more dimensions later."
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Install
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# Project scope (shareable via version control) — recommended
|
|
50
|
-
cp -r spec-evaluator <repo>/.claude/skills/
|
|
51
|
-
|
|
52
|
-
# or user scope (all your projects)
|
|
53
|
-
cp -r spec-evaluator ~/.claude/skills/
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Restart the session (or re-open the skills interface) so the new skill is discovered.
|
|
57
|
-
|
|
58
|
-
## Invoke
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
/spec-evaluator --spec shapeup/checkout-vnpay/spec/ --task TASK-007 # default: spec-conformance
|
|
62
|
-
/spec-evaluator --spec ... --task TASK-007.web # platform variant
|
|
63
|
-
/spec-evaluator --spec ... --task TASK-007.be --browser none # backend-only, no browser
|
|
64
|
-
/spec-evaluator --spec ... --task TASK-007 --dimensions spec-conformance,security # inject a dimension
|
|
65
|
-
/spec-evaluator --spec ... --task TASK-007 --single-pass --auto # one end pass, skip sign-off
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Pipeline position
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
ba-pitch-analyzer (planner) ─► task-executor (generator) ─► spec-evaluator (judge)
|
|
72
|
-
▲ │
|
|
73
|
-
└──── bug list (EVAL-TASK-NNN) ◄─┘ on FAIL
|
|
74
|
-
```
|
|
75
|
-
The evaluator writes `.shapeup/<slug>/evaluation/EVAL-<task_id>.md` (LOCAL run-trace root) and sets `eval_verdict` on the task.
|
|
76
|
-
`task-executor` owns `status: done`. Judge and doer stay separate by design.
|
|
77
|
-
|
|
78
|
-
## Inject a new dimension (zero core edits)
|
|
79
|
-
|
|
80
|
-
1. Copy the "Minimal valid dimension" block from `references/dimension-contract.md` to
|
|
81
|
-
`references/dimensions/<id>.md`; fill criteria, probes, threshold, bug template.
|
|
82
|
-
2. Set `enabled: true` in `references/dimensions/_registry.md` (or pass `--dimensions … ,<id>`).
|
|
83
|
-
3. Re-run. GATE V0.5 validates it against the contract and loads it; non-conforming files
|
|
84
|
-
are skipped with a warning, never half-run.
|
|
85
|
-
|
|
86
|
-
`applies_to` scopes a dimension to a lens / package / platform variant, so a `visual`
|
|
87
|
-
dimension can run only on `.web` tasks and `security` only on `.be`/`.shared`.
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Version
|
|
92
|
-
0.1 — initial template. GATE V0–V3, skeptical-by-default, single enabled dimension,
|
|
93
|
-
pluggable dimension contract, Playwright CLI probing, file:line bug handoff.
|