@ericrisco/rsc 3.7.5 → 4.0.1
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/README.md +16 -9
- package/manifest.json +48 -40
- package/package.json +1 -1
- package/scripts/lib/domains.js +1 -1
- package/scripts/lib/onboarding-apply.js +1 -1
- package/scripts/lib/onboarding.js +7 -7
- package/scripts/lib/retired-skills.js +4 -0
- package/scripts/lib/review-findings.js +3 -0
- package/scripts/lib/review-ledger.js +187 -0
- package/scripts/lib/risk.js +68 -0
- package/scripts/lib/spec-gate.js +10 -1
- package/scripts/rsc.js +104 -6
- package/scripts/spec-gate.js +13 -3
- package/skills/analyze/SKILL.md +3 -3
- package/skills/asdd/SKILL.md +183 -0
- package/skills/{sdd → asdd}/evals/README.md +10 -10
- package/skills/{sdd → asdd}/evals/cases.yaml +5 -5
- package/skills/{sdd → asdd}/references/model-routing.md +2 -2
- package/skills/building-agents/SKILL.md +1 -1
- package/skills/clarify/SKILL.md +59 -55
- package/skills/clarify/evals/cases.yaml +6 -7
- package/skills/constitution/SKILL.md +30 -16
- package/skills/constitution/evals/README.md +1 -1
- package/skills/constitution/evals/cases.yaml +1 -1
- package/skills/debug/SKILL.md +3 -3
- package/skills/fastapi/SKILL.md +1 -1
- package/skills/flutter/SKILL.md +1 -1
- package/skills/ftd/SKILL.md +26 -10
- package/skills/ftd/evals/cases.yaml +1 -1
- package/skills/ftd/evals/routing.json +4 -4
- package/skills/go/SKILL.md +1 -1
- package/skills/implement/SKILL.md +147 -132
- package/skills/implement/evals/cases.yaml +4 -2
- package/skills/implement/references/per-task-review.md +26 -24
- package/skills/implement/scripts/task-brief +28 -22
- package/skills/nextjs/SKILL.md +1 -1
- package/skills/parallel/SKILL.md +1 -1
- package/skills/plan/SKILL.md +3 -3
- package/skills/plan/evals/cases.yaml +1 -1
- package/skills/plan/references/plan-template.md +2 -2
- package/skills/postgresdb/SKILL.md +1 -1
- package/skills/react/SKILL.md +1 -1
- package/skills/review/SKILL.md +80 -41
- package/skills/sdd-init/SKILL.md +11 -9
- package/skills/ship/SKILL.md +58 -30
- package/skills/specify/SKILL.md +42 -41
- package/skills/specify/evals/cases.yaml +2 -2
- package/skills/specify/references/eliciting-requirements.md +1 -1
- package/skills/specify/references/spec-template.md +28 -6
- package/skills/suggest/SKILL.md +4 -4
- package/skills/tasks/SKILL.md +37 -33
- package/skills/tasks/evals/README.md +1 -1
- package/skills/tasks/evals/cases.yaml +3 -3
- package/skills/verify/SKILL.md +34 -18
- package/skills/verify/evals/README.md +2 -2
- package/skills/verify/evals/cases.yaml +2 -1
- package/skills/verify/references/generic-gate.md +43 -0
- package/skills/worktrees/SKILL.md +1 -1
- package/targets/clone-bootstrap.mjs +1 -1
- package/targets/hook-once.mjs +2 -2
- package/targets/sello.mjs +9 -3
- package/skills/sdd/SKILL.md +0 -250
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// `rsc risk` — how much review and whose approval a change needs, decided by WHAT it touches.
|
|
2
|
+
//
|
|
3
|
+
// ASDD reviews inside every task, so the depth of that review cannot be the agent's own call: the
|
|
4
|
+
// change sets the tier, never the author (Anthropic, "How the Claude Code team does code review").
|
|
5
|
+
// The table is the sello's (targets/sello.mjs), reused as is, and it works with the sello off.
|
|
6
|
+
// Spec: 02-DOCS/wiki/sdd/specs/asdd-v4.md.
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { NESTED_WORKTREES, SELLO_STATE_PATHS, classifyRisk, lensesRequired, readEffectiveConfig, trunkRef } from '../../targets/sello.mjs';
|
|
10
|
+
|
|
11
|
+
function git(root, args) {
|
|
12
|
+
const r = spawnSync('git', ['-C', root, '-c', 'core.quotePath=false', ...args], { encoding: 'utf8', windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
|
|
13
|
+
return r.status === 0 ? r.stdout : null;
|
|
14
|
+
}
|
|
15
|
+
const nul = (out) => (out || '').split('\0').filter(Boolean);
|
|
16
|
+
|
|
17
|
+
// Every path that differs from the base (committed, staged, unstaged or untracked). The base is the
|
|
18
|
+
// merge-base with `--base`, or with the trunk when none is given.
|
|
19
|
+
export function changedPaths(root, base = null) {
|
|
20
|
+
const cfg = readEffectiveConfig(root);
|
|
21
|
+
const ref = base || trunkRef(root, cfg);
|
|
22
|
+
if (!ref) return { ok: false, message: 'rsc: no base to compare against. Pass --base <ref> or the paths themselves: npx @ericrisco/rsc risk <path…>' };
|
|
23
|
+
const head = git(root, ['rev-parse', 'HEAD']);
|
|
24
|
+
const mb = head ? (git(root, ['merge-base', 'HEAD', ref]) || '').trim() : '';
|
|
25
|
+
if (!mb) return { ok: false, message: `rsc: cannot find a common ancestor with ${ref}. Pass --base <ref> or the paths themselves: npx @ericrisco/rsc risk <path…>` };
|
|
26
|
+
// --no-renames: a rename reports both paths, so moving a file OUT of a tier-2 place still counts.
|
|
27
|
+
// Nested worktrees are skipped only while UNTRACKED (another checkout sitting in the tree). Once
|
|
28
|
+
// committed, a path under them is part of the change and keeps its tier.
|
|
29
|
+
const untracked = () => nul(git(root, ['ls-files', '--others', '--exclude-standard', '-z'])).filter((n) => !NESTED_WORKTREES.test(n));
|
|
30
|
+
const diff = (from) => new Set([...nul(git(root, ['diff', '--name-only', '--no-renames', '-z', from])), ...untracked()]
|
|
31
|
+
.filter((n) => !SELLO_STATE_PATHS.includes(n)));
|
|
32
|
+
let names = diff(mb);
|
|
33
|
+
let shown = ref;
|
|
34
|
+
// Working straight on the trunk (FTD on an open main), the merge-base IS HEAD, and a change
|
|
35
|
+
// already committed would read as no change at all: tier 0, no reviewer, no escalation. With
|
|
36
|
+
// nothing uncommitted, the last commit is the change being asked about.
|
|
37
|
+
if (!base && mb === head.trim() && !names.size && git(root, ['rev-parse', '--verify', '--quiet', 'HEAD~1']) !== null) {
|
|
38
|
+
names = diff('HEAD~1');
|
|
39
|
+
shown = 'HEAD~1 (on the trunk: the last commit)';
|
|
40
|
+
}
|
|
41
|
+
return { ok: true, base: shown, paths: [...names].sort() };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function assess(paths, cfg = null) {
|
|
45
|
+
const { tier, classes, configError } = classifyRisk(paths, cfg);
|
|
46
|
+
return {
|
|
47
|
+
tier,
|
|
48
|
+
classes: [...new Set(Object.values(classes))].sort(),
|
|
49
|
+
reviewers: lensesRequired(tier, cfg),
|
|
50
|
+
humanApproval: tier >= 2,
|
|
51
|
+
paths: classes,
|
|
52
|
+
...(configError ? { configError } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function render(r) {
|
|
57
|
+
const top = Object.entries(r.paths).filter(([, c]) => r.tier === 0 || c !== 'docs');
|
|
58
|
+
return [
|
|
59
|
+
`tier ${r.tier}`,
|
|
60
|
+
`classes: ${r.classes.join(', ') || 'none'}`,
|
|
61
|
+
`reviewers: ${r.reviewers}`,
|
|
62
|
+
`human approval: ${r.humanApproval ? 'required' : 'not required'}`,
|
|
63
|
+
...(r.base ? [`base: ${r.base}`] : []),
|
|
64
|
+
...(r.configError ? [`config ignored: ${r.configError}`] : []),
|
|
65
|
+
...top.slice(0, 20).map(([p, c]) => ` ${c.padEnd(10)} ${p}`),
|
|
66
|
+
...(top.length > 20 ? [` … ${top.length - 20} more`] : []),
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
package/scripts/lib/spec-gate.js
CHANGED
|
@@ -18,6 +18,10 @@ import { parseFrontmatter } from './frontmatter.js';
|
|
|
18
18
|
// would be exactly the parallel accounting P3 forbids.
|
|
19
19
|
export const NEW_FAMILIES_SINCE = '2026-09-06';
|
|
20
20
|
|
|
21
|
+
// ASDD (4.0) makes a spec prove itself: four questions a long autonomous run is checked against.
|
|
22
|
+
// Same date rule as above, for the same reason — the corpus before it cannot answer them honestly.
|
|
23
|
+
export const ASDD_FAMILIES_SINCE = '2026-11-03';
|
|
24
|
+
|
|
21
25
|
// The four states a point can be in. Order is the table's order in the skill body.
|
|
22
26
|
export const OPEN_POINT_TYPES = [
|
|
23
27
|
'pregunta abierta', // formulable now, unanswered → clarify asks it
|
|
@@ -32,7 +36,7 @@ export const OPEN_POINT_TYPES = [
|
|
|
32
36
|
const FAMILIES = {
|
|
33
37
|
problem: { prefixes: ['problema & por que', 'problema y por que', 'problem & why'] },
|
|
34
38
|
goals: { prefixes: ['objetivos', 'goals'] },
|
|
35
|
-
nongoals: { prefixes: ['no-objetivos', 'no objetivos', 'non-goals'] },
|
|
39
|
+
nongoals: { prefixes: ['no-objetivos', 'no objetivos', 'non-goals', 'fuera de alcance', 'out of scope'] },
|
|
36
40
|
users: { prefixes: ['usuarios & contexto', 'usuarios y contexto', 'users & context'] },
|
|
37
41
|
behaviour: { prefixes: ['comportamiento', 'behaviour', 'behavior'] },
|
|
38
42
|
acceptance: { prefixes: ['criterios de aceptacion', 'acceptance criteria'] },
|
|
@@ -53,6 +57,10 @@ const FAMILIES = {
|
|
|
53
57
|
prefixes: ['la alternativa mas barata', 'alternativa mas barata', 'cheapest alternative', 'the cheapest alternative'],
|
|
54
58
|
since: NEW_FAMILIES_SINCE,
|
|
55
59
|
},
|
|
60
|
+
can: { prefixes: ['que podra hacer el usuario', 'what the user can do'], since: ASDD_FAMILIES_SINCE },
|
|
61
|
+
result: { prefixes: ['que resultado obtiene', 'what result they get'], since: ASDD_FAMILIES_SINCE },
|
|
62
|
+
respect: { prefixes: ['que debe respetar', 'what it must respect'], since: ASDD_FAMILIES_SINCE },
|
|
63
|
+
proof: { prefixes: ['como lo demuestra', 'how it proves it'], since: ASDD_FAMILIES_SINCE },
|
|
56
64
|
};
|
|
57
65
|
|
|
58
66
|
const UNCHECKED = [
|
|
@@ -66,6 +74,7 @@ const UNCHECKED = [
|
|
|
66
74
|
// Sin esta línea, el verde sería más estrecho que la regla que aparenta imponer — que es la
|
|
67
75
|
// definición de puerta decorativa, sólo que por omisión en vez de por ausencia.
|
|
68
76
|
`las specs anteriores a ${NEW_FAMILIES_SINCE} — quedan exentas de "qué pasa si no lo construimos" y de "la alternativa más barata", que no pueden rellenarse a posteriori`,
|
|
77
|
+
`las specs anteriores a ${ASDD_FAMILIES_SINCE} — quedan exentas de las cuatro preguntas de ASDD (qué podrá hacer el usuario, qué resultado obtiene, qué debe respetar, cómo lo demuestra)`,
|
|
69
78
|
];
|
|
70
79
|
|
|
71
80
|
function fold(s) {
|
package/scripts/rsc.js
CHANGED
|
@@ -46,7 +46,7 @@ if (['--version', '-v', 'version'].includes(rawArgv[0])) {
|
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
48
48
|
const GENERAL_HELP = [
|
|
49
|
-
'Use: npx @ericrisco/rsc onboard | reassess | add <id...> | install --profile <p> | consult "<text>" | list | capabilities [--full|gap-log] | audit | registry refresh | doctor | sync | agent-model <target> <model|inherit|status> | agents [status|reset <name|--all>] | memory <on|off|status|save|resume|learn|metrics> |
|
|
49
|
+
'Use: npx @ericrisco/rsc onboard | reassess | add <id...> | install --profile <p> | consult "<text>" | list | capabilities [--full|gap-log] | audit | registry refresh | doctor | sync | agent-model <target> <model|inherit|status> | agents [status|reset <name|--all>] | memory <on|off|status|save|resume|learn|metrics> | risk [--base <ref>] [paths…] | spec gate [specs…] | review consolidate|ledger|escapes | ingest <file...> | copy check <file> | copy rules | sello <on|off|status|…> | worktrees [reap [path] [--confirm]] | backups | restore <id|latest> | upgrade | repair | uninstall <id...> | purge | help',
|
|
50
50
|
'Any command takes --target <claude|codex|cursor|copilot|gemini|…> (comma-separate for several)',
|
|
51
51
|
' → without it, rsc uses the assistant already installed here; if two are, it asks instead of guessing.',
|
|
52
52
|
].join('\n');
|
|
@@ -77,7 +77,7 @@ const PURGE_HELP = [
|
|
|
77
77
|
const GLOBAL_VALUE_FLAGS = new Set([
|
|
78
78
|
'--topic', '--target', '--technical-level', '--accompaniment', '--project-kind', '--goal', '--goal-base64', '--software-scope', '--workflow', '--accept-plan',
|
|
79
79
|
]);
|
|
80
|
-
const COMMANDS = new Set(['ingest', 'copy', 'onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'knowledge-sync', 'git-permissions', 'agent-model', 'agents', 'main', 'isolation', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'sello', 'repair', 'uninstall', 'purge']);
|
|
80
|
+
const COMMANDS = new Set(['ingest', 'copy', 'onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'knowledge-sync', 'git-permissions', 'agent-model', 'agents', 'main', 'isolation', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'risk', 'spec', 'sello', 'repair', 'uninstall', 'purge']);
|
|
81
81
|
function positionalTokens(input) {
|
|
82
82
|
const out = [];
|
|
83
83
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -509,7 +509,7 @@ async function selectAgents() {
|
|
|
509
509
|
// the assistant (with the user present), never blindly from this CLI.
|
|
510
510
|
function printNextSteps(targets, ids) {
|
|
511
511
|
const hasHarness = ids.includes('harness');
|
|
512
|
-
const hasSdd = ids.includes('
|
|
512
|
+
const hasSdd = ids.includes('asdd') || ids.includes('sdd-init');
|
|
513
513
|
const label = (id) => TARGETS.find((t) => t.id === id)?.label || id;
|
|
514
514
|
const openLine = `Open this project in: ${targets.map((t) => `**${label(t)}**`).join(' · ')}`;
|
|
515
515
|
|
|
@@ -667,7 +667,7 @@ async function wizard(flagTargets, declaredTargets = []) {
|
|
|
667
667
|
// First, because it is what someone who already has a harness came here for nine times in ten.
|
|
668
668
|
...(declared ? [{ key: 'update', label: `Update this project — keeps its ${declared.skills.length} skills, refreshes hooks and content` }] : []),
|
|
669
669
|
{ key: 'base', label: `Base install — the essentials (${baseIds.length} skills)` },
|
|
670
|
-
{ key: '
|
|
670
|
+
{ key: 'asdd', label: 'Base + Agent Spec-Driven Development (ASDD) — spec → plan → review-in-the-loop → ship' },
|
|
671
671
|
{ key: 'manual', label: 'Pick skills by hand, by area' },
|
|
672
672
|
]);
|
|
673
673
|
if (choice === null) { say('\nOK — nothing installed. Anytime: npx @ericrisco/rsc'); return; }
|
|
@@ -675,7 +675,7 @@ async function wizard(flagTargets, declaredTargets = []) {
|
|
|
675
675
|
|
|
676
676
|
let ids;
|
|
677
677
|
if (choice === 'base') ids = baseIds;
|
|
678
|
-
else if (choice === '
|
|
678
|
+
else if (choice === 'asdd') ids = skillsForProfile(m, 'core');
|
|
679
679
|
else if (choice === 'manual') {
|
|
680
680
|
const picked = await manualSelect();
|
|
681
681
|
if (picked === null) continue; // backed out → re-show this menu
|
|
@@ -1232,11 +1232,109 @@ async function main() {
|
|
|
1232
1232
|
if (hits.length) process.exitCode = 1;
|
|
1233
1233
|
return;
|
|
1234
1234
|
}
|
|
1235
|
+
case 'spec': {
|
|
1236
|
+
// `spec gate` — the specify phase's exit gate, reachable from a user project. It was only an npm
|
|
1237
|
+
// script inside this package, so the one check ASDD's spec depends on could not run where specs live.
|
|
1238
|
+
const USE = 'Use: npx @ericrisco/rsc spec gate [spec.md ...]\n Without paths it checks 02-DOCS/wiki/sdd/specs/ of this project. Exits 1 when a spec fails.';
|
|
1239
|
+
if (argv[1] !== 'gate') { say(USE); if (argv[1]) process.exitCode = 1; return; }
|
|
1240
|
+
const { spawnSync } = await import('node:child_process');
|
|
1241
|
+
const { fileURLToPath: toPath } = await import('node:url');
|
|
1242
|
+
const rest = argv.slice(2);
|
|
1243
|
+
const specs = rest.filter((a, i) => !a.startsWith('--') && rest[i - 1] !== '--target');
|
|
1244
|
+
const r = spawnSync(process.execPath, [toPath(new URL('./spec-gate.js', import.meta.url)), ...specs], { stdio: 'inherit', windowsHide: true });
|
|
1245
|
+
process.exitCode = r.status ?? 1;
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
case 'risk': {
|
|
1249
|
+
// How much review and whose approval a change needs, by WHAT it touches (ASDD, P1/P7). The
|
|
1250
|
+
// agent never picks its own tier: this does, from the sello's path table, sello on or off.
|
|
1251
|
+
const K = await import('./lib/risk.js');
|
|
1252
|
+
const S = await import('../targets/sello.mjs');
|
|
1253
|
+
const root = S.resolveRoot(process.cwd());
|
|
1254
|
+
const base = flag('base');
|
|
1255
|
+
if (base === true) { say('Use: npx @ericrisco/rsc risk [--base <ref>] [paths…] [--json]'); process.exitCode = 1; return; }
|
|
1256
|
+
const rest = argv.slice(1);
|
|
1257
|
+
// Values of value-taking flags are not paths (`--target` is global). Paths given from a
|
|
1258
|
+
// subdirectory are resolved against the repository root, or the table would never match them.
|
|
1259
|
+
const VALUED = new Set(['--base', '--target']);
|
|
1260
|
+
const { resolve: res, relative: rel } = await import('node:path');
|
|
1261
|
+
const given = rest.filter((a, i) => !a.startsWith('--') && !VALUED.has(rest[i - 1]))
|
|
1262
|
+
.map((p) => rel(root, res(process.cwd(), p)).split('\\').join('/'));
|
|
1263
|
+
let paths = given;
|
|
1264
|
+
let against = null;
|
|
1265
|
+
if (!given.length) {
|
|
1266
|
+
const c = K.changedPaths(root, base || null);
|
|
1267
|
+
if (!c.ok) { say(c.message); process.exitCode = 1; return; }
|
|
1268
|
+
paths = c.paths;
|
|
1269
|
+
against = c.base;
|
|
1270
|
+
}
|
|
1271
|
+
const r = { ...K.assess(paths, S.readEffectiveConfig(root)), ...(against ? { base: against } : {}) };
|
|
1272
|
+
say(argv.includes('--json') ? JSON.stringify(r, null, 2) : K.render(r));
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1235
1275
|
case 'review': {
|
|
1236
1276
|
// `review consolidate` — the deterministic half of the refuter panel (P1): the three lenses
|
|
1237
1277
|
// often report one defect three ways, so grouping happens here, by location and evidence, and
|
|
1238
1278
|
// only the unique serious findings go on to `finding-verifier`, which is judgement.
|
|
1239
|
-
const USE =
|
|
1279
|
+
const USE = [
|
|
1280
|
+
'Use: npx @ericrisco/rsc review consolidate [report.md|findings.json ...] [--json] [--window <lines>]',
|
|
1281
|
+
' Without files it reads stdin. Each lens report carries its findings in ```json findings blocks.',
|
|
1282
|
+
' npx @ericrisco/rsc review ledger record <findings.json> [--task <id>] | outcome <id> <fixed|false|intentional|irrelevant|parked> | round <slug>/<task> | stats [--json]',
|
|
1283
|
+
' npx @ericrisco/rsc review escapes [--since <ref>] [--json]',
|
|
1284
|
+
].join('\n');
|
|
1285
|
+
if (argv[1] === 'ledger' || argv[1] === 'escapes') {
|
|
1286
|
+
// ASDD's fix loop and the reviewer's learning, token-free (P1): rounds capped, repeats caught,
|
|
1287
|
+
// outcomes counted, reverts listed as the cases the reviewer should be scored against.
|
|
1288
|
+
const L = await import('./lib/review-ledger.js');
|
|
1289
|
+
const { resolveRoot } = await import('../targets/sello.mjs');
|
|
1290
|
+
const root = resolveRoot(process.cwd());
|
|
1291
|
+
const str = (name) => { const v = flag(name); return typeof v === 'string' ? v : undefined; };
|
|
1292
|
+
const json = argv.includes('--json');
|
|
1293
|
+
const fail = (msg) => { say(msg); process.exitCode = 1; };
|
|
1294
|
+
if (argv[1] === 'escapes') {
|
|
1295
|
+
const res = L.escapes(root, { since: str('since') || '' });
|
|
1296
|
+
if (!res.ok) return fail(res.message);
|
|
1297
|
+
say(json ? JSON.stringify(res, null, 2) : L.renderEscapes(res));
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const [, , sub, a, b] = argv;
|
|
1301
|
+
if (sub === 'record') {
|
|
1302
|
+
const { readFileSync } = await import('node:fs');
|
|
1303
|
+
const R = await import('./lib/review-findings.js');
|
|
1304
|
+
const args = argv.slice(3);
|
|
1305
|
+
const file = args.find((x, i) => !x.startsWith('--') && args[i - 1] !== '--task');
|
|
1306
|
+
let raw;
|
|
1307
|
+
try { raw = file ? readFileSync(file, 'utf8') : readFileSync(0, 'utf8'); }
|
|
1308
|
+
catch (e) { return fail(`rsc: cannot read ${e.path || 'stdin'} (${e.code || e.message}).\n${USE}`); }
|
|
1309
|
+
let list;
|
|
1310
|
+
try { const v = JSON.parse(raw); list = Array.isArray(v) ? v : (Array.isArray(v?.findings) ? v.findings : [v]); }
|
|
1311
|
+
catch { const p = R.parseFindings(raw, file || 'stdin'); if (p.errors.length) return fail(p.errors.join('\n')); list = p.findings; }
|
|
1312
|
+
const out = L.record(root, list, { task: str('task') || '' });
|
|
1313
|
+
if (json) say(JSON.stringify(out, null, 2));
|
|
1314
|
+
else say(out.map((f) => `${f.id} ${f.status === 'repeat' ? `repeat of ${f.repeatOf} — do not raise it again` : 'new'} ${f.file || '(no file)'} ${f.claim}`).join('\n') || 'nothing to record');
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (sub === 'outcome') {
|
|
1318
|
+
if (!a || !b) return fail(USE);
|
|
1319
|
+
const r = L.outcome(root, a, b);
|
|
1320
|
+
if (!r.ok) return fail(r.message);
|
|
1321
|
+
say(r.message);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (sub === 'round') {
|
|
1325
|
+
if (!a) return fail(USE);
|
|
1326
|
+
const r = L.round(root, a);
|
|
1327
|
+
say(r.message);
|
|
1328
|
+
if (!r.ok) process.exitCode = 1;
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (sub === 'stats') {
|
|
1332
|
+
const s = L.stats(root);
|
|
1333
|
+
say(json ? JSON.stringify(s, null, 2) : L.renderStats(s));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
return fail(USE);
|
|
1337
|
+
}
|
|
1240
1338
|
if (argv[1] !== 'consolidate') { say(USE); if (argv[1]) process.exitCode = 1; return; }
|
|
1241
1339
|
const { readFileSync } = await import('node:fs');
|
|
1242
1340
|
const R = await import('./lib/review-findings.js');
|
package/scripts/spec-gate.js
CHANGED
|
@@ -6,14 +6,24 @@
|
|
|
6
6
|
// the phase's own instrument, and the author's.
|
|
7
7
|
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
8
8
|
import { join, dirname, basename, resolve, relative } from 'node:path';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
9
|
import { execFileSync } from 'node:child_process';
|
|
11
10
|
import { specCompleteness, statusClaims, checkClaims } from './lib/spec-gate.js';
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// The corpus of the project the gate is run FROM — not of the package that ships the script. Run as
|
|
13
|
+
// `npx @ericrisco/rsc spec gate` in a user project, a package-relative default found nothing.
|
|
14
|
+
function projectRoot() {
|
|
15
|
+
try {
|
|
16
|
+
const top = execFileSync('git', ['rev-parse', '--show-toplevel'], { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
17
|
+
if (top) return top;
|
|
18
|
+
} catch { /* not a git project: look upward for 02-DOCS instead */ }
|
|
19
|
+
for (let dir = process.cwd(); ; dir = dirname(dir)) {
|
|
20
|
+
if (existsSync(join(dir, '02-DOCS'))) return dir;
|
|
21
|
+
if (dirname(dir) === dir) return process.cwd();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
function defaultTargets() {
|
|
26
|
+
const DEFAULT_DIR = join(projectRoot(), '02-DOCS', 'wiki', 'sdd', 'specs');
|
|
17
27
|
try {
|
|
18
28
|
return readdirSync(DEFAULT_DIR)
|
|
19
29
|
.filter((f) => f.endsWith('.md') && !f.endsWith('.plan.md'))
|
package/skills/analyze/SKILL.md
CHANGED
|
@@ -11,11 +11,11 @@ origin: risco
|
|
|
11
11
|
|
|
12
12
|
You have a **constitution**, a **spec**, a **plan** and a **task list**. Four artifacts written at four different moments, by a mind that drifted a little each time. `analyze` reads all four *against each other* and surfaces where they disagree. It is the cheapest place in the whole chain to catch a problem — a contradiction found here costs a sentence; the same contradiction found mid-implement costs a rewrite.
|
|
13
13
|
|
|
14
|
-
Sixth phase of the rsc-sdd chain (`constitution → specify → clarify → plan → tasks → analyze → implement`); the method itself lives in `../
|
|
14
|
+
Sixth phase of the rsc-sdd chain (`constitution → specify → clarify → plan → tasks → analyze → implement`); the method itself lives in `../asdd/SKILL.md`. It is a **gate, not a worker**: it produces a report and stops. The user reads the findings and decides what to fix, and which phase to send each fix back to.
|
|
15
15
|
|
|
16
16
|
**Report only. Resolve nothing.** Never edit the constitution, spec, plan or tasks; never open a code file to "just fix it"; never silently reconcile a contradiction by picking a side. This one is absolute because a gate that quietly fixes things stops being a gate: the user never learns the spec was wrong, the plan built on the old assumption stays stale, and the "consistency check" has manufactured a new inconsistency. Name the conflict, show both sides with locations, propose where it should be resolved, hand the decision back.
|
|
17
17
|
|
|
18
|
-
**Model tier: `heavy`** (adversarial cross-reading). Resolve and apply it per `../
|
|
18
|
+
**Model tier: `heavy`** (adversarial cross-reading). Resolve and apply it per `../asdd/references/model-routing.md`; routing is off unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`.
|
|
19
19
|
|
|
20
20
|
**Register.** Read `technical_level` in `02-DOCS/wiki/harness/user-profile.md` before reporting; with no profile, use analogies and say the harness has not set the register yet. The register changes how the report reads, never what gets checked; the six analyses always run in full.
|
|
21
21
|
|
|
@@ -99,7 +99,7 @@ Render it in the orient voice, in the user's register. Do not log a decision to
|
|
|
99
99
|
## Result envelope
|
|
100
100
|
|
|
101
101
|
End with the parseable block every SDD phase shares, so the dispatcher can chain without
|
|
102
|
-
interpreting prose (contract: `../
|
|
102
|
+
interpreting prose (contract: `../asdd/SKILL.md`):
|
|
103
103
|
|
|
104
104
|
```json result-envelope
|
|
105
105
|
{
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: asdd
|
|
3
|
+
description: "Use when a feature or substantial change should go from idea to shipped, verified software and an agent may run it for hours on its own — the ASDD (Agent Spec-Driven Development) dispatcher and front door, in any language. A spec that must prove itself, review inside every task scaled by `rsc risk`, a decision queue for what the agent must not decide alone. Routes constitution -> specify -> clarify -> plan -> tasks -> analyze -> implement (verify + review per task) -> closing -> ship. Successor of `sdd`. NOT a single phase (it dispatches), NOT small authorised changes (that is `ftd`), NOT the workspace harness (that is `harness`)."
|
|
4
|
+
tags: [sdd, asdd, spec, workflow, plan, review]
|
|
5
|
+
recommends: [sdd-init, constitution, specify]
|
|
6
|
+
profiles: [core, full]
|
|
7
|
+
origin: risco
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# asdd — Agent Spec-Driven Development
|
|
11
|
+
|
|
12
|
+
*The front door for letting an agent build for hours without coming back with a Frankenstein: intent in, shipped-and-verified software out, every decision recorded in `02-DOCS/wiki/sdd/`.*
|
|
13
|
+
|
|
14
|
+
**Why it exists.** Classic SDD reviewed at the end. On paper it checked the spec; in a long autonomous run, the only thing that bit for hours was the test — and tests prove the code works, not that it does what was asked (a real run: 8 hours, 4,500 green tests, and a dropped token plus a QR nobody requested). ASDD keeps the phases and changes two things: **the spec must prove itself**, and **the review moves inside every task**, scaled by risk. The human stops reviewing everything and only takes the decisions the agent must not take alone.
|
|
15
|
+
|
|
16
|
+
This skill does **not** do a phase. It names the method, reads the register, places the request on the map and hands off. Each phase skill points at the next one.
|
|
17
|
+
|
|
18
|
+
## The five stages
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
request ─► decisor (answer · ftd · asdd)
|
|
22
|
+
│ asdd
|
|
23
|
+
1. BASE ─────► constitution + REVIEW.md (once per project)
|
|
24
|
+
2. SPEC ─────► specify (4 questions + out of scope) ─► clarify (gap checklist) ◄── HUMAN APPROVES
|
|
25
|
+
3. PLAN ─────► plan ─► tasks (criterion · risk forecast · check) ─► analyze
|
|
26
|
+
4. LOOP ─────► per task, in implement:
|
|
27
|
+
a build + self-check b rsc risk c review by tier
|
|
28
|
+
d verify findings e mandatory controls
|
|
29
|
+
f fix (max 3 rounds) g fix & record ─or─ park ─► DECISION QUEUE ◄── HUMAN
|
|
30
|
+
5. CLOSE ────► verify against main + rollback plan · closing review (whole branch vs spec) · explainer
|
|
31
|
+
SHIP ─────► remote: PR (self-merge only tier 0-1 + green CI + no blocking entry) │ no remote: park ◄── HUMAN
|
|
32
|
+
LEARN ────► finding outcomes · reverts → reviewer cases · repeated finding → rule
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Stage | Phase skills | Writes |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| **1 Base** | `../constitution/SKILL.md` (also drafts `REVIEW.md`), `../sdd-init/SKILL.md` | `constitution.md`, `REVIEW.md`, `config.yaml` |
|
|
38
|
+
| **2 Spec** | `../specify/SKILL.md` → `../clarify/SKILL.md` | `specs/<slug>.md` |
|
|
39
|
+
| **3 Plan** | `../plan/SKILL.md` → `../tasks/SKILL.md` → `../analyze/SKILL.md` | `plans/<slug>.md`, `analysis/<slug>.md` |
|
|
40
|
+
| **4 Loop** | `../implement/SKILL.md`, calling `../verify/SKILL.md` and `../review/SKILL.md` per task | `progress/<slug>.md` (with `## Decision queue`), `decisions.md` |
|
|
41
|
+
| **5 Close** | `../verify/SKILL.md` (closing mode), then `../review/SKILL.md` (closing review, on what will ship), `../ship/SKILL.md` | `verifications/<slug>-<date>.md`, `reviews/<slug>.md`, the PR or parked branch |
|
|
42
|
+
| on demand | `../debug/SKILL.md` · `../worktrees/SKILL.md` · `../parallel/SKILL.md` | — |
|
|
43
|
+
|
|
44
|
+
**Where the human enters — three places only:** approving the spec, answering the decision queue, approving a tier-2 merge. Everything else runs.
|
|
45
|
+
|
|
46
|
+
No ASDD phase edits the root `CLAUDE.md`: every artifact is indexed in `02-DOCS/wiki/index.md` (the Knowledge map). The root `CLAUDE.md` is owned by `harness` and is tier 2 in `rsc risk`.
|
|
47
|
+
|
|
48
|
+
### The spec proves itself
|
|
49
|
+
|
|
50
|
+
Every spec answers four questions, and `spec:gate` refuses one dated 2026-11-03 or later without them: **what the user can do**, **what result they get** (against a reference the human approved — a screenshot, a sketch, an expected API response), **what it must respect** (prior decisions, specs and behaviour; read from the brain when the project has one, else `decisions.md`), **how it proves it** (how the check runs, what evidence remains — "yes, I tested it" is not evidence). Plus an explicit **out of scope**: what the agent might add "just in case".
|
|
51
|
+
|
|
52
|
+
### The risk decides, never the agent
|
|
53
|
+
|
|
54
|
+
`npx @ericrisco/rsc risk [--base <ref>] [paths…]` classifies a change by **what it touches** (the sello's path table; works with the sello off): tier 0 docs/copy → no reviewer; tier 1 ordinary code → one fresh-context reviewer; tier 2 auth, billing, CI workflows, migrations, secrets, harness config → the three-refuter panel **and human approval**. An agent's approval never counts as a human's.
|
|
55
|
+
|
|
56
|
+
### The loop converges
|
|
57
|
+
|
|
58
|
+
`npx @ericrisco/rsc review ledger` holds it to the rules that stopped Anthropic's 50-round reviews: `round <slug>/<task>` refuses a 4th round (rethink the approach or park the task), `record` marks a finding already raised as a `repeat` that is never raised again, `outcome` closes each finding as `fixed|false|intentional|irrelevant`, `stats` measures the noise. Flag the bug class, not the line; from round 2 re-review only the new diff.
|
|
59
|
+
|
|
60
|
+
### Park, don't block
|
|
61
|
+
|
|
62
|
+
Tier 2, a contradiction with a recorded decision, a gap that would make the build unsafe, or no convergence in 3 rounds: the task goes to the **decision queue** (`## Decision queue` in `progress/<slug>.md`) with its evidence, and the agent **continues with the independent tasks**. The run never waits at 2 a.m. for an answer.
|
|
63
|
+
|
|
64
|
+
Every entry carries a kind, one line each:
|
|
65
|
+
|
|
66
|
+
```markdown
|
|
67
|
+
- [blocking] Q3 — <what must be decided>, <evidence>, <task>
|
|
68
|
+
- [review-after] Q4 — <assumption taken>, <where it is recorded>, <task>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **`blocking`** — tier 2, a contradiction with a recorded decision, no convergence in 3 rounds, a hard failure. It stops a self-merge.
|
|
72
|
+
- **`review-after`** — clarify's recorded assumptions, a spec reference the agent had to write itself, a provisional constitution ratification. It never stops a merge; it is listed in the PR body (or the parked summary) for the human.
|
|
73
|
+
|
|
74
|
+
## The invoke rule — route to the phase that fits
|
|
75
|
+
|
|
76
|
+
> **The new-feature gate (hard).** The moment the user is thinking about a new feature or change — "add…", "build…", "it should also…", "quiero añadir…" — and the decisor chose this lane, it goes to `specify` first, *even if a stack skill also fired*. **No feature code is written — by any skill — until a spec AND a plan exist and are approved.** Small authorised changes go to `../ftd/SKILL.md`, which escalates here when `rsc risk` says tier 2 or the change contradicts a recorded decision.
|
|
77
|
+
|
|
78
|
+
1. **Empty repo (nothing to detect)?** → `constitution` first (it decides the stack), then `sdd-init`.
|
|
79
|
+
2. **No `02-DOCS/wiki/sdd/config.yaml` and this is non-trivial?** → `sdd-init`. **No constitution, or `status: draft`?** → `constitution` once (it also drafts `REVIEW.md`).
|
|
80
|
+
3. **Ambiguous / architectural / risky before a spec?** → optional proposal via `specify`.
|
|
81
|
+
4. **Fuzzy idea, no spec?** → `specify`. **Spec with gaps?** → `clarify`.
|
|
82
|
+
5. **Approved spec, no plan?** → `plan`, then `tasks`, then `analyze`.
|
|
83
|
+
6. **Green light?** → `implement` (the per-task loop; `parallel` / `worktrees` when useful).
|
|
84
|
+
7. **All tasks done or parked?** → closing: `verify` (closing mode, main merged in), then `review` (closing review of that exact state), then `ship`.
|
|
85
|
+
8. **Broken mid-flight?** → `debug`, then resume.
|
|
86
|
+
|
|
87
|
+
Cannot tell the phase? Ask one question: *"Do we have a written spec for this yet?"*
|
|
88
|
+
|
|
89
|
+
Skip honestly: `constitution` runs once per project; `clarify` and `analyze` are gates, not paperwork — pass through only when the change is genuinely tiny, and say so.
|
|
90
|
+
|
|
91
|
+
## Autopilot — one up-front yes
|
|
92
|
+
|
|
93
|
+
The user says "take it all the way" once (or `execution_mode: automatic` under `sdd:` in `config.yaml`, set by `sdd-init`) and the chain runs end to end **without asking to continue between phases**, still writing every artifact. The up-front yes satisfies the new-feature gate.
|
|
94
|
+
|
|
95
|
+
Autopilot does not stop the run for questions — it **parks**: real ambiguity, tier 2, a contradiction with a recorded decision or a non-converging task go to the decision queue while independent work continues. It **stops** only for a hard failure nothing else can proceed past, and for destructive or irreversible actions.
|
|
96
|
+
|
|
97
|
+
**In autopilot, per phase:**
|
|
98
|
+
|
|
99
|
+
- `constitution` ratifies provisionally → `review-after` entry.
|
|
100
|
+
- `specify` uses a checkable reference it wrote itself (expected output, sample response) → `review-after` entry.
|
|
101
|
+
- `clarify` records its assumptions (`suposición tomada`) → `review-after` entries.
|
|
102
|
+
- `implement` parks `blocking` items and continues with the independent tasks.
|
|
103
|
+
- `ship` lands by the rule below.
|
|
104
|
+
|
|
105
|
+
**Landing — there is always a legal path.** A remote exists → open a PR; self-merge only when `rsc risk` says tier 0-1, CI is green and no `blocking` entry is open; `review-after` entries go into the PR body. No remote → **park**: leave the branch committed, write the explainer, the open entries and the exact merge command for the human into `progress/<slug>.md`, and stop. Never merge locally around the branch guard: when `rsc main status` says `closed: true`, the guard denies it.
|
|
106
|
+
|
|
107
|
+
Narrate in the `orient` voice: name each stage as it passes and show its artifact, one line each.
|
|
108
|
+
|
|
109
|
+
## Read the register first
|
|
110
|
+
|
|
111
|
+
Read `technical_level` in `02-DOCS/wiki/harness/user-profile.md`. It picks the words (technical, or plain with analogies), never whether the gates exist. No profile → analogies, ask once "technical or with analogies?", persist it (that is `init`'s job; `asdd` honors it). At gates, ask only what changes what gets built, one question at a time, recommendation first.
|
|
112
|
+
|
|
113
|
+
## Per-phase model routing (opt-in)
|
|
114
|
+
|
|
115
|
+
Each phase declares a tier — `heavy` (constitution, plan, analyze, review, debug), `balanced` (specify, clarify, tasks, implement, verify), `light` (ship, worktrees, sdd-init) — resolved to a model in `config.yaml` under `models`. Off by default (`models.enabled: false`). Never switch unasked, never claim a switch a tool can't make; the dispatcher itself stays on the session model. Protocol: `references/model-routing.md`.
|
|
116
|
+
|
|
117
|
+
## Where the artifacts live
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
02-DOCS/wiki/sdd/
|
|
121
|
+
├── config.yaml ← runtime calibration (sdd-init)
|
|
122
|
+
├── constitution.md ← non-negotiables (once)
|
|
123
|
+
├── proposals/<slug>.md ← optional pre-execution briefing
|
|
124
|
+
├── specs/<slug>.md ← one spec per feature (four questions + out of scope)
|
|
125
|
+
├── plans/<slug>.md ← plan + tasks
|
|
126
|
+
├── analysis/<slug>.md ← analyze's cross-artifact report
|
|
127
|
+
├── progress/<slug>.md ← append-only loop ledger + ## Decision queue
|
|
128
|
+
├── verifications/<slug>-YYYY-MM-DD.md
|
|
129
|
+
├── reviews/<slug>.md ← closing review record
|
|
130
|
+
├── review-evals/<sha>.md ← one case per revert that escaped review (`rsc review escapes`)
|
|
131
|
+
├── archive/<slug>/
|
|
132
|
+
├── sessions/<date>-<slug>.md
|
|
133
|
+
└── decisions.md ← append-only: every decision, and the task it was taken in
|
|
134
|
+
REVIEW.md ← project root: how this repo is judged
|
|
135
|
+
.rsc/review-ledger.jsonl ← findings, outcomes, rounds (tool-written)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The directory keeps its `sdd/` name: existing projects keep working. Index it in `02-DOCS/wiki/index.md` under the `sdd/` topic — never in the root `CLAUDE.md`; the harness keeps it honest.
|
|
139
|
+
|
|
140
|
+
## Stack handoff and skill registry
|
|
141
|
+
|
|
142
|
+
ASDD is process, not stack: `plan` defers stack specifics, `implement` borrows the stack skill's testing approach, `verify` runs its checks (`../fastapi/SKILL.md`, `../nextjs/SKILL.md`, `../go/SKILL.md`, `../postgresdb/SKILL.md`, `../flutter/SKILL.md`; UI → `../design/SKILL.md`). Choose skills through `.rsc/skill-registry.json`, digest them into 4-5 compact rules per subagent brief, never load the catalog.
|
|
143
|
+
|
|
144
|
+
## Standard result envelope
|
|
145
|
+
|
|
146
|
+
Every phase ends with this block so the dispatcher can chain without interpreting prose:
|
|
147
|
+
|
|
148
|
+
```json result-envelope
|
|
149
|
+
{
|
|
150
|
+
"status": "complete|blocked|failed",
|
|
151
|
+
"executive_summary": "one short paragraph",
|
|
152
|
+
"artifact": "path/to/artifact-or-none",
|
|
153
|
+
"next_recommended": "sdd-init|constitution|specify|clarify|plan|tasks|analyze|implement|verify|review|ship",
|
|
154
|
+
"risk": "low|medium|high",
|
|
155
|
+
"model": { "tier": "heavy|balanced|light", "resolved": "model-id", "routing": "on|off" },
|
|
156
|
+
"skill_resolution": { "used": [], "missing": [], "fallback": [], "compact_rules": [] },
|
|
157
|
+
"evidence": []
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
A run stopped mid-flight writes `02-DOCS/wiki/sdd/sessions/<date>-<slug>.md` instead: stage, artifacts, last verdict, done and parked tasks, the decision queue, next steps.
|
|
162
|
+
|
|
163
|
+
## Anti-patterns → STOP
|
|
164
|
+
|
|
165
|
+
| Rationalization | Reality / fix |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| "Tests are green, the feature is done." | Green proves it runs. Traceability to the spec proves it is what was asked; untraced behaviour is a finding. |
|
|
168
|
+
| "I'll review everything at the end." | That is how 40 correct tasks become a Frankenstein. Review inside each task, then once more at closing. |
|
|
169
|
+
| "This change is obviously low risk." | `rsc risk` decides, from the paths. The agent never picks or argues down its own tier. |
|
|
170
|
+
| "One more round and it converges." | Round 4 is refused. Rethink the approach or park the task. |
|
|
171
|
+
| "I'll wait for the user's answer." | Park the task in the decision queue and continue with independent work. |
|
|
172
|
+
| "The reviewer agent approved it, merge." | An agent's approval never counts as a human's. Tier 2 waits. |
|
|
173
|
+
| "The screenshots look alike." | Compare against a reference the human approved, or a deterministic diff. |
|
|
174
|
+
| "`asdd` should write the spec itself." | It dispatches. `specify` owns the spec. |
|
|
175
|
+
| "Ship with Co-Authored-By: Claude." | Authorship is the responsible person. No AI trailer, no generated-with footer. |
|
|
176
|
+
|
|
177
|
+
## Start here
|
|
178
|
+
|
|
179
|
+
- **New project** → `sdd-init`, `constitution`, then `specify`. **Empty repo** → `constitution` first (it decides the stack), then `sdd-init`.
|
|
180
|
+
- **Existing project, new feature** → `specify` (proposal first if risky or architectural).
|
|
181
|
+
- **Small authorised change** → `../ftd/SKILL.md`.
|
|
182
|
+
|
|
183
|
+
Then follow the arrows: each phase ends by pointing at the next, all the way to `ship`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Eval harness — `
|
|
1
|
+
# Eval harness — `asdd`
|
|
2
2
|
|
|
3
|
-
Evaluates the `
|
|
3
|
+
Evaluates the `asdd` dispatcher (the rsc-sdd front door) on two axes: **triggering**
|
|
4
4
|
(does it fire when a feature should run the spec-driven flow, and stay quiet when a
|
|
5
5
|
single phase or a different skill owns the request) and **capability** (does loading it
|
|
6
6
|
measurably improve how the agent opens and routes the work). Cases live in `cases.yaml`.
|
|
@@ -9,7 +9,7 @@ feeds prompts to Claude Code and judges the result against the rubrics.
|
|
|
9
9
|
|
|
10
10
|
## What's in `cases.yaml`
|
|
11
11
|
|
|
12
|
-
- `should_trigger` (7) — prompts that MUST invoke `
|
|
12
|
+
- `should_trigger` (7) — prompts that MUST invoke `asdd`, including non-obvious phrasings
|
|
13
13
|
("which step am I on", "decide what and why, write it down, then build") with no SDD
|
|
14
14
|
jargon.
|
|
15
15
|
- `should_not_trigger` (6) — near-misses that must route elsewhere. Each carries a
|
|
@@ -19,12 +19,12 @@ feeds prompts to Claude Code and judges the result against the rubrics.
|
|
|
19
19
|
|
|
20
20
|
## A. Triggering eval
|
|
21
21
|
|
|
22
|
-
1. Load **only** `
|
|
22
|
+
1. Load **only** `asdd` into the agent (no other rsc skills available, so routing is honest).
|
|
23
23
|
2. For each `should_trigger` prompt: open a fresh session, paste the prompt verbatim, and
|
|
24
|
-
record whether `
|
|
24
|
+
record whether `asdd` activates — a pass means it names the method, places the request on
|
|
25
25
|
the phase map, and routes to a phase (it should NOT start writing the spec or coding
|
|
26
26
|
itself). Run **3–5 trials** per prompt.
|
|
27
|
-
3. For each `should_not_trigger` prompt: same procedure, but a **pass** = `
|
|
27
|
+
3. For each `should_not_trigger` prompt: same procedure, but a **pass** = `asdd` does NOT
|
|
28
28
|
fire. Where a `route_to` sibling is named, sanity-check the prompt genuinely belongs there
|
|
29
29
|
(e.g. "write the spec" → `specify`, "open the PR and merge" → `ship`).
|
|
30
30
|
4. Score: a prompt passes if the **majority of its trials** go the expected way.
|
|
@@ -35,7 +35,7 @@ prompts (at most 1 of the 13 may misbehave).
|
|
|
35
35
|
## B. Capability eval
|
|
36
36
|
|
|
37
37
|
1. **Without the skill:** fresh session, skill NOT loaded, give the `scenario` prompt. Save output A.
|
|
38
|
-
2. **With the skill:** fresh session, `
|
|
38
|
+
2. **With the skill:** fresh session, `asdd` loaded, same prompt. Save output B.
|
|
39
39
|
3. Grade each output against that scenario's `must_include` points — count points clearly covered.
|
|
40
40
|
4. Repeat across **3 trials** per scenario per condition and average the coverage.
|
|
41
41
|
|
|
@@ -47,14 +47,14 @@ the skill — or these rubrics — needs work.
|
|
|
47
47
|
|
|
48
48
|
- This is **LLM-as-judge / human-in-the-loop**, not deterministic. Use a consistent grader
|
|
49
49
|
(same model + rubric) across A/B to keep the comparison fair.
|
|
50
|
-
- The headline differentiators for `
|
|
50
|
+
- The headline differentiators for `asdd`: it **dispatches, it does not perform a phase**; it
|
|
51
51
|
states the method ("the artifact is the contract"), reads the **register** (`technical_level`)
|
|
52
52
|
and speaks in the orient voice (never skipping gates), presents the **chained phase map**, applies the **invoke
|
|
53
53
|
rule** and the **skip rules** honestly, and writes artifacts under `02-DOCS/wiki/sdd/`.
|
|
54
|
-
- Key confusables to watch: "write the spec" is `specify`, not `
|
|
54
|
+
- Key confusables to watch: "write the spec" is `specify`, not `asdd`; "set the principles" is
|
|
55
55
|
`constitution`; "open the PR" is `ship`; "build 01-TOOLS/02-DOCS" is `harness`. If the agent
|
|
56
56
|
does the phase work itself instead of routing, that's a fail even if the output looks good.
|
|
57
|
-
- Because `
|
|
57
|
+
- Because `asdd` is the front door, some phase siblings may not yet be installed. A correct
|
|
58
58
|
response still routes by name and does the phase inline if the sibling is absent — it must
|
|
59
59
|
never invent a phase that isn't in the chain.
|
|
60
60
|
- Run after any edit to `SKILL.md` or its `description`, since both axes are sensitive to wording.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
skill:
|
|
1
|
+
skill: asdd
|
|
2
2
|
|
|
3
|
-
# Cases for the
|
|
4
|
-
#
|
|
3
|
+
# Cases for the "asdd" dispatcher / front door (ASDD, successor of sdd in 4.0).
|
|
4
|
+
# asdd states the SDD method, reads the user's register (technical_level) from
|
|
5
5
|
# 02-DOCS/wiki/harness/user-profile.md, and ROUTES to the right phase skill in
|
|
6
6
|
# the chain constitution -> specify -> clarify -> plan -> tasks -> analyze ->
|
|
7
7
|
# implement -> verify -> review -> ship (debug/worktrees/parallel on demand).
|
|
@@ -17,7 +17,7 @@ should_trigger:
|
|
|
17
17
|
- prompt: "I'm somewhere in the middle of building a feature and I've lost track of which step I'm on. Can you orient me?"
|
|
18
18
|
why: "Non-obvious: no SDD jargon, but 'which step am I on' is precisely the dispatcher's 'which phase are you in' job — it asks the one orienting question (is there a written spec?) and routes."
|
|
19
19
|
|
|
20
|
-
- prompt: "Run the full
|
|
20
|
+
- prompt: "Run the full asdd flow on this new authentication requirement."
|
|
21
21
|
why: "Direct request to run the chained flow — the dispatcher owns the flow and routes the first phase; it should not try to do every phase inline itself."
|
|
22
22
|
|
|
23
23
|
- prompt: "I have a one-paragraph idea for a notifications feature. Take it all the way to a PR the disciplined way."
|
|
@@ -69,7 +69,7 @@ capability:
|
|
|
69
69
|
- "Includes sdd-init as step zero when 02-DOCS/wiki/sdd/config.yaml is missing, and explains that it detects test commands, strict TDD and .rsc/skill-registry.json"
|
|
70
70
|
- "Applies the invoke rule: a fuzzy idea with no spec on disk routes to specify (and notes constitution runs once per project if not yet present)"
|
|
71
71
|
- "Does NOT itself write the spec or start coding — it hands off to the specify phase skill (../specify/SKILL.md)"
|
|
72
|
-
- "Notes that artifacts will be written under 02-DOCS/wiki/sdd/ (specs/, plans/, decisions.md) and indexed in the root CLAUDE.md
|
|
72
|
+
- "Notes that artifacts will be written under 02-DOCS/wiki/sdd/ (specs/, plans/, decisions.md) and indexed in 02-DOCS/wiki/index.md (never the root CLAUDE.md)"
|
|
73
73
|
- "Mentions standard result envelopes with status, artifact, next_recommended, risk, skill_resolution and evidence"
|
|
74
74
|
- "Defers concrete stack tooling (test runner, build, framework idioms) to the relevant stack skill rather than prescribing it here"
|
|
75
75
|
|
|
@@ -6,7 +6,7 @@ that lets each phase run on the model its work deserves — cheaper where it's s
|
|
|
6
6
|
where it matters — without surprising anyone and without faking a switch a tool can't make.*
|
|
7
7
|
|
|
8
8
|
This file is the **single source of truth** for the behavior. `sdd-init` writes the profile,
|
|
9
|
-
`
|
|
9
|
+
`asdd` summarizes it, and every phase skill points here for the procedure. If the three ever
|
|
10
10
|
disagree, this file wins.
|
|
11
11
|
|
|
12
12
|
## Why this exists
|
|
@@ -64,7 +64,7 @@ session model, never announce a switch, never nag. Everything below applies only
|
|
|
64
64
|
|
|
65
65
|
**Two phases are deliberately absent from `phases`:**
|
|
66
66
|
|
|
67
|
-
- `
|
|
67
|
+
- `asdd` (the dispatcher) — routing decisions are cheap; it stays on the session model.
|
|
68
68
|
- `parallel` — it has no fixed tier. Each fanned-out subagent inherits the tier of the *kind
|
|
69
69
|
of work* its unit does (an implement-type unit → `balanced`; a scan/research unit → `light`;
|
|
70
70
|
a deep-reasoning unit → `heavy`).
|
|
@@ -14,7 +14,7 @@ A thin provider adapter, a disciplined agent loop, schema-validated tools, provi
|
|
|
14
14
|
|
|
15
15
|
> Program against a **capability interface**, never a vendor SDK. Vendor specifics (model id, tool-schema shape, JSON mode, caching, token limits) live behind one adapter resolved from config. Model names and prices rot — if one appears in business logic it's a bug, and re-verify the dated tables before quoting a number.
|
|
16
16
|
|
|
17
|
-
**Hand off instead when:** a new non-trivial feature has no approved spec + plan under `02-DOCS/wiki/sdd/` → stop and run [`specify`](../specify/SKILL.md) first (method: [`
|
|
17
|
+
**Hand off instead when:** a new non-trivial feature has no approved spec + plan under `02-DOCS/wiki/sdd/` → stop and run [`specify`](../specify/SKILL.md) first (method: [`asdd`](../asdd/SKILL.md)), which routes back here once the plan is approved; one-line/low-risk changes go straight through. Anthropic-SDK internals (caching, thinking, batch) in a file that *only* imports `anthropic` → `claude-api` if your environment has it, since this skill stays multi-provider. Workspace scaffolding → [`harness`](../harness/SKILL.md). Choosing *which* coding agent to use → agent-eval territory. Pure prompt-wording tuning with no architecture change → prompt engineering, not this. A one-shot throwaway prompt, or no retrieval/tools/loop/evals at all → you don't need an agent; call the SDK directly and say so.
|
|
18
18
|
|
|
19
19
|
## Decision rules (read before writing code)
|
|
20
20
|
|