@namewta/speculo 1.0.2 → 1.0.3
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 +6 -2
- package/package.json +2 -2
- package/template/AGENTS.md +3 -1
- package/template/canonical/canonical-specdev-goal-plan.md +757 -225
- package/template/canonical/canonical-specdev-grill-with-docs.md +221 -133
- package/template/canonical/canonical-specdev-spec.md +73 -3
- package/template/canonical/canonical-specdev-tickets.md +681 -252
- package/template/canonical/canonical-specdev-wayfinder.md +330 -113
- package/template/commands/git-repository-audit.md +3 -602
- package/template/commands/references/git-repository-audit-procedure.md +608 -0
- package/template/skills/writing-great-skills/SKILL.md +2 -0
- package/template/skills/writing-great-skills/references/document-contract.md +23 -0
- package/template/workflows/learning/common/rules/activation-and-memory.md +7 -3
- package/template/workflows/ops/common/rules/activation-and-memory.md +7 -3
- package/template/workflows/person/common/rules/activation-and-memory.md +7 -3
- package/template/workflows/specdev/G-grill-with-docs/G-grill-with-docs.md +13 -136
- package/template/workflows/specdev/G-grill-with-docs/references/interview-procedure.md +134 -0
- package/template/workflows/specdev/I-implement/I-implement.md +15 -189
- package/template/workflows/specdev/I-implement/evidence-template.md +12 -0
- package/template/workflows/specdev/I-implement/execution-preflight.md +1 -1
- package/template/workflows/specdev/I-implement/references/implementation-procedure.md +192 -0
- package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +28 -143
- package/template/workflows/specdev/P-goal-plan/completion-control.md +1 -1
- package/template/workflows/specdev/P-goal-plan/references/goal-lifecycle.md +35 -0
- package/template/workflows/specdev/P-goal-plan/references/goal-tickets-map-template.md +15 -0
- package/template/workflows/specdev/P-goal-plan/references/map-control.md +28 -0
- package/template/workflows/specdev/{O-orchestrate-implementation/O-orchestrate-implementation.md → P-goal-plan/references/multi-change-plan.md} +21 -33
- package/template/workflows/specdev/P-goal-plan/references/replan-and-recovery.md +21 -0
- package/template/workflows/specdev/P-goal-plan/references/single-change-plan.md +149 -0
- package/template/workflows/specdev/R-review-architecture/R-review-architecture.md +48 -53
- package/template/workflows/specdev/R-review-architecture/architecture-review-template.md +19 -10
- package/template/workflows/specdev/R-review-architecture/proposal-to-ticket.md +3 -1
- package/template/workflows/specdev/R-review-architecture/review-rubric.md +52 -0
- package/template/workflows/specdev/README.md +36 -216
- package/template/workflows/specdev/T-tickets/T-tickets.md +19 -230
- package/template/workflows/specdev/T-tickets/references/planning-procedure.md +233 -0
- package/template/workflows/specdev/T-tickets/ticket-template.md +16 -0
- package/template/workflows/specdev/T-tickets/tickets-map-template.md +14 -0
- package/template/workflows/specdev/T-triage/T-triage.md +3 -1
- package/template/workflows/specdev/W-wayfinder/W-wayfinder.md +24 -118
- package/template/workflows/specdev/W-wayfinder/references/initiative-discovery.md +29 -0
- package/template/workflows/specdev/W-wayfinder/references/initiative-template.json +8 -0
- package/template/workflows/specdev/W-wayfinder/references/map-traversal.md +120 -0
- package/template/workflows/specdev/W-wayfinder/wayfinder-map-template.md +4 -0
- package/template/workflows/specdev/common/README.md +1 -1
- package/template/workflows/specdev/common/rules/activation-and-memory.md +7 -3
- package/template/workflows/specdev/common/rules/artifact-contract.md +10 -2
- package/template/workflows/specdev/common/rules/operating-governance.md +38 -0
- package/template/workflows/specdev/common/rules/parent-implementation-orchestration.md +6 -2
- package/template/workflows/specdev/common/rules/skill-invocation.md +27 -0
- package/template/workflows/specdev/common/rules/workflow-routing.md +24 -0
- package/template/workflows/specdev/common/rules/workflow-state-and-lifecycle.md +93 -0
- package/template/workflows/specdev/common/schemas/goal-tickets-map.schema.json +33 -0
- package/template/workflows/specdev/common/schemas/initiative.schema.json +94 -0
- package/template/workflows/specdev/common/schemas/ticket.schema.json +168 -1
- package/template/workflows/specdev/common/schemas/tickets-map.schema.json +74 -6
- package/template/workflows/specdev/common/skills/code-review/SKILL.md +3 -2
- package/template/workflows/specdev/common/skills/code-review/references/risk-review.md +25 -0
- package/template/workflows/specdev/common/skills/plan-quality-review/SKILL.md +10 -0
- package/template/workflows/specdev/common/skills/plan-quality-review/references/checklist.md +13 -0
- package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +5 -83
- package/template/workflows/specdev/common/skills/subagent-delivery/references/dispatch-and-accept.md +87 -0
- package/template/workflows/specdev/common/tools/README.md +14 -2
- package/template/workflows/specdev/common/tools/plan-contract.mjs +256 -0
- package/template/workflows/specdev/common/tools/ticket-control.mjs +251 -0
- package/template/workflows/specdev/common/tools/validate-specdev.mjs +58 -40
- package/template/workflows/specdev/manifest.json +97 -1
- package/template/canonical/canonical-specdev-orchestrate-implementation.md +0 -2839
- package/template/workflows/specdev/O-orchestrate-implementation/implementation-evidence-template.md +0 -39
- package/template/workflows/specdev/O-orchestrate-implementation/implementation-map-template.md +0 -50
- package/template/workflows/specdev/O-orchestrate-implementation/implementation-plan-template.md +0 -61
- package/template/workflows/specdev/R-review-architecture/architecture-report-contract.md +0 -123
- package/template/workflows/specdev/R-review-architecture/architecture-review-report-template.html +0 -106
- /package/template/workflows/specdev/{O-orchestrate-implementation/conflict-and-drift.md → P-goal-plan/references/multi-conflict-and-drift.md} +0 -0
- /package/template/workflows/specdev/{O-orchestrate-implementation/execution-loop.md → P-goal-plan/references/multi-execution-loop.md} +0 -0
- /package/template/workflows/specdev/{O-orchestrate-implementation/input-readiness.md → P-goal-plan/references/multi-input-readiness.md} +0 -0
- /package/template/workflows/specdev/{O-orchestrate-implementation/super-dag.md → P-goal-plan/references/multi-super-dag.md} +0 -0
|
@@ -20,6 +20,11 @@ import { execFileSync } from "node:child_process";
|
|
|
20
20
|
import { dirname, basename, extname, join, relative, resolve, sep } from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
22
22
|
|
|
23
|
+
import {
|
|
24
|
+
validateTicketPlan, validatePlanMap, validateInvocationCoverage,
|
|
25
|
+
validateGoalMap, validateInitiative, resolveProjectSource,
|
|
26
|
+
} from "./plan-contract.mjs";
|
|
27
|
+
|
|
23
28
|
const DOMAIN_SCHEMA_VERSION = 3;
|
|
24
29
|
const CONFIG_SCHEMA_VERSION = 5;
|
|
25
30
|
const GOAL_PLAN_SCHEMA_VERSION = 6;
|
|
@@ -42,7 +47,6 @@ const EXPECTED_WORKS = new Set([
|
|
|
42
47
|
"I-implement",
|
|
43
48
|
"I-init-setup",
|
|
44
49
|
"L-learn-change",
|
|
45
|
-
"O-orchestrate-implementation",
|
|
46
50
|
"P-goal-plan",
|
|
47
51
|
"P-prototype",
|
|
48
52
|
"R-review-architecture",
|
|
@@ -111,7 +115,6 @@ const VALID_STAGES = new Set([
|
|
|
111
115
|
"review",
|
|
112
116
|
"prototype",
|
|
113
117
|
"wayfinder",
|
|
114
|
-
"orchestrate-implementation",
|
|
115
118
|
"complete",
|
|
116
119
|
]);
|
|
117
120
|
const REQUIRED_TICKET_KEYS = new Set([
|
|
@@ -175,10 +178,11 @@ const STATE_ARTIFACT_BASENAMES = new Set([
|
|
|
175
178
|
"diagnosis.md",
|
|
176
179
|
"source.md",
|
|
177
180
|
"architecture-review.md",
|
|
178
|
-
"architecture-review.html",
|
|
179
181
|
"wayfinder-map.md",
|
|
180
182
|
"design-tree.json",
|
|
181
183
|
"implementation-orchestration.md",
|
|
184
|
+
"initiative.json",
|
|
185
|
+
"goal-delivery.md",
|
|
182
186
|
]);
|
|
183
187
|
const FORBIDDEN_OBSOLETE_BASENAMES = new Set([
|
|
184
188
|
"source-issue.md",
|
|
@@ -240,7 +244,11 @@ function parseScalar(raw) {
|
|
|
240
244
|
return value.slice(1, -1);
|
|
241
245
|
}
|
|
242
246
|
if (value.startsWith("[") && value.endsWith("]")) {
|
|
247
|
+
// JSON object arrays are used by the additive Skill invocation contract.
|
|
248
|
+
// Keep the existing plain YAML scalar-list form for old artifacts.
|
|
249
|
+
try { return JSON.parse(value); } catch { /* legacy scalar list below */ }
|
|
243
250
|
const inner = value.slice(1, -1).trim();
|
|
251
|
+
if (inner.startsWith("{")) throw new Error("invalid JSON object array in frontmatter");
|
|
244
252
|
if (!inner) return [];
|
|
245
253
|
return inner.split(",").map((item) => parseScalar(item));
|
|
246
254
|
}
|
|
@@ -296,6 +304,9 @@ function parseFrontmatter(path) {
|
|
|
296
304
|
const colon = line.indexOf(":");
|
|
297
305
|
if (colon < 0) continue;
|
|
298
306
|
const key = line.slice(0, colon).trim();
|
|
307
|
+
if (["__proto__", "constructor", "prototype"].includes(key) || Object.hasOwn(meta, key)) {
|
|
308
|
+
throw new Error(`${path}: duplicate or unsafe frontmatter key ${key}`);
|
|
309
|
+
}
|
|
299
310
|
const raw = line.slice(colon + 1).trim();
|
|
300
311
|
if (!raw) {
|
|
301
312
|
meta[key] = [];
|
|
@@ -854,13 +865,6 @@ function capabilityChecks(root) {
|
|
|
854
865
|
],
|
|
855
866
|
],
|
|
856
867
|
],
|
|
857
|
-
[
|
|
858
|
-
"orchestrate-implementation",
|
|
859
|
-
[
|
|
860
|
-
join(root, "O-orchestrate-implementation", "O-orchestrate-implementation.md"),
|
|
861
|
-
["Ready Spec", "Ready Tickets", "Implementation Map", "Implementation Plan", "lead-directed", "implementation_agent_limit", "serialization", "I-implement"],
|
|
862
|
-
],
|
|
863
|
-
],
|
|
864
868
|
[
|
|
865
869
|
"implement",
|
|
866
870
|
[
|
|
@@ -900,7 +904,14 @@ function capabilityChecks(root) {
|
|
|
900
904
|
"architecture-review",
|
|
901
905
|
[
|
|
902
906
|
join(root, "R-review-architecture", "R-review-architecture.md"),
|
|
903
|
-
["
|
|
907
|
+
["结构性坏味道", "code-judo", "删除测试", "高置信候选", "最佳推荐"],
|
|
908
|
+
],
|
|
909
|
+
],
|
|
910
|
+
[
|
|
911
|
+
"architecture-review-rubric",
|
|
912
|
+
[
|
|
913
|
+
join(root, "R-review-architecture", "review-rubric.md"),
|
|
914
|
+
["删除测试", "文件大小", "高置信候选", "spaghetti growth"],
|
|
904
915
|
],
|
|
905
916
|
],
|
|
906
917
|
[
|
|
@@ -938,15 +949,6 @@ function capabilityChecks(root) {
|
|
|
938
949
|
errors.push(`capability '${ability}' lost markers: ${JSON.stringify(missing)}`);
|
|
939
950
|
}
|
|
940
951
|
}
|
|
941
|
-
if (!isFile(join(root, "R-review-architecture", "architecture-review-report-template.html"))) {
|
|
942
|
-
errors.push("architecture review lost its visual HTML report template");
|
|
943
|
-
} else {
|
|
944
|
-
const html = readText(join(root, "R-review-architecture", "architecture-review-report-template.html"));
|
|
945
|
-
for (const marker of ["cdn.tailwindcss.com", "mermaid", "Before", "After", "top-recommendation"]) {
|
|
946
|
-
if (!html.includes(marker)) errors.push(`architecture HTML template lost marker ${marker}`);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
952
|
for (const required of [
|
|
951
953
|
"common/rules/codebase-design.md",
|
|
952
954
|
"common/schemas/design-tree.schema.json",
|
|
@@ -954,14 +956,9 @@ function capabilityChecks(root) {
|
|
|
954
956
|
"G-grill-with-docs/design-tree-template.json",
|
|
955
957
|
"W-wayfinder/local-tracker-contract.md",
|
|
956
958
|
"W-wayfinder/solution-comment-template.md",
|
|
957
|
-
"R-review-architecture/architecture-report-contract.md",
|
|
958
959
|
"common/rules/parent-implementation-orchestration.md",
|
|
959
960
|
"common/schemas/implementation-map.schema.json",
|
|
960
961
|
"common/schemas/implementation-plan.schema.json",
|
|
961
|
-
"O-orchestrate-implementation/input-readiness.md",
|
|
962
|
-
"O-orchestrate-implementation/implementation-map-template.md",
|
|
963
|
-
"O-orchestrate-implementation/implementation-plan-template.md",
|
|
964
|
-
"O-orchestrate-implementation/implementation-evidence-template.md",
|
|
965
962
|
]) {
|
|
966
963
|
if (!isFile(join(root, required))) errors.push(`missing architecture/wayfinding contract ${required}`);
|
|
967
964
|
}
|
|
@@ -1555,6 +1552,9 @@ function validateProjectSkillMatrix(body, label, errors, repoRoot = null) {
|
|
|
1555
1552
|
errors.push(`${label}: project Skill Path escapes the project root: ${skillPath}`);
|
|
1556
1553
|
} else if (!isFile(resolvedSkill)) {
|
|
1557
1554
|
errors.push(`${label}: project Skill does not exist under --repo: ${skillPath}`);
|
|
1555
|
+
} else {
|
|
1556
|
+
try { resolveProjectSource(resolvedRepo, `<Path>${skillPath}</Path>`); }
|
|
1557
|
+
catch (error) { errors.push(`${label}: ${error.message}`); }
|
|
1558
1558
|
}
|
|
1559
1559
|
}
|
|
1560
1560
|
entries.push({ appliesTo: new Set(appliesTo), path: skillPath });
|
|
@@ -1591,6 +1591,11 @@ function validateMap(path, errors, repoRoot = null) {
|
|
|
1591
1591
|
return null;
|
|
1592
1592
|
}
|
|
1593
1593
|
const { meta, body } = parseFrontmatter(path);
|
|
1594
|
+
if (meta.artifact === "goal-tickets-map") {
|
|
1595
|
+
errors.push(...validateGoalMap({ path, meta, body }, dirname(path)).map(error => `${basename(path)}: ${error}`));
|
|
1596
|
+
return { path, meta, body, projectSkillMatrix: [], goalMap: true };
|
|
1597
|
+
}
|
|
1598
|
+
errors.push(...validatePlanMap({ path, meta, body }, dirname(path)).map(error => `${basename(path)}: ${error}`));
|
|
1594
1599
|
if (meta.artifact !== "tickets-map" || meta.schema_version !== DOMAIN_SCHEMA_VERSION) {
|
|
1595
1600
|
errors.push(`${basename(path)}: artifact/schema_version must be tickets-map/${DOMAIN_SCHEMA_VERSION}`);
|
|
1596
1601
|
}
|
|
@@ -2392,13 +2397,13 @@ function validateGitEvidence(repoRoot, changeStatus, errors) {
|
|
|
2392
2397
|
}
|
|
2393
2398
|
}
|
|
2394
2399
|
|
|
2395
|
-
function validateParentImplementation(change, parentStatus, stage, errors, warnings) {
|
|
2396
|
-
const required = stage === "
|
|
2400
|
+
function validateParentImplementation(change, parentStatus, stage, errors, warnings, repoRoot = null) {
|
|
2401
|
+
const required = stage === "goal-plan";
|
|
2397
2402
|
const mapPath = join(change, "implementation-map.md");
|
|
2398
2403
|
const planPath = join(change, "implementation-plan.md");
|
|
2399
2404
|
if (!required && !isFile(mapPath) && !isFile(planPath)) return null;
|
|
2400
|
-
if (!isFile(mapPath)) errors.push("
|
|
2401
|
-
if (!isFile(planPath)) errors.push("
|
|
2405
|
+
if (!isFile(mapPath)) errors.push("goal-plan stage requires implementation-map.md");
|
|
2406
|
+
if (!isFile(planPath)) errors.push("goal-plan stage requires implementation-plan.md");
|
|
2402
2407
|
if (!isFile(mapPath) || !isFile(planPath)) return null;
|
|
2403
2408
|
|
|
2404
2409
|
const parentName = basename(change);
|
|
@@ -2552,7 +2557,7 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2552
2557
|
}
|
|
2553
2558
|
|
|
2554
2559
|
const childMapPath = join(memberRoot, "tickets-map.md");
|
|
2555
|
-
const childMap = isFile(childMapPath) ? validateMap(childMapPath, memberErrors) : null;
|
|
2560
|
+
const childMap = isFile(childMapPath) ? validateMap(childMapPath, memberErrors, repoRoot) : null;
|
|
2556
2561
|
if (!childMap) memberErrors.push("Ready Tickets Map is required before parent creation");
|
|
2557
2562
|
else {
|
|
2558
2563
|
if (childMap.meta.change !== member) memberErrors.push("tickets-map.md: change must equal member directory name");
|
|
@@ -2569,6 +2574,7 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2569
2574
|
const childTickets = new Map();
|
|
2570
2575
|
for (const name of ticketFiles) {
|
|
2571
2576
|
const artifact = validateTicket(join(ticketRoot, name), memberErrors);
|
|
2577
|
+
if (artifact) memberErrors.push(...validateTicketPlan(artifact, { repoRoot }).map(error => `${name}: ${error}`));
|
|
2572
2578
|
if (!artifact) continue;
|
|
2573
2579
|
const ticketId = String(artifact.meta.id);
|
|
2574
2580
|
if (artifact.meta.change !== member) memberErrors.push(`${name}: change must equal member directory name`);
|
|
@@ -2592,8 +2598,10 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2592
2598
|
memberErrors.push(`${ticketId}: parent creation requires status=ready and ready=true`);
|
|
2593
2599
|
} else if (new Set(["ready", "in_progress", "review"]).has(artifact.meta.status) && artifact.meta.ready !== true) {
|
|
2594
2600
|
memberErrors.push(`${ticketId}: executable Ticket must keep ready=true`);
|
|
2595
|
-
} else if (new Set(["blocked", "deviated"]).has(artifact.meta.status)
|
|
2596
|
-
|
|
2601
|
+
} else if (new Set(["blocked", "deviated"]).has(artifact.meta.status)) {
|
|
2602
|
+
// A local blocker does not revoke unrelated nodes' execution gate.
|
|
2603
|
+
// The controller excludes this node and its dependency closure; only a
|
|
2604
|
+
// global blocker or an empty legal frontier pauses the whole parent.
|
|
2597
2605
|
} else if (artifact.meta.status === "draft") {
|
|
2598
2606
|
memberErrors.push(`${ticketId}: draft Ticket cannot belong to a parent implementation`);
|
|
2599
2607
|
}
|
|
@@ -2601,6 +2609,7 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2601
2609
|
}
|
|
2602
2610
|
|
|
2603
2611
|
if (childMap) {
|
|
2612
|
+
memberErrors.push(...validateInvocationCoverage(childTickets, childMap.projectSkillMatrix));
|
|
2604
2613
|
validateProjectSkillCoverage(
|
|
2605
2614
|
childMap.projectSkillMatrix,
|
|
2606
2615
|
childTickets.keys(),
|
|
@@ -2772,8 +2781,8 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
|
|
|
2772
2781
|
if (count > 1) errors.push(`repository/ref integration must be serialized for ${ref}; found ${count}`);
|
|
2773
2782
|
}
|
|
2774
2783
|
|
|
2775
|
-
if (required && parentStatus && new Set(["active", "blocked"]).has(parentStatus.change_status) &&
|
|
2776
|
-
errors.push("parent active/blocked status must keep current_work=specdev/
|
|
2784
|
+
if (required && parentStatus && new Set(["active", "blocked"]).has(parentStatus.change_status) && !new Set(["specdev/goal-plan"]).has(parentStatus.current_work)) {
|
|
2785
|
+
errors.push("parent active/blocked status must keep current_work=specdev/goal-plan");
|
|
2777
2786
|
}
|
|
2778
2787
|
if (parentStatus?.change_status === "completed") {
|
|
2779
2788
|
const incomplete = members.filter((member) => memberStatuses.get(member)?.change_status !== "completed");
|
|
@@ -2841,7 +2850,8 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2841
2850
|
}
|
|
2842
2851
|
|
|
2843
2852
|
const changeStatus = validateChangeStatus(join(change, ".status.json"), basename(change), errors);
|
|
2844
|
-
validateParentImplementation(change, changeStatus, stage, errors, warnings);
|
|
2853
|
+
validateParentImplementation(change, changeStatus, stage, errors, warnings, repoRoot);
|
|
2854
|
+
errors.push(...validateInitiative(change));
|
|
2845
2855
|
if (isFile(join(change, "source-issue.md"))) {
|
|
2846
2856
|
errors.push("obsolete source-issue.md is forbidden; use source.md without compatibility fallback");
|
|
2847
2857
|
}
|
|
@@ -2862,13 +2872,14 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2862
2872
|
validatePrototypes(change, stage === "prototype", errors);
|
|
2863
2873
|
validateChangeLearning(change, stage === "learn-change", errors);
|
|
2864
2874
|
|
|
2865
|
-
const
|
|
2875
|
+
const isParentImplementation = isFile(join(change, "implementation-map.md"));
|
|
2876
|
+
const specRequired = new Set(["spec", "tickets", "goal-plan", "implement", "complete"]).has(stage) && !isParentImplementation;
|
|
2866
2877
|
const specPath = join(change, "spec.md");
|
|
2867
2878
|
const spec = isFile(specPath) || specRequired
|
|
2868
2879
|
? validateSpec(specPath, errors, warnings)
|
|
2869
2880
|
: null;
|
|
2870
2881
|
const ticketMode = isDirectory(join(change, "ticket"));
|
|
2871
|
-
const mapRequired = new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode);
|
|
2882
|
+
const mapRequired = !isParentImplementation && (new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode));
|
|
2872
2883
|
const mapPath = join(change, "tickets-map.md");
|
|
2873
2884
|
const ticketsMap = isFile(mapPath) || mapRequired ? validateMap(mapPath, errors, repoRoot) : null;
|
|
2874
2885
|
const goalPlanPath = join(change, "goal-plan.md");
|
|
@@ -2892,7 +2903,7 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2892
2903
|
}
|
|
2893
2904
|
|
|
2894
2905
|
const ticketDir = join(change, "ticket");
|
|
2895
|
-
const ticketsRequired = new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode);
|
|
2906
|
+
const ticketsRequired = !isParentImplementation && (new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode));
|
|
2896
2907
|
const ticketFiles = isDirectory(ticketDir)
|
|
2897
2908
|
? readdirSync(ticketDir)
|
|
2898
2909
|
.filter((name) => name.endsWith(".md"))
|
|
@@ -2909,6 +2920,7 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2909
2920
|
for (const path of ticketFiles) {
|
|
2910
2921
|
const artifact = validateTicket(path, errors);
|
|
2911
2922
|
if (!artifact) continue;
|
|
2923
|
+
errors.push(...validateTicketPlan(artifact, { repoRoot, requirePlan: stage === "implement" }).map(error => `${basename(path)}: ${error}`));
|
|
2912
2924
|
const ticketId = String(artifact.meta.id);
|
|
2913
2925
|
if (artifact.meta.change !== basename(change)) {
|
|
2914
2926
|
errors.push(`${basename(path)}: change must equal directory name ${basename(change)}`);
|
|
@@ -2931,7 +2943,8 @@ function validateChange(change, stage = null, repoRoot = null) {
|
|
|
2931
2943
|
}
|
|
2932
2944
|
}
|
|
2933
2945
|
|
|
2934
|
-
if (ticketsMap) {
|
|
2946
|
+
if (ticketsMap && !ticketsMap.goalMap) {
|
|
2947
|
+
errors.push(...validateInvocationCoverage(tickets, ticketsMap.projectSkillMatrix));
|
|
2935
2948
|
validateProjectSkillCoverage(
|
|
2936
2949
|
ticketsMap.projectSkillMatrix,
|
|
2937
2950
|
tickets.keys(),
|
|
@@ -3210,4 +3223,9 @@ function main(argv) {
|
|
|
3210
3223
|
return usage();
|
|
3211
3224
|
}
|
|
3212
3225
|
|
|
3213
|
-
|
|
3226
|
+
export { parseFrontmatter, validateChange, validateTicket, validateMap, pathsOverlap, findCycle };
|
|
3227
|
+
|
|
3228
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
3229
|
+
try { process.exitCode = main(process.argv.slice(2)); }
|
|
3230
|
+
catch (error) { console.error(`ERROR: ${error.message}`); process.exitCode = 1; }
|
|
3231
|
+
}
|
|
@@ -1 +1,97 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"id": "specdev",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"persistent_knowledge": [
|
|
6
|
+
"<Path>{roots.state}/specdev/adr/</Path>",
|
|
7
|
+
"<Path>{roots.state}/specdev/context/</Path>"
|
|
8
|
+
],
|
|
9
|
+
"stages": [
|
|
10
|
+
{
|
|
11
|
+
"id": "intake",
|
|
12
|
+
"after": [],
|
|
13
|
+
"inputs": [
|
|
14
|
+
"request"
|
|
15
|
+
],
|
|
16
|
+
"outputs": [
|
|
17
|
+
"source",
|
|
18
|
+
"triage"
|
|
19
|
+
],
|
|
20
|
+
"risk": "read-only",
|
|
21
|
+
"context_budget": 12000
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "decide",
|
|
25
|
+
"after": [],
|
|
26
|
+
"inputs": [
|
|
27
|
+
"request-or-frozen-source"
|
|
28
|
+
],
|
|
29
|
+
"outputs": [
|
|
30
|
+
"spec",
|
|
31
|
+
"adr"
|
|
32
|
+
],
|
|
33
|
+
"risk": "read-only",
|
|
34
|
+
"context_budget": 20000
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "plan",
|
|
38
|
+
"after": [
|
|
39
|
+
"decide"
|
|
40
|
+
],
|
|
41
|
+
"inputs": [
|
|
42
|
+
"spec"
|
|
43
|
+
],
|
|
44
|
+
"outputs": [
|
|
45
|
+
"ticket",
|
|
46
|
+
"plan"
|
|
47
|
+
],
|
|
48
|
+
"risk": "read-only",
|
|
49
|
+
"context_budget": 18000
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "execute",
|
|
53
|
+
"after": [
|
|
54
|
+
"plan"
|
|
55
|
+
],
|
|
56
|
+
"inputs": [
|
|
57
|
+
"ticket",
|
|
58
|
+
"plan"
|
|
59
|
+
],
|
|
60
|
+
"outputs": [
|
|
61
|
+
"code",
|
|
62
|
+
"evidence"
|
|
63
|
+
],
|
|
64
|
+
"risk": "local-reversible",
|
|
65
|
+
"context_budget": 24000
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "verify",
|
|
69
|
+
"after": [
|
|
70
|
+
"execute"
|
|
71
|
+
],
|
|
72
|
+
"inputs": [
|
|
73
|
+
"code",
|
|
74
|
+
"evidence"
|
|
75
|
+
],
|
|
76
|
+
"outputs": [
|
|
77
|
+
"verification"
|
|
78
|
+
],
|
|
79
|
+
"risk": "read-only",
|
|
80
|
+
"context_budget": 12000
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "learn",
|
|
84
|
+
"after": [
|
|
85
|
+
"verify"
|
|
86
|
+
],
|
|
87
|
+
"inputs": [
|
|
88
|
+
"verification"
|
|
89
|
+
],
|
|
90
|
+
"outputs": [
|
|
91
|
+
"learning"
|
|
92
|
+
],
|
|
93
|
+
"risk": "read-only",
|
|
94
|
+
"context_budget": 10000
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
}
|