@namewta/speculo 0.7.0 → 0.7.2

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 (52) hide show
  1. package/README.md +9 -6
  2. package/dist/src/cli.js +12 -0
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/index.d.ts +2 -0
  5. package/dist/src/index.js +56 -58
  6. package/dist/src/index.js.map +1 -1
  7. package/dist/src/migrations.d.ts +23 -0
  8. package/dist/src/migrations.js +470 -0
  9. package/dist/src/migrations.js.map +1 -0
  10. package/package.json +2 -2
  11. package/template/.speculo/README.md +13 -6
  12. package/template/canonical/canonical-specdev-engineering-cognitive-mentor.md +178 -0
  13. package/template/canonical/canonical-specdev-goal-plan.md +386 -74
  14. package/template/canonical/canonical-specdev-grill-with-docs.md +178 -0
  15. package/template/canonical/canonical-specdev-spec.md +178 -0
  16. package/template/canonical/canonical-specdev-tickets.md +247 -21
  17. package/template/canonical/canonical-specdev-wayfinder.md +178 -0
  18. package/template/commands/migrate-runtime-state.md +43 -0
  19. package/template/skills/github-npm-ops/references/preflight-checklist.md +1 -1
  20. package/template/skills/migrate-runtime-state/SKILL.md +93 -0
  21. package/template/skills/migrate-runtime-state/references/migration-contract.md +58 -0
  22. package/template/skills/migrate-runtime-state/scripts/migrate-runtime-state.mjs +545 -0
  23. package/template/skills/optimize-codex-config/SKILL.md +81 -0
  24. package/template/skills/optimize-codex-config/references/configuration-contract.md +103 -0
  25. package/template/skills/optimize-codex-config/references/troubleshooting.md +79 -0
  26. package/template/skills/optimize-codex-config/scripts/audit-codex-config.mjs +747 -0
  27. package/template/workflows/specdev/I-implement/I-implement.md +11 -10
  28. package/template/workflows/specdev/I-implement/delegated-evidence-template.md +2 -1
  29. package/template/workflows/specdev/I-implement/execution-preflight.md +5 -3
  30. package/template/workflows/specdev/I-implement/merge-conflict-protocol.md +6 -5
  31. package/template/workflows/specdev/I-init-setup/I-init-setup.md +1 -1
  32. package/template/workflows/specdev/INDEX.md +13 -9
  33. package/template/workflows/specdev/P-goal-plan/P-goal-plan.md +27 -18
  34. package/template/workflows/specdev/P-goal-plan/completion-control.md +3 -3
  35. package/template/workflows/specdev/P-goal-plan/delegated-execution-template.md +6 -4
  36. package/template/workflows/specdev/P-goal-plan/delegated-execution.md +13 -7
  37. package/template/workflows/specdev/P-goal-plan/goal-plan-template.md +11 -2
  38. package/template/workflows/specdev/P-goal-plan/orchestration-protocol.md +7 -3
  39. package/template/workflows/specdev/P-goal-plan/planning-modes.md +23 -8
  40. package/template/workflows/specdev/P-goal-plan/workspace-execution-template.md +24 -0
  41. package/template/workflows/specdev/common/README.md +2 -2
  42. package/template/workflows/specdev/common/rules/change-completion.md +3 -2
  43. package/template/workflows/specdev/common/rules/path-ownership.md +2 -2
  44. package/template/workflows/specdev/common/schemas/change-status.schema.json +178 -0
  45. package/template/workflows/specdev/common/schemas/goal-plan.schema.json +10 -0
  46. package/template/workflows/specdev/common/skills/dev-worktree/SKILL.md +11 -11
  47. package/template/workflows/specdev/common/skills/dev-worktree/references/create.md +19 -4
  48. package/template/workflows/specdev/common/skills/dev-worktree/references/finalize.md +12 -5
  49. package/template/workflows/specdev/common/skills/subagent-delivery/SKILL.md +4 -3
  50. package/template/workflows/specdev/common/skills/subagent-delivery/references/external-web-subagent.md +1 -1
  51. package/template/workflows/specdev/common/skills/subagent-delivery/references/native-subagent.md +2 -3
  52. package/template/workflows/specdev/common/tools/validate-specdev.mjs +218 -5
@@ -61,6 +61,21 @@ const VALID_PLAN_MODES = new Set([
61
61
  "reference-conformance",
62
62
  "release-coordination",
63
63
  ]);
64
+ const VALID_COORDINATION_MODES = new Set(["single-session", "lead-team"]);
65
+ const VALID_WORKSPACE_STRATEGIES = new Set(["current", "worktree", "mixed"]);
66
+ const VALID_WORKTREE_STATUS = new Set([
67
+ "planned",
68
+ "active",
69
+ "review",
70
+ "integrating",
71
+ "integrated",
72
+ "removed",
73
+ "blocked",
74
+ ]);
75
+ const VALID_TERMINAL_ACTION = new Set(["integrate", "retain"]);
76
+ const VALID_INTEGRATION_STATUS = new Set(["pending", "running", "passed", "blocked"]);
77
+ const VALID_INTEGRATION_METHOD = new Set([null, "fast-forward", "merge-commit"]);
78
+ const VALID_INTEGRATION_VERIFICATION = new Set(["pending", "passed", "failed"]);
64
79
  const VALID_DESIGN_TREE_STATUS = new Set(["active", "consensus", "blocked"]);
65
80
  const VALID_DESIGN_NODE_STATUS = new Set(["open", "answered", "deferred", "rejected"]);
66
81
  const VALID_WAYFINDER_LABEL = new Set([
@@ -131,7 +146,7 @@ const DELEGATED_GOAL_PLAN_TRIGGERS = [
131
146
  "## Delegated Execution Addendum",
132
147
  "### Delivery Contract",
133
148
  "### Per-Ticket Dispatch Packets",
134
- "### Candidate Delivery Return and Lead Integration",
149
+ "### Candidate Delivery Return and Lead Acceptance",
135
150
  "Lead / Provider",
136
151
  "Lead:",
137
152
  "Lead:",
@@ -147,11 +162,20 @@ const REQUIRED_DELEGATED_GOAL_PLAN_MARKERS = [
147
162
  "## Delegated Execution Addendum",
148
163
  "### Delivery Contract",
149
164
  "### Per-Ticket Dispatch Packets",
150
- "### Candidate Delivery Return and Lead Integration",
165
+ "### Candidate Delivery Return and Lead Acceptance",
151
166
  "native-subagent / external-web-subagent",
152
167
  "Lead / Provider",
153
168
  "#### Dispatch:",
154
169
  ];
170
+ const REQUIRED_WORKSPACE_GOAL_PLAN_MARKERS = [
171
+ "## Isolated Workspace Addendum",
172
+ "### Workspace Decision",
173
+ "### Per-Ticket Workspace Allocation",
174
+ "### Local Integration Authorization",
175
+ "Integration owner",
176
+ "Parent branch",
177
+ "Terminal action",
178
+ ];
155
179
  const STATE_ARTIFACT_BASENAMES = new Set([
156
180
  "spec.md",
157
181
  "tickets-map.md",
@@ -663,9 +687,10 @@ function capabilityChecks(root) {
663
687
  "DAG",
664
688
  "Gate",
665
689
  "Wave",
666
- "普通 Goal Plan",
667
- "委派 Goal Plan",
668
- "每次向用户询问",
690
+ "coordination_mode",
691
+ "workspace_strategy",
692
+ "single-session",
693
+ "lead-team",
669
694
  "delegated-execution.md",
670
695
  "Definition of Done",
671
696
  ],
@@ -770,6 +795,13 @@ function capabilityChecks(root) {
770
795
  const ordinaryGoalPlanTemplate = join(root, "P-goal-plan", "goal-plan-template.md");
771
796
  if (isFile(ordinaryGoalPlanTemplate)) {
772
797
  const text = readText(ordinaryGoalPlanTemplate);
798
+ for (const required of [
799
+ "coordination_mode: single-session",
800
+ "workspace_strategy: current",
801
+ "### Execution Topology",
802
+ ]) {
803
+ if (!text.includes(required)) errors.push(`ordinary Goal Plan template lost marker ${required}`);
804
+ }
773
805
  for (const forbidden of [
774
806
  "Lead",
775
807
  "Subagent",
@@ -780,6 +812,9 @@ function capabilityChecks(root) {
780
812
  "Worker",
781
813
  "max_correction_rounds",
782
814
  "execution_model",
815
+ "worktree",
816
+ "Ticket branch",
817
+ "merge commit",
783
818
  ]) {
784
819
  if (text.includes(forbidden)) {
785
820
  errors.push(`ordinary Goal Plan template contains delegated marker ${forbidden}`);
@@ -787,6 +822,15 @@ function capabilityChecks(root) {
787
822
  }
788
823
  }
789
824
 
825
+ const workspaceTemplate = join(root, "P-goal-plan", "workspace-execution-template.md");
826
+ if (!isFile(workspaceTemplate)) {
827
+ errors.push("missing isolated workspace Goal Plan template");
828
+ } else {
829
+ const text = readText(workspaceTemplate);
830
+ const missing = REQUIRED_WORKSPACE_GOAL_PLAN_MARKERS.filter((marker) => !text.includes(marker));
831
+ if (missing.length) errors.push(`isolated workspace Goal Plan template lost markers: ${JSON.stringify(missing)}`);
832
+ }
833
+
790
834
  const delegatedTemplate = join(root, "P-goal-plan", "delegated-execution-template.md");
791
835
  if (!isFile(delegatedTemplate)) {
792
836
  errors.push("missing delegated Goal Plan template");
@@ -1163,6 +1207,15 @@ function validateDelegatedGoalPlan(body, label, errors) {
1163
1207
  }
1164
1208
  }
1165
1209
 
1210
+ function validateWorkspaceGoalPlan(body, label, errors, required = false) {
1211
+ const hasWorkspaceAddendum = body.includes("## Isolated Workspace Addendum");
1212
+ if (!required && !hasWorkspaceAddendum) return;
1213
+ const missing = REQUIRED_WORKSPACE_GOAL_PLAN_MARKERS.filter((marker) => !body.includes(marker));
1214
+ if (missing.length) {
1215
+ errors.push(`${label}: incomplete isolated workspace addendum; missing ${JSON.stringify(missing)}`);
1216
+ }
1217
+ }
1218
+
1166
1219
  function validateGoalPlan(path, errors) {
1167
1220
  if (!isFile(path)) return null;
1168
1221
  const { meta, body } = parseFrontmatter(path);
@@ -1174,7 +1227,34 @@ function validateGoalPlan(path, errors) {
1174
1227
  if (invalidModes.length) {
1175
1228
  errors.push(`${basename(path)}: invalid modes ${JSON.stringify(invalidModes)}`);
1176
1229
  }
1230
+ const coordinationMode = meta.coordination_mode;
1231
+ const workspaceStrategy = meta.workspace_strategy;
1232
+ if ((coordinationMode === undefined) !== (workspaceStrategy === undefined)) {
1233
+ errors.push(`${basename(path)}: coordination_mode and workspace_strategy must appear together`);
1234
+ }
1235
+ if (coordinationMode !== undefined && !VALID_COORDINATION_MODES.has(coordinationMode)) {
1236
+ errors.push(`${basename(path)}: invalid coordination_mode ${coordinationMode}`);
1237
+ }
1238
+ if (workspaceStrategy !== undefined && !VALID_WORKSPACE_STRATEGIES.has(workspaceStrategy)) {
1239
+ errors.push(`${basename(path)}: invalid workspace_strategy ${workspaceStrategy}`);
1240
+ }
1241
+
1242
+ const hasDelegatedMarkers = DELEGATED_GOAL_PLAN_TRIGGERS.some((marker) => body.includes(marker));
1243
+ if (coordinationMode === "lead-team" && !hasDelegatedMarkers) {
1244
+ errors.push(`${basename(path)}: lead-team requires a complete delegated execution addendum`);
1245
+ } else if (coordinationMode === "single-session" && hasDelegatedMarkers) {
1246
+ errors.push(`${basename(path)}: single-session cannot contain delegated execution markers`);
1247
+ }
1177
1248
  validateDelegatedGoalPlan(body, basename(path), errors);
1249
+
1250
+ const hasWorkspaceAddendum = body.includes("## Isolated Workspace Addendum");
1251
+ if (new Set(["worktree", "mixed"]).has(workspaceStrategy)) {
1252
+ validateWorkspaceGoalPlan(body, basename(path), errors, true);
1253
+ } else if (workspaceStrategy === "current" && hasWorkspaceAddendum) {
1254
+ errors.push(`${basename(path)}: current workspace_strategy cannot contain an isolated workspace addendum`);
1255
+ } else if (workspaceStrategy === undefined) {
1256
+ validateWorkspaceGoalPlan(body, basename(path), errors);
1257
+ }
1178
1258
  if (meta.ready_for_execution === true) {
1179
1259
  if (!new Set(["ready", "in_progress", "completed"]).has(meta.status)) {
1180
1260
  errors.push(`${basename(path)}: ready_for_execution conflicts with status`);
@@ -1509,12 +1589,145 @@ function validateChangeStatus(path, expectedChange, errors) {
1509
1589
  errors.push(`${basename(path)}: workspace_ref must be a portable non-absolute locator`);
1510
1590
  continue;
1511
1591
  }
1592
+ if (!VALID_WORKTREE_STATUS.has(worktree.status)) {
1593
+ errors.push(`${basename(path)}: invalid worktree status ${worktree.status}`);
1594
+ }
1512
1595
  if (worktree.provider === "git") {
1513
1596
  const expectedRef = `specdev-worktree/${worktree.ticket_id}`;
1514
1597
  if (ref !== expectedRef) {
1515
1598
  errors.push(`${basename(path)}: git workspace_ref must equal ${expectedRef}`);
1516
1599
  }
1517
1600
  }
1601
+
1602
+ const hasIntegrationContract = worktree.terminal_action !== undefined;
1603
+ if (!hasIntegrationContract) {
1604
+ if (worktree.status === "integrating") {
1605
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integrating requires the complete integration contract`);
1606
+ }
1607
+ continue;
1608
+ }
1609
+ const requiredIntegrationKeys = [
1610
+ "integration_owner",
1611
+ "parent_branch",
1612
+ "source_checkpoint",
1613
+ "integration",
1614
+ ];
1615
+ const missingIntegrationKeys = requiredIntegrationKeys.filter((key) => !(key in worktree));
1616
+ if (missingIntegrationKeys.length) {
1617
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration contract missing ${JSON.stringify(missingIntegrationKeys)}`);
1618
+ continue;
1619
+ }
1620
+ if (!VALID_TERMINAL_ACTION.has(worktree.terminal_action)) {
1621
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} has invalid terminal_action ${worktree.terminal_action}`);
1622
+ }
1623
+ if (typeof worktree.integration_owner !== "string" || !worktree.integration_owner.trim()) {
1624
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration_owner is required`);
1625
+ }
1626
+ if (typeof worktree.parent_branch !== "string" || !worktree.parent_branch.trim()) {
1627
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} parent_branch is required`);
1628
+ } else if (worktree.parent_branch === worktree.branch) {
1629
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} parent_branch must differ from branch`);
1630
+ }
1631
+
1632
+ const sourceRequired = new Set(["review", "integrating", "integrated"]).has(worktree.status);
1633
+ if (sourceRequired && (typeof worktree.source_checkpoint !== "string" || !worktree.source_checkpoint.trim())) {
1634
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} ${worktree.status} requires source_checkpoint`);
1635
+ } else if (
1636
+ worktree.source_checkpoint !== null &&
1637
+ (typeof worktree.source_checkpoint !== "string" || !worktree.source_checkpoint.trim())
1638
+ ) {
1639
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} source_checkpoint must be null or non-empty`);
1640
+ }
1641
+
1642
+ const integration = worktree.integration;
1643
+ if (!integration || typeof integration !== "object" || Array.isArray(integration)) {
1644
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration must be an object`);
1645
+ continue;
1646
+ }
1647
+ const requiredResultKeys = [
1648
+ "status",
1649
+ "parent_before_sha",
1650
+ "source_sha",
1651
+ "result_sha",
1652
+ "method",
1653
+ "conflict_paths",
1654
+ "verification",
1655
+ "evidence",
1656
+ "attempts",
1657
+ ];
1658
+ const missingResultKeys = requiredResultKeys.filter((key) => !(key in integration));
1659
+ if (missingResultKeys.length) {
1660
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration missing ${JSON.stringify(missingResultKeys)}`);
1661
+ }
1662
+ if (!VALID_INTEGRATION_STATUS.has(integration.status)) {
1663
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} has invalid integration status ${integration.status}`);
1664
+ }
1665
+ if (!VALID_INTEGRATION_METHOD.has(integration.method)) {
1666
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} has invalid integration method ${integration.method}`);
1667
+ }
1668
+ if (!VALID_INTEGRATION_VERIFICATION.has(integration.verification)) {
1669
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} has invalid integration verification ${integration.verification}`);
1670
+ }
1671
+ if (!Array.isArray(integration.conflict_paths)) {
1672
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} conflict_paths must be an array`);
1673
+ }
1674
+ if (!Number.isInteger(integration.attempts) || integration.attempts < 0) {
1675
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} attempts must be a non-negative integer`);
1676
+ }
1677
+ if (
1678
+ typeof integration.evidence !== "string" ||
1679
+ !/^<Path>\{roots\.state\}\/specdev\/changes\/[^<]+\/evidence\/T-[0-9]{2,}\.md<\/Path>$/.test(integration.evidence)
1680
+ ) {
1681
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} integration evidence must be a rooted Ticket Evidence path`);
1682
+ }
1683
+ if (worktree.terminal_action === "retain" && new Set(["integrating", "integrated"]).has(worktree.status)) {
1684
+ errors.push(`${basename(path)}: retain worktree ${worktree.ticket_id} cannot be ${worktree.status}`);
1685
+ }
1686
+ const integrationLifecycleActive = new Set(["integrating", "integrated"]).has(worktree.status);
1687
+ if (integrationLifecycleActive) {
1688
+ if (worktree.terminal_action !== "integrate") {
1689
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} ${worktree.status} requires terminal_action=integrate`);
1690
+ }
1691
+ if (typeof integration.parent_before_sha !== "string" || !integration.parent_before_sha.trim()) {
1692
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} ${worktree.status} requires parent_before_sha`);
1693
+ }
1694
+ if (typeof integration.source_sha !== "string" || !integration.source_sha.trim()) {
1695
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} ${worktree.status} requires source_sha`);
1696
+ } else if (integration.source_sha !== worktree.source_checkpoint) {
1697
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} source_sha must equal source_checkpoint`);
1698
+ }
1699
+ if (!Number.isInteger(integration.attempts) || integration.attempts < 1) {
1700
+ errors.push(`${basename(path)}: worktree ${worktree.ticket_id} ${worktree.status} requires attempts >= 1`);
1701
+ }
1702
+ }
1703
+ if (worktree.status === "integrating" && integration.status !== "running") {
1704
+ errors.push(`${basename(path)}: integrating worktree ${worktree.ticket_id} requires integration.status=running`);
1705
+ }
1706
+ if (worktree.status === "integrated") {
1707
+ if (
1708
+ integration.status !== "passed" ||
1709
+ integration.verification !== "passed" ||
1710
+ !new Set(["fast-forward", "merge-commit"]).has(integration.method) ||
1711
+ typeof integration.result_sha !== "string" ||
1712
+ !integration.result_sha.trim()
1713
+ ) {
1714
+ errors.push(`${basename(path)}: integrated worktree ${worktree.ticket_id} requires a passed result checkpoint`);
1715
+ }
1716
+ if (integration.method === "fast-forward") {
1717
+ if (integration.result_sha !== worktree.source_checkpoint) {
1718
+ errors.push(`${basename(path)}: integrated worktree ${worktree.ticket_id} fast-forward result_sha must equal source_checkpoint`);
1719
+ }
1720
+ if (Array.isArray(integration.conflict_paths) && integration.conflict_paths.length > 0) {
1721
+ errors.push(`${basename(path)}: integrated worktree ${worktree.ticket_id} fast-forward cannot record conflict_paths`);
1722
+ }
1723
+ } else if (
1724
+ integration.method === "merge-commit" &&
1725
+ typeof integration.result_sha === "string" &&
1726
+ (integration.result_sha === worktree.source_checkpoint || integration.result_sha === integration.parent_before_sha)
1727
+ ) {
1728
+ errors.push(`${basename(path)}: integrated worktree ${worktree.ticket_id} merge-commit result_sha must be a distinct checkpoint`);
1729
+ }
1730
+ }
1518
1731
  }
1519
1732
  if (data.change_status === "archived") {
1520
1733
  if (data.archived !== true) errors.push(`${basename(path)}: archived status requires archived=true`);