@namewta/speculo 0.8.10 → 0.8.13

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 (56) hide show
  1. package/README.md +2 -1
  2. package/package.json +1 -1
  3. package/template/canonical/canonical-specdev-goal-plan.md +27 -11
  4. package/template/canonical/canonical-specdev-grill-with-docs.md +1 -1
  5. package/template/canonical/canonical-specdev-orchestrate-implementation.md +56 -18
  6. package/template/canonical/canonical-specdev-spec.md +1 -1
  7. package/template/canonical/canonical-specdev-tickets.md +43 -6
  8. package/template/skills/engineering-standards-builder/README.md +6 -28
  9. package/template/skills/engineering-standards-builder/SKILL.md +88 -163
  10. package/template/skills/engineering-standards-builder/examples/README.md +2 -0
  11. package/template/skills/engineering-standards-builder/manifest.txt +4 -0
  12. package/template/skills/engineering-standards-builder/references/rules/00-governance-and-precedence.md +22 -29
  13. package/template/skills/engineering-standards-builder/references/rules/01-project-discovery.md +26 -59
  14. package/template/skills/engineering-standards-builder/references/rules/02-evidence-topology-and-scope.md +28 -55
  15. package/template/skills/engineering-standards-builder/references/rules/03-interview-and-decisions.md +4 -3
  16. package/template/skills/engineering-standards-builder/references/rules/14-generation-contract.md +64 -80
  17. package/template/skills/engineering-standards-builder/references/rules/15-validation-contract.md +24 -47
  18. package/template/skills/engineering-standards-builder/references/rules/16-language-adapter-contract.md +11 -48
  19. package/template/skills/engineering-standards-builder/references/rules/README.md +3 -3
  20. package/template/skills/engineering-standards-builder/scripts/self-test.mjs +51 -5
  21. package/template/skills/engineering-standards-builder/scripts/validate-builder.mjs +16 -4
  22. package/template/skills/engineering-standards-builder/scripts/validate-generated-skill.mjs +166 -64
  23. package/template/skills/engineering-standards-builder/templates/README.md +12 -3
  24. package/template/skills/engineering-standards-builder/templates/domain-skill/SKILL.md.template +32 -0
  25. package/template/skills/engineering-standards-builder/templates/project-skill/SKILL.md.template +16 -11
  26. package/template/skills/engineering-standards-builder/templates/project-skill/generated-skill-set.json.template +7 -0
  27. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/00-project-profile.md.template +3 -1
  28. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/01-module-map.md.template +4 -0
  29. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/02-decisions-and-exceptions.md.template +1 -1
  30. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/03-skill-map.md.template +19 -0
  31. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/04-source-and-template-map.md.template +22 -0
  32. package/template/skills/engineering-standards-builder/templates/project-skill/references/project/review-checklist.md.template +2 -0
  33. package/template/skills/git-history-squash/SKILL.md +100 -0
  34. package/template/skills/git-history-squash/assets/request-template.json +18 -0
  35. package/template/skills/git-history-squash/references/recovery-contract.md +50 -0
  36. package/template/skills/git-history-squash/references/rewrite-contract.md +123 -0
  37. package/template/skills/git-history-squash/references/submodule-contract.md +54 -0
  38. package/template/skills/git-history-squash/scripts/git-history-squash.mjs +1171 -0
  39. package/template/workflows/specdev/I-implement/I-implement.md +18 -7
  40. package/template/workflows/specdev/I-implement/evidence-template.md +13 -0
  41. package/template/workflows/specdev/I-implement/execution-preflight.md +4 -0
  42. package/template/workflows/specdev/O-orchestrate-implementation/execution-loop.md +3 -2
  43. package/template/workflows/specdev/P-goal-plan/completion-control.md +3 -0
  44. package/template/workflows/specdev/P-goal-plan/lead-orchestration.md +6 -2
  45. package/template/workflows/specdev/README.md +1 -1
  46. package/template/workflows/specdev/T-tickets/T-tickets.md +16 -3
  47. package/template/workflows/specdev/T-tickets/ticket-readiness.md +3 -0
  48. package/template/workflows/specdev/T-tickets/ticket-template.md +3 -0
  49. package/template/workflows/specdev/T-tickets/tickets-map-template.md +15 -0
  50. package/template/workflows/specdev/common/rules/artifact-contract.md +1 -1
  51. package/template/workflows/specdev/common/skills/dev-worktree/references/finalize.md +5 -2
  52. package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +4 -2
  53. package/template/workflows/specdev/common/skills/subagent-delivery/references/external-web-subagent.md +2 -1
  54. package/template/workflows/specdev/common/skills/subagent-delivery/references/native-subagent.md +2 -1
  55. package/template/workflows/specdev/common/skills/subagent-delivery/references/source-package.md +4 -2
  56. package/template/workflows/specdev/common/tools/validate-specdev.mjs +143 -3
@@ -324,6 +324,12 @@ function stripPathTag(value) {
324
324
  return match ? match[1].trim() : null;
325
325
  }
326
326
 
327
+ function stripInlineCode(value) {
328
+ const trimmed = String(value).trim();
329
+ const match = /^`([^`]+)`$/.exec(trimmed);
330
+ return match ? match[1].trim() : trimmed;
331
+ }
332
+
327
333
  function normalizeProjectPath(value) {
328
334
  const inner = stripPathTag(value) ?? String(value);
329
335
  return inner
@@ -1466,7 +1472,120 @@ function validateSpec(path, errors, warnings) {
1466
1472
  return { path, meta, body };
1467
1473
  }
1468
1474
 
1469
- function validateMap(path, errors) {
1475
+ function validateProjectSkillMatrix(body, label, errors, repoRoot = null) {
1476
+ const section = sectionBody(body, "### 项目 Skill 读取矩阵");
1477
+ if (!section) return [];
1478
+
1479
+ const lines = section.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1480
+ const headerIndex = lines.findIndex((line) =>
1481
+ line.startsWith("|") &&
1482
+ line.includes("Applies To") &&
1483
+ line.includes("Project Skill") &&
1484
+ line.includes("Trigger / Scope") &&
1485
+ line.includes("Read Timing") &&
1486
+ line.includes("Purpose"),
1487
+ );
1488
+ if (headerIndex < 0) {
1489
+ errors.push(`${label}: project Skill matrix is missing the required five-column header`);
1490
+ return [];
1491
+ }
1492
+
1493
+ const entries = [];
1494
+ const seenSkillPaths = new Set();
1495
+ for (const line of lines.slice(headerIndex + 1)) {
1496
+ if (!line.startsWith("|")) continue;
1497
+ if (/^\|(?:\s*:?-{3,}:?\s*\|)+$/.test(line)) continue;
1498
+ const cells = line.replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
1499
+ if (cells.length !== 5) {
1500
+ errors.push(`${label}: project Skill matrix row must contain five columns: ${line}`);
1501
+ continue;
1502
+ }
1503
+
1504
+ const appliesTo = stripInlineCode(cells[0]).split(/[\s,]+/).filter(Boolean);
1505
+ if (!appliesTo.length || appliesTo.some((value) => value !== "ALL" && !/^T-\d{2,}$/.test(value))) {
1506
+ errors.push(`${label}: invalid project Skill Applies To value '${cells[0]}'`);
1507
+ continue;
1508
+ }
1509
+ if (new Set(appliesTo).size !== appliesTo.length) {
1510
+ errors.push(`${label}: duplicate project Skill Applies To value '${cells[0]}'`);
1511
+ }
1512
+ for (const [column, value] of [
1513
+ ["Trigger / Scope", cells[2]],
1514
+ ["Read Timing", cells[3]],
1515
+ ["Purpose", cells[4]],
1516
+ ]) {
1517
+ if (!value || value === "...") errors.push(`${label}: project Skill ${column} must be explicit`);
1518
+ }
1519
+
1520
+ const skillValue = stripInlineCode(cells[1]);
1521
+ if (skillValue.startsWith("无")) {
1522
+ if (!`${skillValue} ${cells[2]}`.includes("已扫描")) {
1523
+ errors.push(`${label}: no-project-Skill row must record the scanned scope`);
1524
+ }
1525
+ entries.push({ appliesTo: new Set(appliesTo), path: null });
1526
+ continue;
1527
+ }
1528
+
1529
+ const skillPath = stripPathTag(skillValue);
1530
+ if (!skillPath) {
1531
+ errors.push(`${label}: project Skill must be a Path-tagged SKILL.md or an explicit no-Skill row`);
1532
+ continue;
1533
+ }
1534
+ const pathIssue = validatePathValue(skillPath);
1535
+ if (pathIssue) errors.push(`${label}: invalid project Skill Path ${skillPath}: ${pathIssue}`);
1536
+ if (skillPath.includes("{roots.")) {
1537
+ errors.push(`${label}: project Skill Path must be project-relative, not a Speculo root Path: ${skillPath}`);
1538
+ }
1539
+ if (!skillPath.endsWith("/SKILL.md")) {
1540
+ errors.push(`${label}: project Skill Path must end with /SKILL.md: ${skillPath}`);
1541
+ }
1542
+ if (PLACEHOLDER_RE.test(skillPath)) {
1543
+ errors.push(`${label}: project Skill Path contains an unresolved placeholder: ${skillPath}`);
1544
+ }
1545
+ if (seenSkillPaths.has(skillPath)) {
1546
+ errors.push(`${label}: duplicate project Skill Path ${skillPath}; combine its Applies To values`);
1547
+ }
1548
+ seenSkillPaths.add(skillPath);
1549
+
1550
+ if (repoRoot && !pathIssue && !skillPath.includes("{roots.") && !PLACEHOLDER_RE.test(skillPath)) {
1551
+ const resolvedRepo = resolve(repoRoot);
1552
+ const resolvedSkill = resolve(resolvedRepo, normalizeProjectPath(skillPath));
1553
+ const relativeSkill = relative(resolvedRepo, resolvedSkill);
1554
+ if (relativeSkill === "" || relativeSkill.split(sep)[0] === "..") {
1555
+ errors.push(`${label}: project Skill Path escapes the project root: ${skillPath}`);
1556
+ } else if (!isFile(resolvedSkill)) {
1557
+ errors.push(`${label}: project Skill does not exist under --repo: ${skillPath}`);
1558
+ }
1559
+ }
1560
+ entries.push({ appliesTo: new Set(appliesTo), path: skillPath });
1561
+ }
1562
+
1563
+ if (!entries.length) errors.push(`${label}: project Skill matrix contains no data rows`);
1564
+ const allNoSkill = entries.some((entry) => entry.path === null && entry.appliesTo.has("ALL"));
1565
+ if (allNoSkill && entries.some((entry) => entry.path !== null)) {
1566
+ errors.push(`${label}: an ALL no-project-Skill row conflicts with listed project Skills`);
1567
+ }
1568
+ return entries;
1569
+ }
1570
+
1571
+ function validateProjectSkillCoverage(matrix, ticketIds, label, errors) {
1572
+ if (!matrix.length) return;
1573
+ const knownTicketIds = new Set(ticketIds);
1574
+ for (const entry of matrix) {
1575
+ for (const appliesTo of entry.appliesTo) {
1576
+ if (appliesTo !== "ALL" && !knownTicketIds.has(appliesTo)) {
1577
+ errors.push(`${label}: project Skill matrix references missing Ticket ${appliesTo}`);
1578
+ }
1579
+ }
1580
+ }
1581
+ for (const ticketId of knownTicketIds) {
1582
+ if (!matrix.some((entry) => entry.appliesTo.has("ALL") || entry.appliesTo.has(ticketId))) {
1583
+ errors.push(`${label}: project Skill matrix does not cover ${ticketId}`);
1584
+ }
1585
+ }
1586
+ }
1587
+
1588
+ function validateMap(path, errors, repoRoot = null) {
1470
1589
  if (!isFile(path)) {
1471
1590
  errors.push("missing Tickets Map");
1472
1591
  return null;
@@ -1476,6 +1595,8 @@ function validateMap(path, errors) {
1476
1595
  errors.push(`${basename(path)}: artifact/schema_version must be tickets-map/${DOMAIN_SCHEMA_VERSION}`);
1477
1596
  }
1478
1597
  for (const heading of [
1598
+ "### 总体实施背景",
1599
+ "### 项目 Skill 读取矩阵",
1479
1600
  "## 2. 执行清单",
1480
1601
  "## 3. 依赖 DAG",
1481
1602
  "## 4. 合同覆盖矩阵",
@@ -1483,7 +1604,8 @@ function validateMap(path, errors) {
1483
1604
  ]) {
1484
1605
  if (!body.includes(heading)) errors.push(`${basename(path)}: missing '${heading}'`);
1485
1606
  }
1486
- return { path, meta, body };
1607
+ const projectSkillMatrix = validateProjectSkillMatrix(body, basename(path), errors, repoRoot);
1608
+ return { path, meta, body, projectSkillMatrix };
1487
1609
  }
1488
1610
 
1489
1611
  function validateGoalPlan(path, errors) {
@@ -2478,6 +2600,15 @@ function validateParentImplementation(change, parentStatus, stage, errors, warni
2478
2600
  }
2479
2601
  }
2480
2602
 
2603
+ if (childMap) {
2604
+ validateProjectSkillCoverage(
2605
+ childMap.projectSkillMatrix,
2606
+ childTickets.keys(),
2607
+ basename(childMap.path),
2608
+ memberErrors,
2609
+ );
2610
+ }
2611
+
2481
2612
  for (const [ticketId, artifact] of childTickets) {
2482
2613
  for (const dependency of (artifact.meta.blocked_by ?? []).map(String)) {
2483
2614
  if (!childTickets.has(dependency)) memberErrors.push(`${ticketId}: blocked_by references missing ${dependency}`);
@@ -2739,7 +2870,7 @@ function validateChange(change, stage = null, repoRoot = null) {
2739
2870
  const ticketMode = isDirectory(join(change, "ticket"));
2740
2871
  const mapRequired = new Set(["tickets", "goal-plan"]).has(stage) || (stage === "implement" && ticketMode);
2741
2872
  const mapPath = join(change, "tickets-map.md");
2742
- const ticketsMap = isFile(mapPath) || mapRequired ? validateMap(mapPath, errors) : null;
2873
+ const ticketsMap = isFile(mapPath) || mapRequired ? validateMap(mapPath, errors, repoRoot) : null;
2743
2874
  const goalPlanPath = join(change, "goal-plan.md");
2744
2875
  const goalPlan = isFile(goalPlanPath) || stage === "goal-plan"
2745
2876
  ? validateGoalPlan(goalPlanPath, errors)
@@ -2800,6 +2931,15 @@ function validateChange(change, stage = null, repoRoot = null) {
2800
2931
  }
2801
2932
  }
2802
2933
 
2934
+ if (ticketsMap) {
2935
+ validateProjectSkillCoverage(
2936
+ ticketsMap.projectSkillMatrix,
2937
+ tickets.keys(),
2938
+ basename(ticketsMap.path),
2939
+ errors,
2940
+ );
2941
+ }
2942
+
2803
2943
  if (stage === "implement" && ticketMode && changeStatus) {
2804
2944
  const worktreesByTicket = new Map(
2805
2945
  (Array.isArray(changeStatus.worktrees) ? changeStatus.worktrees : [])