strikethroo 3.7.0 → 3.8.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.
Files changed (93) hide show
  1. package/README.md +12 -12
  2. package/dist/index.js +1 -1
  3. package/dist/metadata.d.ts +1 -1
  4. package/dist/metadata.js +1 -1
  5. package/dist/serve/derivation.d.ts +1 -0
  6. package/dist/serve/derivation.d.ts.map +1 -1
  7. package/dist/serve/derivation.js +1 -0
  8. package/dist/serve/derivation.js.map +1 -1
  9. package/dist/serve/markdown.d.ts +1 -0
  10. package/dist/serve/markdown.d.ts.map +1 -1
  11. package/dist/serve/markdown.js +9 -0
  12. package/dist/serve/markdown.js.map +1 -1
  13. package/dist/skill-scripts/shared/root.js +1 -1
  14. package/dist-web/assets/{arc-xFaTbwSI.js → arc-ClZ3judi.js} +1 -1
  15. package/dist-web/assets/{architectureDiagram-3BPJPVTR-Xz0i2dF0.js → architectureDiagram-3BPJPVTR-BRD031Y3.js} +1 -1
  16. package/dist-web/assets/{blockDiagram-GPEHLZMM-BdKR7Rki.js → blockDiagram-GPEHLZMM-CfaXqzcG.js} +1 -1
  17. package/dist-web/assets/{c4Diagram-AAUBKEIU-DAayjcH7.js → c4Diagram-AAUBKEIU-D9Os_mjw.js} +1 -1
  18. package/dist-web/assets/channel-CJXJUs_u.js +1 -0
  19. package/dist-web/assets/{chunk-2J33WTMH-D4OfjzqG.js → chunk-2J33WTMH-CScaCetR.js} +1 -1
  20. package/dist-web/assets/{chunk-4BX2VUAB-Ds6twHLE.js → chunk-4BX2VUAB-C_0wTF2D.js} +1 -1
  21. package/dist-web/assets/{chunk-55IACEB6-CoVGj0A6.js → chunk-55IACEB6-BU0yxIXn.js} +1 -1
  22. package/dist-web/assets/{chunk-727SXJPM--kGZcEL_.js → chunk-727SXJPM-CEYPnfpF.js} +1 -1
  23. package/dist-web/assets/{chunk-AQP2D5EJ-dK8X6Knv.js → chunk-AQP2D5EJ-3Wp97XA5.js} +1 -1
  24. package/dist-web/assets/{chunk-FMBD7UC4-CIkH8fwk.js → chunk-FMBD7UC4-B_iff0-_.js} +1 -1
  25. package/dist-web/assets/{chunk-ND2GUHAM-BqrY0fV2.js → chunk-ND2GUHAM-C9bo0MIV.js} +1 -1
  26. package/dist-web/assets/{chunk-QZHKN3VN-Bx8TPE1r.js → chunk-QZHKN3VN-DuWFBza0.js} +1 -1
  27. package/dist-web/assets/classDiagram-4FO5ZUOK-DSnAL-3H.js +1 -0
  28. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-DSnAL-3H.js +1 -0
  29. package/dist-web/assets/{cose-bilkent-S5V4N54A-PyZxi1yI.js → cose-bilkent-S5V4N54A-BvbHJ-Zg.js} +1 -1
  30. package/dist-web/assets/{dagre-BM42HDAG-yXPAIOqC.js → dagre-BM42HDAG-D6Et-JgF.js} +1 -1
  31. package/dist-web/assets/{diagram-2AECGRRQ-DDkzzAIo.js → diagram-2AECGRRQ-CNCYsbbA.js} +1 -1
  32. package/dist-web/assets/{diagram-5GNKFQAL-BD_aXIRb.js → diagram-5GNKFQAL-CxRozyyO.js} +1 -1
  33. package/dist-web/assets/{diagram-KO2AKTUF-rntOw_IG.js → diagram-KO2AKTUF-IZlLB90E.js} +1 -1
  34. package/dist-web/assets/{diagram-LMA3HP47-BAB3HQQs.js → diagram-LMA3HP47-CrSB8ihh.js} +1 -1
  35. package/dist-web/assets/{diagram-OG6HWLK6-CwTzozdw.js → diagram-OG6HWLK6-tCjUSZwa.js} +1 -1
  36. package/dist-web/assets/{erDiagram-TEJ5UH35-BW-Ha54s.js → erDiagram-TEJ5UH35-JLv81E72.js} +1 -1
  37. package/dist-web/assets/{flowDiagram-I6XJVG4X-AGkxH_mx.js → flowDiagram-I6XJVG4X-Det1_WLC.js} +1 -1
  38. package/dist-web/assets/{ganttDiagram-6RSMTGT7--ZS9LkZ2.js → ganttDiagram-6RSMTGT7-Mi2wMw1v.js} +1 -1
  39. package/dist-web/assets/{gitGraphDiagram-PVQCEYII-D4KfIxPI.js → gitGraphDiagram-PVQCEYII-Ddy55D8B.js} +1 -1
  40. package/dist-web/assets/{index-uk0kSvAs.js → index-BwABcoi9.js} +4 -4
  41. package/dist-web/assets/{index-BN30VZfQ.js → index-CEoxzYhV.js} +1 -1
  42. package/dist-web/assets/{index-IHSMZO2U.js → index-Dw0N6U6b.js} +1 -1
  43. package/dist-web/assets/{infoDiagram-5YYISTIA-D_Z40zdo.js → infoDiagram-5YYISTIA-vrktGk5U.js} +1 -1
  44. package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-CGzxJ2vS.js → ishikawaDiagram-YF4QCWOH-CATnDGrp.js} +1 -1
  45. package/dist-web/assets/{journeyDiagram-JHISSGLW-DLtpX1Ni.js → journeyDiagram-JHISSGLW-CEvYFLj2.js} +1 -1
  46. package/dist-web/assets/{kanban-definition-UN3LZRKU-C4_O7GEC.js → kanban-definition-UN3LZRKU-_6lp0n4D.js} +1 -1
  47. package/dist-web/assets/{linear-BoMijEDN.js → linear-C1oEZdKm.js} +1 -1
  48. package/dist-web/assets/{mermaid.core-B7AM5BmV.js → mermaid.core-bqVxqCbb.js} +4 -4
  49. package/dist-web/assets/{mindmap-definition-RKZ34NQL-b8o1AjHb.js → mindmap-definition-RKZ34NQL-DxAus_u1.js} +1 -1
  50. package/dist-web/assets/{pieDiagram-4H26LBE5-D7BGsvW4.js → pieDiagram-4H26LBE5-Bqjkt8dx.js} +1 -1
  51. package/dist-web/assets/{quadrantDiagram-W4KKPZXB-BOsVmBQ_.js → quadrantDiagram-W4KKPZXB-BoZHhSII.js} +1 -1
  52. package/dist-web/assets/{requirementDiagram-4Y6WPE33-BwuELhlD.js → requirementDiagram-4Y6WPE33-D0rcLxmM.js} +1 -1
  53. package/dist-web/assets/{sankeyDiagram-5OEKKPKP-DDoQnSJz.js → sankeyDiagram-5OEKKPKP-CWShHxDe.js} +1 -1
  54. package/dist-web/assets/{sequenceDiagram-3UESZ5HK-Dto9SgGd.js → sequenceDiagram-3UESZ5HK-T8LE2GTr.js} +1 -1
  55. package/dist-web/assets/{stateDiagram-AJRCARHV-Dx0qn_5x.js → stateDiagram-AJRCARHV-a8e0ie70.js} +1 -1
  56. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-BmYjmWHJ.js +1 -0
  57. package/dist-web/assets/{timeline-definition-PNZ67QCA-Dbhzc3Xh.js → timeline-definition-PNZ67QCA-Fn2xKeuG.js} +1 -1
  58. package/dist-web/assets/{vennDiagram-CIIHVFJN-BEUtD6fT.js → vennDiagram-CIIHVFJN-CdlvIB8X.js} +1 -1
  59. package/dist-web/assets/{wardley-L42UT6IY-CqNfcWnP.js → wardley-L42UT6IY-9pciD2XX.js} +1 -1
  60. package/dist-web/assets/{wardleyDiagram-YWT4CUSO-Co_bCOQZ.js → wardleyDiagram-YWT4CUSO-BK6qNOxa.js} +1 -1
  61. package/dist-web/assets/{xychartDiagram-2RQKCTM6-CguzZIIB.js → xychartDiagram-2RQKCTM6-QjMTFvse.js} +1 -1
  62. package/dist-web/index.html +1 -1
  63. package/package.json +1 -1
  64. package/templates/harness/skills/st-create-plan/scripts/find-strikethroo-root.cjs +1 -1
  65. package/templates/harness/skills/st-create-plan/scripts/get-next-plan-id.cjs +1 -1
  66. package/templates/harness/skills/st-execute-blueprint/SKILL.md +1 -1
  67. package/templates/harness/skills/st-execute-blueprint/scripts/check-phase-readiness.cjs +1 -1
  68. package/templates/harness/skills/st-execute-blueprint/scripts/create-feature-branch.cjs +1 -1
  69. package/templates/harness/skills/st-execute-blueprint/scripts/find-strikethroo-root.cjs +1 -1
  70. package/templates/harness/skills/st-execute-blueprint/scripts/validate-plan-blueprint.cjs +51 -3
  71. package/templates/harness/skills/st-execute-task/scripts/check-task-dependencies.cjs +1 -1
  72. package/templates/harness/skills/st-execute-task/scripts/find-strikethroo-root.cjs +1 -1
  73. package/templates/harness/skills/st-execute-task/scripts/validate-plan-blueprint.cjs +51 -3
  74. package/templates/harness/skills/st-full-workflow/SKILL.md +56 -10
  75. package/templates/harness/skills/st-full-workflow/scripts/check-phase-readiness.cjs +1 -1
  76. package/templates/harness/skills/st-full-workflow/scripts/create-feature-branch.cjs +1 -1
  77. package/templates/harness/skills/st-full-workflow/scripts/find-strikethroo-root.cjs +1 -1
  78. package/templates/harness/skills/st-full-workflow/scripts/get-next-plan-id.cjs +1 -1
  79. package/templates/harness/skills/st-full-workflow/scripts/get-next-task-id.cjs +1 -1
  80. package/templates/harness/skills/st-full-workflow/scripts/validate-plan-blueprint.cjs +51 -3
  81. package/templates/harness/skills/st-generate-tasks/SKILL.md +56 -11
  82. package/templates/harness/skills/st-generate-tasks/scripts/find-strikethroo-root.cjs +1 -1
  83. package/templates/harness/skills/st-generate-tasks/scripts/get-next-task-id.cjs +1 -1
  84. package/templates/harness/skills/st-generate-tasks/scripts/validate-plan-blueprint.cjs +51 -3
  85. package/templates/harness/skills/st-refine-plan/scripts/find-strikethroo-root.cjs +1 -1
  86. package/templates/harness/skills/st-refine-plan/scripts/validate-plan-blueprint.cjs +51 -3
  87. package/templates/strikethroo/README.md +1 -1
  88. package/templates/strikethroo/config/hooks/POST_TASK_GENERATION_ALL.md +3 -13
  89. package/templates/strikethroo/config/templates/TASK_TEMPLATE.md +1 -0
  90. package/dist-web/assets/channel-Cmb5ngmL.js +0 -1
  91. package/dist-web/assets/classDiagram-4FO5ZUOK-Dh2pEfOy.js +0 -1
  92. package/dist-web/assets/classDiagram-v2-Q7XG4LA2-Dh2pEfOy.js +0 -1
  93. package/dist-web/assets/stateDiagram-v2-BHNVJYJU-qo6que2v.js +0 -1
@@ -39,7 +39,7 @@ var path4 = __toESM(require("path"));
39
39
  // src/skill-scripts/shared/root.ts
40
40
  var fs = __toESM(require("fs"));
41
41
  var path = __toESM(require("path"));
42
- var EXPECTED_SCHEMA = true ? 2 : 2;
42
+ var EXPECTED_SCHEMA = true ? 3 : 3;
43
43
  var isValidStrikethrooRoot = (strikethrooPath) => {
44
44
  try {
45
45
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -242,6 +242,12 @@ var resolvePlan = (input, startPath = process.cwd()) => {
242
242
  return resolveByIdInAncestry(planId, startPath);
243
243
  };
244
244
 
245
+ // src/skill-scripts/shared/task-file.ts
246
+ var extractFrontmatter = (content) => {
247
+ const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
248
+ return match && match[1] ? match[1] : null;
249
+ };
250
+
245
251
  // src/skill-scripts/validate-plan-blueprint.ts
246
252
  var VALID_FIELDS = [
247
253
  "planFile",
@@ -249,8 +255,47 @@ var VALID_FIELDS = [
249
255
  "taskCount",
250
256
  "blueprintExists",
251
257
  "strikethrooRoot",
252
- "planId"
258
+ "planId",
259
+ "complexityScoresValid",
260
+ "invalidComplexityTasks"
253
261
  ];
262
+ var validateComplexityScores = (planDir) => {
263
+ const tasksDir = path4.join(planDir, "tasks");
264
+ if (!fs4.existsSync(tasksDir)) return [];
265
+ let files;
266
+ try {
267
+ if (!fs4.lstatSync(tasksDir).isDirectory()) return [];
268
+ files = fs4.readdirSync(tasksDir).filter((f) => f.endsWith(".md")).sort();
269
+ } catch (_err) {
270
+ return [];
271
+ }
272
+ const invalid = [];
273
+ for (const file of files) {
274
+ let content;
275
+ try {
276
+ content = fs4.readFileSync(path4.join(tasksDir, file), "utf8");
277
+ } catch (_err) {
278
+ invalid.push(`${file}: unreadable`);
279
+ continue;
280
+ }
281
+ const frontmatter = extractFrontmatter(content);
282
+ const match = frontmatter ? frontmatter.match(/^\s*complexity_score\s*:\s*(.*?)\s*(?:#.*)?$/im) : null;
283
+ if (!match || match[1] === void 0) {
284
+ invalid.push(`${file}: missing complexity_score`);
285
+ continue;
286
+ }
287
+ const raw = match[1].trim();
288
+ if (!/^\d+$/.test(raw)) {
289
+ invalid.push(`${file}: non-integer complexity_score "${raw}"`);
290
+ continue;
291
+ }
292
+ const parsed = Number.parseInt(raw, 10);
293
+ if (parsed < 1 || parsed > 10) {
294
+ invalid.push(`${file}: complexity_score ${parsed} out of range 1-10`);
295
+ }
296
+ }
297
+ return invalid;
298
+ };
254
299
  var countTasks = (planDir) => {
255
300
  const tasksDir = path4.join(planDir, "tasks");
256
301
  if (!fs4.existsSync(tasksDir)) return 0;
@@ -324,13 +369,16 @@ var main = () => {
324
369
  }
325
370
  process.exit(1);
326
371
  }
372
+ const invalidComplexity = validateComplexityScores(resolved.planDir);
327
373
  const result = {
328
374
  planFile: resolved.planFile,
329
375
  planDir: resolved.planDir,
330
376
  strikethrooRoot: resolved.strikethrooRoot,
331
377
  planId: resolved.planId,
332
378
  taskCount: countTasks(resolved.planDir),
333
- blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no"
379
+ blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no",
380
+ complexityScoresValid: invalidComplexity.length === 0 ? "yes" : "no",
381
+ invalidComplexityTasks: invalidComplexity.join("; ")
334
382
  };
335
383
  if (fieldName) {
336
384
  if (!VALID_FIELDS.includes(fieldName)) {
@@ -222,13 +222,48 @@ For each task, identify:
222
222
 
223
223
  A task B depends on A if B requires A's output or artifacts, modifies code created by A, or tests functionality implemented by A. Validate that the final dependency graph is acyclic.
224
224
 
225
- #### 7. Allocate task IDs
225
+ #### 7. Complexity analysis
226
+
227
+ For every candidate task, assign a `complexity_score` (integer 1–10) before
228
+ writing any file. Base the score on these four dimensions:
229
+
230
+ | Score | Skill breadth | Acceptance-criteria clarity | Integration surface | Decomposition depth |
231
+ | --- | --- | --- | --- | --- |
232
+ | 1–3 | One well-known skill | Criteria are concrete and observable | None or a single file/module | No further split possible |
233
+ | 4–5 | One primary skill plus a familiar adjacent skill | Criteria are clear with few edge cases | One component or API boundary | Already atomic |
234
+ | 6–7 | Two distinct skills, or one skill with ambiguous requirements | Criteria need clarification or have multiple edge cases | Multiple components or contracts | Could still be split |
235
+ | 8–10 | Three or more skills, or cross-cutting design decisions | Criteria are vague, unknown, or depend on unresolved choices | Wide integration surface or external systems | Must be decomposed further |
236
+
237
+ **Pre-emission sanity rules** — apply these before any task is written:
238
+
239
+ - 3+ skills assigned → split the task into smaller tasks, each with 1–2 skills.
240
+ - Vague acceptance criteria → sharpen them until they include at least one
241
+ concrete, runnable verification step.
242
+ - Trivially small adjacent tasks → merge them into a single task.
243
+ - Score ≥ 8 → decompose further; do not emit as-is.
244
+ - Score 6–7 → either sharpen criteria or split; do not emit without an
245
+ explicit reason.
246
+
247
+ **Required frontmatter:**
248
+
249
+ - Every emitted task MUST include `complexity_score` (integer 1–10).
250
+ - Optionally include `complexity_notes` when the score needs justification,
251
+ such as "Ambiguous API contract" or "Decomposed from a higher-score parent".
252
+
253
+ **Loop-back rule:**
254
+
255
+ After applying split, sharpen, or merge, re-run dependency analysis and
256
+ re-score the adjusted tasks. Repeat this loop no more than three times. If
257
+ complexity is still unresolved after three passes, stop and surface the
258
+ blocker to the user.
259
+
260
+ #### 8. Allocate task IDs
226
261
 
227
262
  Run `scripts/get-next-task-id.cjs <plan-id>` to obtain the first available task ID. Allocate subsequent IDs by incrementing in-process. Use the unpadded integer in the task frontmatter `id` field and the zero-padded form (`{padded-id}--{slug}`) for the filename.
228
263
 
229
264
  The slug derives from a short task title: lowercase, alphanumeric and hyphens only, collapsed, trimmed.
230
265
 
231
- #### 8. Emit the task files
266
+ #### 9. Emit the task files
232
267
 
233
268
  Write each task to:
234
269
 
@@ -246,11 +281,15 @@ including required frontmatter fields:
246
281
  - `created` (YYYY-MM-DD)
247
282
  - `skills` (array of 1–2 kebab-case skills)
248
283
 
249
- Optional frontmatter for high-complexity or decomposed tasks:
284
+ Required additional frontmatter:
285
+
286
+ - `complexity_score` (integer 1–10, required on every emitted task)
287
+
288
+ Optional frontmatter:
250
289
 
251
- - `complexity_score` (number, 1–10, include only if >4 or for decomposed
252
- tasks)
253
- - `complexity_notes` (string)
290
+ - `complexity_notes` (string) — include when the score needs justification,
291
+ such as "Decomposed from a cross-cutting parent task" or "Ambiguous API
292
+ contract".
254
293
 
255
294
  The body sections (Objective, Skills Required, Acceptance Criteria, Technical
256
295
  Requirements, Input Dependencies, Output Artifacts, Implementation Notes)
@@ -258,7 +297,7 @@ must be filled with task-specific content. Place detailed implementation
258
297
  guidance inside a `<details>` block under "Implementation Notes" — write it
259
298
  so a non-thinking LLM could execute the task from that section alone.
260
299
 
261
- #### 9. Validation checklist
300
+ #### 10. Validation checklist
262
301
 
263
302
  Before declaring task generation complete, verify:
264
303
 
@@ -276,16 +315,23 @@ Before declaring task generation complete, verify:
276
315
  - Minimization applied (20–30% reduction target).
277
316
  - Test tasks focus on business logic, not framework functionality.
278
317
  - No gold-plating: only plan requirements are addressed.
318
+ - After writing the task files, run
319
+ `scripts/validate-plan-blueprint.cjs <plan-id> complexityScoresValid`. Stop
320
+ unless it prints `yes`; if it prints `no`, run
321
+ `scripts/validate-plan-blueprint.cjs <plan-id> invalidComplexityTasks` to see
322
+ which files are missing, non-integer, or out-of-range, fix them, and re-run.
323
+ Every generated task must carry an integer `complexity_score` from 1 to 10.
324
+ - Add `complexity_notes` only when a score needs explanation (typically atomic
325
+ tasks scoring greater than 4).
279
326
 
280
- #### 10. Run the POST_TASK_GENERATION_ALL hook
327
+ #### 11. Run the POST_TASK_GENERATION_ALL hook
281
328
 
282
329
  Read `<root>/config/hooks/POST_TASK_GENERATION_ALL.md` and follow its instructions. This typically requires:
283
330
 
284
- - Sanity-checking complexity.
285
331
  - Appending an Execution Blueprint section to the plan document, including a Mermaid dependency diagram and explicit phase groupings.
286
332
  - Use `<root>/config/templates/BLUEPRINT_TEMPLATE.md` for structure.
287
333
 
288
- #### 11. Emit the Phase 2 structured summary
334
+ #### 12. Emit the Phase 2 structured summary
289
335
 
290
336
  Conclude Phase 2 with exactly this block:
291
337
 
@@ -86,7 +86,7 @@ var path3 = __toESM(require("path"));
86
86
  // src/skill-scripts/shared/root.ts
87
87
  var fs = __toESM(require("fs"));
88
88
  var path = __toESM(require("path"));
89
- var EXPECTED_SCHEMA = true ? 2 : 2;
89
+ var EXPECTED_SCHEMA = true ? 3 : 3;
90
90
  var isValidStrikethrooRoot = (strikethrooPath) => {
91
91
  try {
92
92
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -54,7 +54,7 @@ var path3 = __toESM(require("path"));
54
54
  // src/skill-scripts/shared/root.ts
55
55
  var fs = __toESM(require("fs"));
56
56
  var path = __toESM(require("path"));
57
- var EXPECTED_SCHEMA = true ? 2 : 2;
57
+ var EXPECTED_SCHEMA = true ? 3 : 3;
58
58
  var isValidStrikethrooRoot = (strikethrooPath) => {
59
59
  try {
60
60
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(find_strikethroo_root_exports);
37
37
  // src/skill-scripts/shared/root.ts
38
38
  var fs = __toESM(require("fs"));
39
39
  var path = __toESM(require("path"));
40
- var EXPECTED_SCHEMA = true ? 2 : 2;
40
+ var EXPECTED_SCHEMA = true ? 3 : 3;
41
41
  var isValidStrikethrooRoot = (strikethrooPath) => {
42
42
  try {
43
43
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(get_next_plan_id_exports);
37
37
  // src/skill-scripts/shared/root.ts
38
38
  var fs = __toESM(require("fs"));
39
39
  var path = __toESM(require("path"));
40
- var EXPECTED_SCHEMA = true ? 2 : 2;
40
+ var EXPECTED_SCHEMA = true ? 3 : 3;
41
41
  var isValidStrikethrooRoot = (strikethrooPath) => {
42
42
  try {
43
43
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -41,7 +41,7 @@ var path3 = __toESM(require("path"));
41
41
  // src/skill-scripts/shared/root.ts
42
42
  var fs = __toESM(require("fs"));
43
43
  var path = __toESM(require("path"));
44
- var EXPECTED_SCHEMA = true ? 2 : 2;
44
+ var EXPECTED_SCHEMA = true ? 3 : 3;
45
45
  var isValidStrikethrooRoot = (strikethrooPath) => {
46
46
  try {
47
47
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -39,7 +39,7 @@ var path4 = __toESM(require("path"));
39
39
  // src/skill-scripts/shared/root.ts
40
40
  var fs = __toESM(require("fs"));
41
41
  var path = __toESM(require("path"));
42
- var EXPECTED_SCHEMA = true ? 2 : 2;
42
+ var EXPECTED_SCHEMA = true ? 3 : 3;
43
43
  var isValidStrikethrooRoot = (strikethrooPath) => {
44
44
  try {
45
45
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -242,6 +242,12 @@ var resolvePlan = (input, startPath = process.cwd()) => {
242
242
  return resolveByIdInAncestry(planId, startPath);
243
243
  };
244
244
 
245
+ // src/skill-scripts/shared/task-file.ts
246
+ var extractFrontmatter = (content) => {
247
+ const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
248
+ return match && match[1] ? match[1] : null;
249
+ };
250
+
245
251
  // src/skill-scripts/validate-plan-blueprint.ts
246
252
  var VALID_FIELDS = [
247
253
  "planFile",
@@ -249,8 +255,47 @@ var VALID_FIELDS = [
249
255
  "taskCount",
250
256
  "blueprintExists",
251
257
  "strikethrooRoot",
252
- "planId"
258
+ "planId",
259
+ "complexityScoresValid",
260
+ "invalidComplexityTasks"
253
261
  ];
262
+ var validateComplexityScores = (planDir) => {
263
+ const tasksDir = path4.join(planDir, "tasks");
264
+ if (!fs4.existsSync(tasksDir)) return [];
265
+ let files;
266
+ try {
267
+ if (!fs4.lstatSync(tasksDir).isDirectory()) return [];
268
+ files = fs4.readdirSync(tasksDir).filter((f) => f.endsWith(".md")).sort();
269
+ } catch (_err) {
270
+ return [];
271
+ }
272
+ const invalid = [];
273
+ for (const file of files) {
274
+ let content;
275
+ try {
276
+ content = fs4.readFileSync(path4.join(tasksDir, file), "utf8");
277
+ } catch (_err) {
278
+ invalid.push(`${file}: unreadable`);
279
+ continue;
280
+ }
281
+ const frontmatter = extractFrontmatter(content);
282
+ const match = frontmatter ? frontmatter.match(/^\s*complexity_score\s*:\s*(.*?)\s*(?:#.*)?$/im) : null;
283
+ if (!match || match[1] === void 0) {
284
+ invalid.push(`${file}: missing complexity_score`);
285
+ continue;
286
+ }
287
+ const raw = match[1].trim();
288
+ if (!/^\d+$/.test(raw)) {
289
+ invalid.push(`${file}: non-integer complexity_score "${raw}"`);
290
+ continue;
291
+ }
292
+ const parsed = Number.parseInt(raw, 10);
293
+ if (parsed < 1 || parsed > 10) {
294
+ invalid.push(`${file}: complexity_score ${parsed} out of range 1-10`);
295
+ }
296
+ }
297
+ return invalid;
298
+ };
254
299
  var countTasks = (planDir) => {
255
300
  const tasksDir = path4.join(planDir, "tasks");
256
301
  if (!fs4.existsSync(tasksDir)) return 0;
@@ -324,13 +369,16 @@ var main = () => {
324
369
  }
325
370
  process.exit(1);
326
371
  }
372
+ const invalidComplexity = validateComplexityScores(resolved.planDir);
327
373
  const result = {
328
374
  planFile: resolved.planFile,
329
375
  planDir: resolved.planDir,
330
376
  strikethrooRoot: resolved.strikethrooRoot,
331
377
  planId: resolved.planId,
332
378
  taskCount: countTasks(resolved.planDir),
333
- blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no"
379
+ blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no",
380
+ complexityScoresValid: invalidComplexity.length === 0 ? "yes" : "no",
381
+ invalidComplexityTasks: invalidComplexity.join("; ")
334
382
  };
335
383
  if (fieldName) {
336
384
  if (!VALID_FIELDS.includes(fieldName)) {
@@ -160,7 +160,42 @@ A task B depends on A if B requires A's output or artifacts, modifies code
160
160
  created by A, or tests functionality implemented by A. Validate that the
161
161
  final dependency graph is acyclic.
162
162
 
163
- ### 8. Allocate task IDs
163
+ ### 8. Complexity analysis
164
+
165
+ For every candidate task, assign a `complexity_score` (integer 1–10) before
166
+ writing any file. Base the score on these four dimensions:
167
+
168
+ | Score | Skill breadth | Acceptance-criteria clarity | Integration surface | Decomposition depth |
169
+ | --- | --- | --- | --- | --- |
170
+ | 1–3 | One well-known skill | Criteria are concrete and observable | None or a single file/module | No further split possible |
171
+ | 4–5 | One primary skill plus a familiar adjacent skill | Criteria are clear with few edge cases | One component or API boundary | Already atomic |
172
+ | 6–7 | Two distinct skills, or one skill with ambiguous requirements | Criteria need clarification or have multiple edge cases | Multiple components or contracts | Could still be split |
173
+ | 8–10 | Three or more skills, or cross-cutting design decisions | Criteria are vague, unknown, or depend on unresolved choices | Wide integration surface or external systems | Must be decomposed further |
174
+
175
+ **Pre-emission sanity rules** — apply these before any task is written:
176
+
177
+ - 3+ skills assigned → split the task into smaller tasks, each with 1–2 skills.
178
+ - Vague acceptance criteria → sharpen them until they include at least one
179
+ concrete, runnable verification step.
180
+ - Trivially small adjacent tasks → merge them into a single task.
181
+ - Score ≥ 8 → decompose further; do not emit as-is.
182
+ - Score 6–7 → either sharpen criteria or split; do not emit without an
183
+ explicit reason.
184
+
185
+ **Required frontmatter:**
186
+
187
+ - Every emitted task MUST include `complexity_score` (integer 1–10).
188
+ - Optionally include `complexity_notes` when the score needs justification,
189
+ such as "Ambiguous API contract" or "Decomposed from a higher-score parent".
190
+
191
+ **Loop-back rule:**
192
+
193
+ After applying split, sharpen, or merge, re-run dependency analysis and
194
+ re-score the adjusted tasks. Repeat this loop no more than three times. If
195
+ complexity is still unresolved after three passes, stop and surface the
196
+ blocker to the user.
197
+
198
+ ### 9. Allocate task IDs
164
199
 
165
200
  Run `scripts/get-next-task-id.cjs <plan-id>` to obtain the first available
166
201
  task ID. Allocate subsequent IDs by incrementing in-process; do not invoke
@@ -170,7 +205,7 @@ field and the zero-padded form (`{padded-id}--{slug}`) for the filename.
170
205
  The slug derives from a short task title: lowercase, alphanumeric and
171
206
  hyphens only, collapsed, trimmed.
172
207
 
173
- ### 9. Emit the task files
208
+ ### 10. Emit the task files
174
209
 
175
210
  Write each task to:
176
211
 
@@ -188,11 +223,15 @@ including required frontmatter fields:
188
223
  - `created` (YYYY-MM-DD)
189
224
  - `skills` (array of 1–2 kebab-case skills)
190
225
 
191
- Optional frontmatter for high-complexity or decomposed tasks:
226
+ Required additional frontmatter:
227
+
228
+ - `complexity_score` (integer 1–10, required on every emitted task)
229
+
230
+ Optional frontmatter:
192
231
 
193
- - `complexity_score` (number, 1–10, include only if >4 or for decomposed
194
- tasks)
195
- - `complexity_notes` (string)
232
+ - `complexity_notes` (string) — include when the score needs justification,
233
+ such as "Decomposed from a cross-cutting parent task" or "Ambiguous API
234
+ contract".
196
235
 
197
236
  The body sections (Objective, Skills Required, Acceptance Criteria, Technical
198
237
  Requirements, Input Dependencies, Output Artifacts, Implementation Notes)
@@ -200,7 +239,7 @@ must be filled with task-specific content. Place detailed implementation
200
239
  guidance inside a `<details>` block under "Implementation Notes" — write it
201
240
  so a non-thinking LLM could execute the task from that section alone.
202
241
 
203
- ### 10. Validation checklist
242
+ ### 11. Validation checklist
204
243
 
205
244
  Before declaring task generation complete, verify:
206
245
 
@@ -218,21 +257,27 @@ Before declaring task generation complete, verify:
218
257
  - Minimization applied (20–30% reduction target).
219
258
  - Test tasks focus on business logic, not framework functionality.
220
259
  - No gold-plating: only plan requirements are addressed.
260
+ - After writing the task files, run
261
+ `scripts/validate-plan-blueprint.cjs <plan-id> complexityScoresValid`. Stop
262
+ unless it prints `yes`; if it prints `no`, run
263
+ `scripts/validate-plan-blueprint.cjs <plan-id> invalidComplexityTasks` to see
264
+ which files are missing, non-integer, or out-of-range, fix them, and re-run.
265
+ Every generated task must carry an integer `complexity_score` from 1 to 10.
266
+ - Add `complexity_notes` only when a score needs explanation (typically atomic
267
+ tasks scoring greater than 4).
221
268
 
222
- ### 11. Run the POST_TASK_GENERATION_ALL hook
269
+ ### 12. Run the POST_TASK_GENERATION_ALL hook
223
270
 
224
271
  Read `<root>/config/hooks/POST_TASK_GENERATION_ALL.md` and follow its
225
272
  instructions. This typically requires:
226
273
 
227
- - Sanity-checking complexity (3+ technologies/skills → split; vague criteria
228
- → sharpen; trivially small → merge).
229
274
  - Appending an Execution Blueprint section to the plan document, including a
230
275
  Mermaid dependency diagram and explicit phase groupings (Phase 1 contains
231
276
  zero-dependency tasks; each subsequent phase contains tasks whose
232
277
  dependencies all live in earlier phases). Use
233
278
  `<root>/config/templates/BLUEPRINT_TEMPLATE.md` for structure.
234
279
 
235
- ### 12. Emit the structured summary
280
+ ### 13. Emit the structured summary
236
281
 
237
282
  Conclude with exactly this block as the final output:
238
283
 
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(find_strikethroo_root_exports);
37
37
  // src/skill-scripts/shared/root.ts
38
38
  var fs = __toESM(require("fs"));
39
39
  var path = __toESM(require("path"));
40
- var EXPECTED_SCHEMA = true ? 2 : 2;
40
+ var EXPECTED_SCHEMA = true ? 3 : 3;
41
41
  var isValidStrikethrooRoot = (strikethrooPath) => {
42
42
  try {
43
43
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -41,7 +41,7 @@ var path3 = __toESM(require("path"));
41
41
  // src/skill-scripts/shared/root.ts
42
42
  var fs = __toESM(require("fs"));
43
43
  var path = __toESM(require("path"));
44
- var EXPECTED_SCHEMA = true ? 2 : 2;
44
+ var EXPECTED_SCHEMA = true ? 3 : 3;
45
45
  var isValidStrikethrooRoot = (strikethrooPath) => {
46
46
  try {
47
47
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -39,7 +39,7 @@ var path4 = __toESM(require("path"));
39
39
  // src/skill-scripts/shared/root.ts
40
40
  var fs = __toESM(require("fs"));
41
41
  var path = __toESM(require("path"));
42
- var EXPECTED_SCHEMA = true ? 2 : 2;
42
+ var EXPECTED_SCHEMA = true ? 3 : 3;
43
43
  var isValidStrikethrooRoot = (strikethrooPath) => {
44
44
  try {
45
45
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -242,6 +242,12 @@ var resolvePlan = (input, startPath = process.cwd()) => {
242
242
  return resolveByIdInAncestry(planId, startPath);
243
243
  };
244
244
 
245
+ // src/skill-scripts/shared/task-file.ts
246
+ var extractFrontmatter = (content) => {
247
+ const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
248
+ return match && match[1] ? match[1] : null;
249
+ };
250
+
245
251
  // src/skill-scripts/validate-plan-blueprint.ts
246
252
  var VALID_FIELDS = [
247
253
  "planFile",
@@ -249,8 +255,47 @@ var VALID_FIELDS = [
249
255
  "taskCount",
250
256
  "blueprintExists",
251
257
  "strikethrooRoot",
252
- "planId"
258
+ "planId",
259
+ "complexityScoresValid",
260
+ "invalidComplexityTasks"
253
261
  ];
262
+ var validateComplexityScores = (planDir) => {
263
+ const tasksDir = path4.join(planDir, "tasks");
264
+ if (!fs4.existsSync(tasksDir)) return [];
265
+ let files;
266
+ try {
267
+ if (!fs4.lstatSync(tasksDir).isDirectory()) return [];
268
+ files = fs4.readdirSync(tasksDir).filter((f) => f.endsWith(".md")).sort();
269
+ } catch (_err) {
270
+ return [];
271
+ }
272
+ const invalid = [];
273
+ for (const file of files) {
274
+ let content;
275
+ try {
276
+ content = fs4.readFileSync(path4.join(tasksDir, file), "utf8");
277
+ } catch (_err) {
278
+ invalid.push(`${file}: unreadable`);
279
+ continue;
280
+ }
281
+ const frontmatter = extractFrontmatter(content);
282
+ const match = frontmatter ? frontmatter.match(/^\s*complexity_score\s*:\s*(.*?)\s*(?:#.*)?$/im) : null;
283
+ if (!match || match[1] === void 0) {
284
+ invalid.push(`${file}: missing complexity_score`);
285
+ continue;
286
+ }
287
+ const raw = match[1].trim();
288
+ if (!/^\d+$/.test(raw)) {
289
+ invalid.push(`${file}: non-integer complexity_score "${raw}"`);
290
+ continue;
291
+ }
292
+ const parsed = Number.parseInt(raw, 10);
293
+ if (parsed < 1 || parsed > 10) {
294
+ invalid.push(`${file}: complexity_score ${parsed} out of range 1-10`);
295
+ }
296
+ }
297
+ return invalid;
298
+ };
254
299
  var countTasks = (planDir) => {
255
300
  const tasksDir = path4.join(planDir, "tasks");
256
301
  if (!fs4.existsSync(tasksDir)) return 0;
@@ -324,13 +369,16 @@ var main = () => {
324
369
  }
325
370
  process.exit(1);
326
371
  }
372
+ const invalidComplexity = validateComplexityScores(resolved.planDir);
327
373
  const result = {
328
374
  planFile: resolved.planFile,
329
375
  planDir: resolved.planDir,
330
376
  strikethrooRoot: resolved.strikethrooRoot,
331
377
  planId: resolved.planId,
332
378
  taskCount: countTasks(resolved.planDir),
333
- blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no"
379
+ blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no",
380
+ complexityScoresValid: invalidComplexity.length === 0 ? "yes" : "no",
381
+ invalidComplexityTasks: invalidComplexity.join("; ")
334
382
  };
335
383
  if (fieldName) {
336
384
  if (!VALID_FIELDS.includes(fieldName)) {
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(find_strikethroo_root_exports);
37
37
  // src/skill-scripts/shared/root.ts
38
38
  var fs = __toESM(require("fs"));
39
39
  var path = __toESM(require("path"));
40
- var EXPECTED_SCHEMA = true ? 2 : 2;
40
+ var EXPECTED_SCHEMA = true ? 3 : 3;
41
41
  var isValidStrikethrooRoot = (strikethrooPath) => {
42
42
  try {
43
43
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -39,7 +39,7 @@ var path4 = __toESM(require("path"));
39
39
  // src/skill-scripts/shared/root.ts
40
40
  var fs = __toESM(require("fs"));
41
41
  var path = __toESM(require("path"));
42
- var EXPECTED_SCHEMA = true ? 2 : 2;
42
+ var EXPECTED_SCHEMA = true ? 3 : 3;
43
43
  var isValidStrikethrooRoot = (strikethrooPath) => {
44
44
  try {
45
45
  if (!fs.existsSync(strikethrooPath)) return false;
@@ -242,6 +242,12 @@ var resolvePlan = (input, startPath = process.cwd()) => {
242
242
  return resolveByIdInAncestry(planId, startPath);
243
243
  };
244
244
 
245
+ // src/skill-scripts/shared/task-file.ts
246
+ var extractFrontmatter = (content) => {
247
+ const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
248
+ return match && match[1] ? match[1] : null;
249
+ };
250
+
245
251
  // src/skill-scripts/validate-plan-blueprint.ts
246
252
  var VALID_FIELDS = [
247
253
  "planFile",
@@ -249,8 +255,47 @@ var VALID_FIELDS = [
249
255
  "taskCount",
250
256
  "blueprintExists",
251
257
  "strikethrooRoot",
252
- "planId"
258
+ "planId",
259
+ "complexityScoresValid",
260
+ "invalidComplexityTasks"
253
261
  ];
262
+ var validateComplexityScores = (planDir) => {
263
+ const tasksDir = path4.join(planDir, "tasks");
264
+ if (!fs4.existsSync(tasksDir)) return [];
265
+ let files;
266
+ try {
267
+ if (!fs4.lstatSync(tasksDir).isDirectory()) return [];
268
+ files = fs4.readdirSync(tasksDir).filter((f) => f.endsWith(".md")).sort();
269
+ } catch (_err) {
270
+ return [];
271
+ }
272
+ const invalid = [];
273
+ for (const file of files) {
274
+ let content;
275
+ try {
276
+ content = fs4.readFileSync(path4.join(tasksDir, file), "utf8");
277
+ } catch (_err) {
278
+ invalid.push(`${file}: unreadable`);
279
+ continue;
280
+ }
281
+ const frontmatter = extractFrontmatter(content);
282
+ const match = frontmatter ? frontmatter.match(/^\s*complexity_score\s*:\s*(.*?)\s*(?:#.*)?$/im) : null;
283
+ if (!match || match[1] === void 0) {
284
+ invalid.push(`${file}: missing complexity_score`);
285
+ continue;
286
+ }
287
+ const raw = match[1].trim();
288
+ if (!/^\d+$/.test(raw)) {
289
+ invalid.push(`${file}: non-integer complexity_score "${raw}"`);
290
+ continue;
291
+ }
292
+ const parsed = Number.parseInt(raw, 10);
293
+ if (parsed < 1 || parsed > 10) {
294
+ invalid.push(`${file}: complexity_score ${parsed} out of range 1-10`);
295
+ }
296
+ }
297
+ return invalid;
298
+ };
254
299
  var countTasks = (planDir) => {
255
300
  const tasksDir = path4.join(planDir, "tasks");
256
301
  if (!fs4.existsSync(tasksDir)) return 0;
@@ -324,13 +369,16 @@ var main = () => {
324
369
  }
325
370
  process.exit(1);
326
371
  }
372
+ const invalidComplexity = validateComplexityScores(resolved.planDir);
327
373
  const result = {
328
374
  planFile: resolved.planFile,
329
375
  planDir: resolved.planDir,
330
376
  strikethrooRoot: resolved.strikethrooRoot,
331
377
  planId: resolved.planId,
332
378
  taskCount: countTasks(resolved.planDir),
333
- blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no"
379
+ blueprintExists: checkBlueprintExists(resolved.planFile) ? "yes" : "no",
380
+ complexityScoresValid: invalidComplexity.length === 0 ? "yes" : "no",
381
+ invalidComplexityTasks: invalidComplexity.join("; ")
334
382
  };
335
383
  if (fieldName) {
336
384
  if (!VALID_FIELDS.includes(fieldName)) {
@@ -4,4 +4,4 @@ This directory contains AI-assisted task management files for this project.
4
4
 
5
5
  Managed by the [Strikethroo](https://www.github.com/e0ipso/strikethroo) project.
6
6
 
7
- **Documentation**: https://mateuaguilo.com/strikethroo
7
+ **Documentation**: https://strikethroo.canpicasoft.com