mandrel 2.58.0 → 2.59.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/.agents/README.md +6 -3
- package/.agents/docs/SDLC.md +6 -7
- package/.agents/docs/quality-gates.md +1 -1
- package/.agents/instructions.md +2 -3
- package/.agents/runtime-deps.json +7 -2
- package/.agents/schemas/crap-baseline.schema.json +1 -1
- package/.agents/schemas/crap-report.schema.json +1 -1
- package/.agents/scripts/install-matrix-assert.js +48 -3
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
- package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
- package/.agents/scripts/lib/crap-engine.js +2 -2
- package/.agents/scripts/lib/crap-utils.js +21 -5
- package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
- package/.agents/scripts/lib/escomplex-kernel.js +298 -0
- package/.agents/scripts/lib/maintainability-engine.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-context.js +31 -25
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +28 -24
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +8 -9
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +107 -0
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +25 -209
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
- package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
- package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
- package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
- package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +21 -18
- package/.agents/scripts/plan-persist.js +0 -11
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-to-stories.md +14 -11
- package/.agents/workflows/helpers/plan-reference.md +18 -7
- package/.agents/workflows/mandrel-plan.md +14 -13
- package/README.md +3 -3
- package/docs/CHANGELOG.md +8 -0
- package/lib/cli/registry.js +45 -25
- package/package.json +7 -2
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
- package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
- package/.agents/skills/core/scope-triage/SKILL.md +0 -48
package/lib/cli/registry.js
CHANGED
|
@@ -41,6 +41,8 @@ import {
|
|
|
41
41
|
} from '../../.agents/scripts/lib/bootstrap/project-bootstrap.js';
|
|
42
42
|
import { isCommandExcluded } from '../../.agents/scripts/lib/command-header.js';
|
|
43
43
|
import { getDeliveryRouting } from '../../.agents/scripts/lib/config/delivery-routing.js';
|
|
44
|
+
import { isResolvable } from '../../.agents/scripts/lib/runtime-deps/dep-resolution.js';
|
|
45
|
+
import { describeParserMajorError } from '../../.agents/scripts/lib/runtime-deps/parser-major.js';
|
|
44
46
|
import {
|
|
45
47
|
defaultResolvePackageRoot,
|
|
46
48
|
listFiles as listPayloadFiles,
|
|
@@ -519,6 +521,30 @@ function runAgentsInSync({
|
|
|
519
521
|
// check: runtime-deps
|
|
520
522
|
// ---------------------------------------------------------------------------
|
|
521
523
|
|
|
524
|
+
/**
|
|
525
|
+
* The verdict when every required package is present.
|
|
526
|
+
*
|
|
527
|
+
* Presence is not the whole contract: `.agents/` resolves from the consumer's
|
|
528
|
+
* node_modules, so a declared range cannot enforce which `@babel/parser` major
|
|
529
|
+
* the complexity kernel actually gets, and the wrong major fails scoring
|
|
530
|
+
* mid-scan with an opaque plugin-list error. Reporting it here puts it where a
|
|
531
|
+
* consumer is already looking for what to fix.
|
|
532
|
+
*
|
|
533
|
+
* @param {() => string|null} parserMajorError
|
|
534
|
+
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
535
|
+
*/
|
|
536
|
+
function allPresentVerdict(parserMajorError) {
|
|
537
|
+
const parserProblem = parserMajorError();
|
|
538
|
+
if (parserProblem === null) {
|
|
539
|
+
return { ok: true, detail: 'all dependencies found' };
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
ok: false,
|
|
543
|
+
detail: 'dependency version unsupported',
|
|
544
|
+
remedy: parserProblem,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
522
548
|
/**
|
|
523
549
|
* Verify that the framework's required runtime dependencies are resolvable
|
|
524
550
|
* from the project's node_modules.
|
|
@@ -536,6 +562,8 @@ function runAgentsInSync({
|
|
|
536
562
|
* is missing.
|
|
537
563
|
* - `manifestRequired` — array of required package names, skips the
|
|
538
564
|
* filesystem read of `runtime-deps.json`.
|
|
565
|
+
* - `parserMajorError()` — replaces the resolved-parser-major probe, so the
|
|
566
|
+
* unsupported-major report is assertable without installing one.
|
|
539
567
|
*
|
|
540
568
|
* @param {{ projectRoot?: string, resolve?: (dep: string) => string, manifestRequired?: string[] }} [opts]
|
|
541
569
|
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
@@ -544,6 +572,7 @@ function runRuntimeDeps({
|
|
|
544
572
|
projectRoot,
|
|
545
573
|
resolve: resolveSeam,
|
|
546
574
|
manifestRequired,
|
|
575
|
+
parserMajorError = describeParserMajorError,
|
|
547
576
|
} = {}) {
|
|
548
577
|
// Anchor at process.cwd() (the consumer root), not resolveProjectRoot() (the
|
|
549
578
|
// package root). Under pnpm isolated-mode the consumer's node_modules are not
|
|
@@ -569,33 +598,24 @@ function runRuntimeDeps({
|
|
|
569
598
|
|
|
570
599
|
const missing = [];
|
|
571
600
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
for (const dep of required) {
|
|
588
|
-
try {
|
|
589
|
-
req.resolve(dep);
|
|
590
|
-
} catch {
|
|
591
|
-
missing.push(dep);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
601
|
+
// Anchor resolution to the consumer project root so it mirrors the context
|
|
602
|
+
// in which the framework scripts run (they free-ride on the consumer's
|
|
603
|
+
// node_modules). Under pnpm isolated-mode the consumer's node_modules are
|
|
604
|
+
// not reachable from inside node_modules/mandrel/; anchoring at process.cwd()
|
|
605
|
+
// finds them correctly.
|
|
606
|
+
//
|
|
607
|
+
// `isResolvable` is shared with the framework-side preflight rather than
|
|
608
|
+
// reimplemented here: it probes the bare specifier AND `<name>/package.json`,
|
|
609
|
+
// because a dependency with no `main` and no `exports` cannot be resolved by
|
|
610
|
+
// name at all. Probing only the bare name reports such a package missing
|
|
611
|
+
// while it sits installed, and this check gates `mandrel doctor`.
|
|
612
|
+
const resolve =
|
|
613
|
+
resolveSeam ?? createRequire(path.join(root, 'package.json')).resolve;
|
|
614
|
+
for (const dep of required) {
|
|
615
|
+
if (!isResolvable(dep, resolve)) missing.push(dep);
|
|
594
616
|
}
|
|
595
617
|
|
|
596
|
-
if (missing.length === 0)
|
|
597
|
-
return { ok: true, detail: 'all dependencies found' };
|
|
598
|
-
}
|
|
618
|
+
if (missing.length === 0) return allPresentVerdict(parserMajorError);
|
|
599
619
|
return {
|
|
600
620
|
ok: false,
|
|
601
621
|
detail: `missing: ${missing.join(', ')}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.59.0",
|
|
4
4
|
"description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
|
|
5
5
|
"files": [
|
|
6
6
|
".agents/",
|
|
@@ -109,12 +109,17 @@
|
|
|
109
109
|
"typescript": "^6.0.3"
|
|
110
110
|
},
|
|
111
111
|
"dependencies": {
|
|
112
|
+
"@babel/parser": "^7.29.3",
|
|
112
113
|
"ajv": "^8.20.0",
|
|
113
114
|
"ajv-formats": "^3.0.1",
|
|
115
|
+
"babel-runtime": "^6.26.0",
|
|
116
|
+
"escomplex-plugin-metrics-module": "^0.1.0",
|
|
117
|
+
"escomplex-plugin-syntax-babylon": "^0.1.0",
|
|
114
118
|
"js-yaml": "^4.3.2",
|
|
115
119
|
"minimatch": "^10.0.0",
|
|
116
120
|
"picomatch": "^4.0.4",
|
|
117
|
-
"typhonjs-
|
|
121
|
+
"typhonjs-ast-walker": "^0.2.1",
|
|
122
|
+
"typhonjs-escomplex-commons": "^0.1.1"
|
|
118
123
|
},
|
|
119
124
|
"peerDependencies": {
|
|
120
125
|
"@cucumber/gherkin": ">=32.0.0",
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 split-policy validator — the plan-time "one-owner-AC" split rejector.
|
|
3
|
-
*
|
|
4
|
-
* Under the v2 default-single split policy (`docs/roadmap.md` § v2.0.0), a
|
|
5
|
-
* plan authors **one Story by default**; it splits into N>1 Stories only when
|
|
6
|
-
* the pieces have near-zero overlap or sit across an architectural seam. This
|
|
7
|
-
* validator is the deterministic guardrail on that policy: **every acceptance
|
|
8
|
-
* criterion must belong to exactly one Story.** An identical AC appearing in
|
|
9
|
-
* two Stories is evidence the split coupled what should have stayed one Story,
|
|
10
|
-
* so the plan is refused rather than reconciled at delivery time (there is no
|
|
11
|
-
* epic-level acceptance reconcile in v2).
|
|
12
|
-
*
|
|
13
|
-
* Scope of the deterministic check:
|
|
14
|
-
* - **Cross-Story duplication** (always): the same normalized AC text must
|
|
15
|
-
* not appear in more than one Story.
|
|
16
|
-
* - **Full coverage** (optional, when a plan-level `acceptance` manifest is
|
|
17
|
-
* supplied): every manifest AC is claimed by exactly one Story, and no
|
|
18
|
-
* Story claims an AC absent from the manifest.
|
|
19
|
-
*
|
|
20
|
-
* Semantic overlap between differently-worded ACs is **not** caught here — it
|
|
21
|
-
* is a gate-#2 review call. This validator only sees identical (normalized)
|
|
22
|
-
* text, keeping it deterministic and false-positive-free.
|
|
23
|
-
*
|
|
24
|
-
* Normalization for comparison: trim, collapse internal whitespace, and
|
|
25
|
-
* lower-case. Reporting always uses the first-seen original text.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Normalize an acceptance string for equality comparison. Trims, collapses
|
|
30
|
-
* runs of whitespace to a single space, and lower-cases. Non-strings and
|
|
31
|
-
* empty/whitespace-only strings normalize to `null` (ignored).
|
|
32
|
-
*
|
|
33
|
-
* @param {unknown} ac
|
|
34
|
-
* @returns {string | null}
|
|
35
|
-
*/
|
|
36
|
-
export function normalizeAcceptance(ac) {
|
|
37
|
-
if (typeof ac !== 'string') return null;
|
|
38
|
-
const norm = ac.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
39
|
-
return norm === '' ? null : norm;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @typedef {object} StorySlice
|
|
44
|
-
* @property {string} [id] Story identifier for reporting (slug or #id).
|
|
45
|
-
* @property {string} [slug] Alternate identifier (used when `id` absent).
|
|
46
|
-
* @property {string[]} acceptance Acceptance criteria this Story claims.
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* @typedef {object} SplitPolicyViolation
|
|
51
|
-
* @property {'cross-story-duplicate'|'orphan-ac'|'unclaimed-manifest-ac'} kind
|
|
52
|
-
* @property {string} acceptance The original (first-seen) AC text.
|
|
53
|
-
* @property {string[]} [stories] Story ids sharing a duplicated AC (`cross-story-duplicate`).
|
|
54
|
-
* @property {string} [story] Story id owning an AC absent from the manifest (`orphan-ac`).
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Resolve a Story's reporting id.
|
|
59
|
-
*
|
|
60
|
-
* @param {StorySlice} story
|
|
61
|
-
* @param {number} index
|
|
62
|
-
* @returns {string}
|
|
63
|
-
*/
|
|
64
|
-
function storyId(story, index) {
|
|
65
|
-
if (story && typeof story.id === 'string' && story.id.trim() !== '') {
|
|
66
|
-
return story.id;
|
|
67
|
-
}
|
|
68
|
-
if (story && typeof story.slug === 'string' && story.slug.trim() !== '') {
|
|
69
|
-
return story.slug;
|
|
70
|
-
}
|
|
71
|
-
return `story[${index}]`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Validate that acceptance criteria partition cleanly across Stories.
|
|
76
|
-
*
|
|
77
|
-
* @param {StorySlice[]} stories The plan's Stories, each with `acceptance[]`.
|
|
78
|
-
* @param {object} [opts]
|
|
79
|
-
* @param {string[]} [opts.planAcceptance] Optional plan-level acceptance
|
|
80
|
-
* manifest. When supplied, coverage is enforced (every manifest AC claimed
|
|
81
|
-
* exactly once; no Story claims an off-manifest AC).
|
|
82
|
-
* @returns {{ ok: boolean, violations: SplitPolicyViolation[] }}
|
|
83
|
-
*/
|
|
84
|
-
export function validateAcceptancePartition(stories, opts = {}) {
|
|
85
|
-
const violations = [];
|
|
86
|
-
const list = Array.isArray(stories) ? stories : [];
|
|
87
|
-
|
|
88
|
-
// normalized AC → { original, owners: Set<storyId> }
|
|
89
|
-
const owners = new Map();
|
|
90
|
-
list.forEach((story, index) => {
|
|
91
|
-
const id = storyId(story, index);
|
|
92
|
-
const acceptance = Array.isArray(story?.acceptance) ? story.acceptance : [];
|
|
93
|
-
for (const ac of acceptance) {
|
|
94
|
-
const norm = normalizeAcceptance(ac);
|
|
95
|
-
if (norm === null) continue;
|
|
96
|
-
const existing = owners.get(norm);
|
|
97
|
-
if (existing) {
|
|
98
|
-
existing.owners.add(id);
|
|
99
|
-
} else {
|
|
100
|
-
owners.set(norm, { original: ac.trim(), owners: new Set([id]) });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Cross-Story duplication: any AC owned by more than one Story.
|
|
106
|
-
for (const { original, owners: set } of owners.values()) {
|
|
107
|
-
if (set.size > 1) {
|
|
108
|
-
violations.push({
|
|
109
|
-
kind: 'cross-story-duplicate',
|
|
110
|
-
acceptance: original,
|
|
111
|
-
stories: [...set],
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Optional coverage check against a plan-level manifest.
|
|
117
|
-
const manifest = Array.isArray(opts.planAcceptance)
|
|
118
|
-
? opts.planAcceptance
|
|
119
|
-
: null;
|
|
120
|
-
if (manifest !== null) {
|
|
121
|
-
const manifestNorms = new Map();
|
|
122
|
-
for (const ac of manifest) {
|
|
123
|
-
const norm = normalizeAcceptance(ac);
|
|
124
|
-
if (norm !== null && !manifestNorms.has(norm)) {
|
|
125
|
-
manifestNorms.set(norm, ac.trim());
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Every manifest AC must be claimed by exactly one Story.
|
|
129
|
-
for (const [norm, original] of manifestNorms) {
|
|
130
|
-
if (!owners.has(norm)) {
|
|
131
|
-
violations.push({
|
|
132
|
-
kind: 'unclaimed-manifest-ac',
|
|
133
|
-
acceptance: original,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
// No Story may claim an AC absent from the manifest.
|
|
138
|
-
for (const [norm, { original, owners: set }] of owners) {
|
|
139
|
-
if (!manifestNorms.has(norm)) {
|
|
140
|
-
violations.push({
|
|
141
|
-
kind: 'orphan-ac',
|
|
142
|
-
acceptance: original,
|
|
143
|
-
story: [...set][0],
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return { ok: violations.length === 0, violations };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Render a single violation as a human-readable line.
|
|
154
|
-
*
|
|
155
|
-
* @param {SplitPolicyViolation} v
|
|
156
|
-
* @returns {string}
|
|
157
|
-
*/
|
|
158
|
-
function formatViolation(v) {
|
|
159
|
-
switch (v.kind) {
|
|
160
|
-
case 'cross-story-duplicate':
|
|
161
|
-
return `acceptance criterion appears in ${v.stories.length} Stories (${v.stories.join(', ')}) — a coupled split; keep it one Story: "${v.acceptance}"`;
|
|
162
|
-
case 'unclaimed-manifest-ac':
|
|
163
|
-
return `plan acceptance criterion is claimed by no Story: "${v.acceptance}"`;
|
|
164
|
-
case 'orphan-ac':
|
|
165
|
-
return `Story ${v.story} claims an acceptance criterion absent from the plan manifest: "${v.acceptance}"`;
|
|
166
|
-
default:
|
|
167
|
-
return `unknown split-policy violation: "${v.acceptance}"`;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Throwing wrapper for the persist path: throws a single batched error when
|
|
173
|
-
* the acceptance criteria do not partition cleanly, otherwise returns
|
|
174
|
-
* `stories` unchanged. Wired into `plan-persist` in Stage 3.
|
|
175
|
-
*
|
|
176
|
-
* @param {StorySlice[]} stories
|
|
177
|
-
* @param {object} [opts] See {@link validateAcceptancePartition}.
|
|
178
|
-
* @returns {StorySlice[]}
|
|
179
|
-
*/
|
|
180
|
-
export function assertAcceptancePartition(stories, opts = {}) {
|
|
181
|
-
const { ok, violations } = validateAcceptancePartition(stories, opts);
|
|
182
|
-
if (ok) return stories;
|
|
183
|
-
throw new Error(
|
|
184
|
-
`[split-policy] ${violations.length} acceptance-partition violation(s) — the plan splits coupled work; refuse:\n${violations
|
|
185
|
-
.map((v) => ` - ${formatViolation(v)}`)
|
|
186
|
-
.join('\n')}`,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* spec-author-prompts.js — Story-scoped Spec / Acceptance authoring prompts.
|
|
3
|
-
*
|
|
4
|
-
* v2 keeps a single executable document per Story. These prompts used to
|
|
5
|
-
* author a separate Epic Tech Spec + Acceptance Spec that were folded into
|
|
6
|
-
* the Epic body and then restated on Stories — a duplication source. They
|
|
7
|
-
* now author only Story `## Spec` approach prose and remind authors that
|
|
8
|
-
* acceptance lives once on the Story (`acceptance[]` / `## Acceptance`).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @returns {string}
|
|
13
|
-
*/
|
|
14
|
-
export function renderTechSpecSystemPrompt() {
|
|
15
|
-
return TECH_SPEC_SYSTEM_PROMPT;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export function renderAcceptanceSpecSystemPrompt() {
|
|
22
|
-
return ACCEPTANCE_SPEC_SYSTEM_PROMPT;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const TECH_SPEC_SYSTEM_PROMPT = `You are an expert Engineering Architect.
|
|
26
|
-
Your job is to author the Story's \`## Spec\` approach section — the technical
|
|
27
|
-
how for one cohesive, executable Story. There is no separate Epic Tech Spec
|
|
28
|
-
document and no spill-to-docs path.
|
|
29
|
-
|
|
30
|
-
The Spec should outline only what an implementing agent needs beyond Goal /
|
|
31
|
-
Changes / Acceptance:
|
|
32
|
-
1. Architecture & Design (approach, seams, reuse)
|
|
33
|
-
2. Data Models (if any)
|
|
34
|
-
3. API Changes (if any)
|
|
35
|
-
4. Core Components
|
|
36
|
-
5. Security & Privacy Considerations
|
|
37
|
-
|
|
38
|
-
CRITICAL REQUIREMENTS:
|
|
39
|
-
- Respond ONLY with valid Markdown suitable to paste into a Story \`## Spec\`.
|
|
40
|
-
- Do not use top-level <h1> (# ) tags. Prefer \`##\` / \`###\` under Spec.
|
|
41
|
-
- Do NOT restate the Story's Goal, Acceptance, Verify, Changes, or Non-Goals —
|
|
42
|
-
those sections already live on the same Story body. Restatement is
|
|
43
|
-
duplication and a drift risk. If a brief orientation helps, keep a
|
|
44
|
-
\`## Technical Overview\` to 2–3 sentences naming the technical approach only.
|
|
45
|
-
- Do NOT author a Delivery Slicing / fan-out table. If the work needs multiple
|
|
46
|
-
independent Stories, say so in one short note and stop — oversized Specs
|
|
47
|
-
mean the Story should be split, not documented elsewhere.
|
|
48
|
-
- Format architectural decisions clearly with bullet points.
|
|
49
|
-
- Keep the Spec lean enough to stay inline on the Story (persist rejects
|
|
50
|
-
over-budget Specs; they are never written under docs/).`;
|
|
51
|
-
|
|
52
|
-
const ACCEPTANCE_SPEC_SYSTEM_PROMPT = `You are an expert Acceptance Engineer.
|
|
53
|
-
Your job is to help author the Story's binding acceptance contract — not a
|
|
54
|
-
separate Acceptance Spec document.
|
|
55
|
-
|
|
56
|
-
v2 rule: acceptance lives **once**, on the Story:
|
|
57
|
-
- Machine contract: top-level \`acceptance[]\` (and \`verify[]\`) on the ticket JSON
|
|
58
|
-
- Human document: the same items rendered under \`## Acceptance\` / \`## Verify\`
|
|
59
|
-
on the Story body (persist syncs top-level into the body)
|
|
60
|
-
|
|
61
|
-
Do **not** author an Epic Acceptance Table, PRD restatement, or a second
|
|
62
|
-
criteria list inside \`## Spec\`.
|
|
63
|
-
|
|
64
|
-
CRITICAL REQUIREMENTS:
|
|
65
|
-
- Respond ONLY with guidance or a draft \`acceptance[]\` / \`verify[]\` list for
|
|
66
|
-
one Story — never a parallel "Acceptance Spec" markdown artifact.
|
|
67
|
-
- Every acceptance item MUST be observable from outside the agent (command
|
|
68
|
-
exits 0, file exists, selector resolves, fixture count matches). Reject
|
|
69
|
-
vague "matches the spec" / "looks good" items.
|
|
70
|
-
- Every verify entry MUST name a tier in parentheses: unit | contract | e2e |
|
|
71
|
-
validate (or \`manual:<reason>\` when genuinely unverifiable in isolation).
|
|
72
|
-
- Do NOT re-elaborate Goal or Spec prose inside acceptance items — bind the
|
|
73
|
-
outcome, not the approach.
|
|
74
|
-
- Acceptance Outcomes MUST NOT prescribe a commit subject that begins with a
|
|
75
|
-
non-Conventional-Commits prefix (allowed leading types: feat|fix|chore|
|
|
76
|
-
refactor|perf|docs|style|test|build|ci|revert).`;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: scope-triage
|
|
3
|
-
description:
|
|
4
|
-
Optional split-advisory for `/mandrel-plan`. Under v2 there is no epic|story routing
|
|
5
|
-
verdict — `/mandrel-plan` always authors Stories. Use this skill only when judging
|
|
6
|
-
whether a draft should stay one Story or legitimately split (near-zero
|
|
7
|
-
overlap or an architectural seam).
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# scope-triage (split advisory)
|
|
11
|
-
|
|
12
|
-
## Policy Capsule
|
|
13
|
-
|
|
14
|
-
- There is **no** `epic | story | borderline` routing verdict in v2. `/mandrel-plan`
|
|
15
|
-
is a single path that emits **one Story by default**.
|
|
16
|
-
- This skill is an optional **split advisory**: should the author keep one
|
|
17
|
-
Story, or does the seed clear the default-single split policy?
|
|
18
|
-
- Anchor sizing judgment to `DELIVERABLE_GRANULARITY_GUIDANCE` in
|
|
19
|
-
[`ticket-validator-sizing.js`](../../../scripts/lib/orchestration/ticket-validator-sizing.js).
|
|
20
|
-
There is no numeric threshold to restate — Story #5312 deleted the
|
|
21
|
-
plan-time capacity ceilings.
|
|
22
|
-
- Lead with **cohesion**: one Story is one coherent change with one reason
|
|
23
|
-
to exist. Coupled work stays one Story and uses `## Slicing` for
|
|
24
|
-
intra-session checkpoints.
|
|
25
|
-
- Emit an **advisory only** — `keep-single` (the default) or `split` with its
|
|
26
|
-
seam/overlap rationale. **Never auto-route**: the operator decides, and
|
|
27
|
-
`--yes` defaults to `keep-single`.
|
|
28
|
-
|
|
29
|
-
## Split policy (when N>1 is allowed)
|
|
30
|
-
|
|
31
|
-
Split into sibling Stories **only** when:
|
|
32
|
-
|
|
33
|
-
1. **Near-zero overlap** — genuinely independent capabilities that happen to
|
|
34
|
-
share a seed idea, or
|
|
35
|
-
2. **Architectural seam** — different deployables, or a migration vs the
|
|
36
|
-
feature that consumes it.
|
|
37
|
-
|
|
38
|
-
Otherwise keep one Story. Persist refuses coupled splits via
|
|
39
|
-
`assertAcceptancePartition` (identical AC text across Stories).
|
|
40
|
-
|
|
41
|
-
## Advisory output
|
|
42
|
-
|
|
43
|
-
Emit one of:
|
|
44
|
-
|
|
45
|
-
- `keep-single` — default; fold complexity into `## Spec` / `## Slicing`
|
|
46
|
-
- `split` — list the proposed Stories and the seam/overlap rationale
|
|
47
|
-
|
|
48
|
-
Never auto-route. The operator (or `--yes` default = `keep-single`) decides.
|