@tea-agent/loop-agent 0.41.1-beta.0 → 0.41.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 (209) hide show
  1. package/CHANGELOG.md +61 -267
  2. package/dist/application/dag/generate-task-dag.js +19 -75
  3. package/dist/application/task-lifecycle/advance.js +5 -24
  4. package/dist/application/task-lifecycle/observe.js +17 -171
  5. package/dist/application/task-lifecycle/plan-transitions.js +7 -42
  6. package/dist/application/task-lifecycle/recommendations.js +2 -13
  7. package/dist/build-stamp.json +3 -3
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +0 -5
  10. package/dist/commands/client-recovery.js +0 -3
  11. package/dist/commands/init.js +1 -27
  12. package/dist/commands/task-advance.js +0 -19
  13. package/dist/executors/dag-pi-executor.js +231 -3784
  14. package/dist/executors/pi-executor.js +4 -15
  15. package/dist/executors/pi-extension-resolver.js +14 -2
  16. package/dist/executors/shell-executor.js +217 -945
  17. package/dist/executors/shell-write-guard.js +0 -7
  18. package/dist/shared/dag-failure-category.js +0 -12
  19. package/dist/shared/openspec-spec.js +4 -70
  20. package/dist/shared/operator/capabilities.js +0 -7
  21. package/dist/task/config-types.js +7 -34
  22. package/dist/task/contract/adopt.js +0 -4
  23. package/dist/task/contract/import-revision.js +0 -4
  24. package/dist/task/contract/project.js +0 -3
  25. package/dist/task/contract/schema.js +1 -2
  26. package/dist/task/frontend-project-capability.js +20 -203
  27. package/dist/task/runtime.js +2 -5
  28. package/dist/task/source-prepare/build-draft.js +3 -3
  29. package/dist/task/source-prepare/fragment-inventory.js +9 -15
  30. package/dist/task/source-prepare/prepare.js +1 -89
  31. package/dist/task/source-prepare/semantic-intake.js +2 -6
  32. package/dist/task/source-references.js +1 -22
  33. package/dist/worker/console/chat/chat-ui-policy.js +4 -2
  34. package/dist/worker/console/chat/pi-runtime.js +235 -13
  35. package/dist/worker/console/chat/repo-browser.js +10 -3
  36. package/dist/worker/console/chat/routes.js +86 -44
  37. package/dist/worker/console/chat/semantic-activity.js +12 -11
  38. package/dist/worker/console/chat/session-stats.js +96 -0
  39. package/dist/worker/console/chat/session-store.js +133 -70
  40. package/dist/worker/console/chat/shortcuts.js +1 -1
  41. package/dist/worker/console/chat/turn-process.js +139 -60
  42. package/dist/worker/console/index.js +0 -3
  43. package/dist/worker/console/operation-store.js +6 -226
  44. package/dist/worker/console/operator-actions.js +7 -169
  45. package/dist/worker/console/pi-plugins.js +3 -1
  46. package/dist/worker/console/server.js +19 -4
  47. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bho4we4c.js → abnfDiagram-N423BO3Z-DQ5jlYVU.js} +1 -1
  48. package/dist/worker/console/static/assets/{arc-TBDkTeK0.js → arc-BeTVCzH9.js} +1 -1
  49. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BF_fZrn2.js → architectureDiagram-T3A2C74G-DassREBi.js} +1 -1
  50. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-CCnPgUe2.js → blockDiagram-VBNYF7ZC-DyeNX8Ek.js} +1 -1
  51. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BCYZJLAT.js → c4Diagram-5PPSVZJV-BXb_zxGx.js} +1 -1
  52. package/dist/worker/console/static/assets/channel-B_lofstT.js +1 -0
  53. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D8K6adhU.js → chunk-2GRJ4B5K-B9F9gxGC.js} +1 -1
  54. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DkT1Q78b.js → chunk-2Q5K7J3B-Byd5zHEQ.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DuhvpIr7.js → chunk-5RXB4S5H-BqoxMJk9.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BnK6dC49.js → chunk-5VM5RSS4-CT7d6YZw.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DPz__z7r.js → chunk-6Q2QTUOP-CdiNyN0N.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-0kkppM2j.js → chunk-GF5L2VYU-BGw9bpVc.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BB41iK5t.js → chunk-JWPE2WC7-DiDVlVB1.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-CiVrxKHU.js → chunk-KBJHAD2P-BhHf-4yK.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-C2h9Xje1.js → chunk-RYQCIY6F-B79pEgwv.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-3iOSHdbM.js → chunk-XXDRQBXY-Cb70EcUV.js} +1 -1
  63. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-ieUI0-eY.js +1 -0
  64. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-ieUI0-eY.js +1 -0
  65. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BN3A7tkk.js → cose-bilkent-JH36ORCC-DBHLnAf1.js} +1 -1
  66. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CTuBy0Or.js → cynefin-VYW2F7L2-BT_JRsOH.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-wb6tUlMN.js → cynefinDiagram-MW4NZA55-Bss-msa3.js} +1 -1
  68. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-GP1Ivx8B.js → dagre-VZM6K2ZE-B8Gr24pt.js} +1 -1
  69. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-ejrcK6L0.js → diagram-7IWD3JNH-BrjU70Jw.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-YFkCCvUU.js → diagram-B4RE2ZJO-Cp_JpPGd.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-B4rrvIaL.js → diagram-LBJQPF4R-BWKICEP-.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-C-YIZcUf.js → diagram-Q27KOJAE-D3z_UNR_.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPF5wcL1.js → diagram-UB23O5K3-_Ywm390x.js} +1 -1
  74. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CU5SzXjO.js → ebnfDiagram-BXEA7PRR-D9QYQQMB.js} +1 -1
  75. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CpL414DE.js → erDiagram-JOGREHBK-BNgXNKAr.js} +1 -1
  76. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-MrhLzNau.js → flowDiagram-UKHOOZJN-QRLnkPCg.js} +1 -1
  77. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Bg4qFuUo.js → ganttDiagram-PKOTCBZU-B9fOduNi.js} +1 -1
  78. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-ColxSXLR.js → gitGraphDiagram-DS77QQ5N-FSEdtPN2.js} +1 -1
  79. package/dist/worker/console/static/assets/index-Cs9JUSPs.css +1 -0
  80. package/dist/worker/console/static/assets/index-DtPrwu7M.js +451 -0
  81. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Czvtm6gc.js → infoDiagram-6WML65LV-D5Bjg9dm.js} +1 -1
  82. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B7Xp8HyJ.js → ishikawaDiagram-WSZJBQD7-BlNRkWvf.js} +1 -1
  83. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DWYZcvph.js → journeyDiagram-NVQOT4AX-BdJiw3SL.js} +1 -1
  84. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-KTLZRUjx.js → kanban-definition-27J2QSJJ-CsDNlYht.js} +1 -1
  85. package/dist/worker/console/static/assets/{linear-_Exn7bfl.js → linear-8g9dbEmX.js} +1 -1
  86. package/dist/worker/console/static/assets/{mermaid.core-Cxg8kX8V.js → mermaid.core-ChyQH_ut.js} +5 -5
  87. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-oKrlo2Bt.js → mindmap-definition-FAOFIHXS-DwYoCTYt.js} +1 -1
  88. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-XAeteiNd.js → pegDiagram-VL7TDLO6-T_b2rS17.js} +1 -1
  89. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BTf1K-lp.js → pieDiagram-7S7Q4E2Y-1Cr7WMhH.js} +1 -1
  90. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C0IlY0Jf.js → quadrantDiagram-CIZ2JOQS-Bq4qM59o.js} +1 -1
  91. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BVbKtZhW.js → railroadDiagram-AXF67PYL-ytFgR1RL.js} +1 -1
  92. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-C5e4JFcr.js → requirementDiagram-LRYGKXZP-CnUdUVYX.js} +1 -1
  93. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cz30hc29.js → sankeyDiagram-W5VNT64P-_5rJqG6R.js} +1 -1
  94. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DS33n6d0.js → sequenceDiagram-SI44F4Z6-D5imyGbt.js} +1 -1
  95. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-46w7v-9T.js → sizeCapture-X5ZJPWSS-DvgDn3wL.js} +1 -1
  96. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-pbcD5SsZ.js → stateDiagram-OKZ733FA-BViu7duc.js} +1 -1
  97. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-kNxnG4_w.js +1 -0
  98. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DuivYBSj.js → swimlanes-SLNWSIFB-BcULKg2P.js} +2 -2
  99. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DyTzlcWH.js +8 -0
  100. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cb9VZVIl.js → timeline-definition-Z64GVDOM-DmPveIjt.js} +1 -1
  101. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-D2-mfOgR.js → vennDiagram-T6HMQDX7-9r-iFy0R.js} +1 -1
  102. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DeS2aC76.js → wardleyDiagram-T6FBY63Y-D3fIXdvM.js} +1 -1
  103. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-WGsrFjxH.js → xychartDiagram-ELKLHX3M-BCSS9SBg.js} +1 -1
  104. package/dist/worker/console/static/index.html +2 -2
  105. package/dist/worker/console/static-src/app/useRunProgress.js +9 -2
  106. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
  107. package/dist/worker/console/static-src/operator-chat/dag-progress-link.js +22 -0
  108. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
  109. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +2 -0
  110. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -0
  111. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
  112. package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +106 -0
  113. package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
  114. package/dist/worker/console/workspace-context.js +1 -111
  115. package/dist/worker/observability/read-model.js +4 -0
  116. package/dist/worker/observe/routes.js +4 -0
  117. package/dist/worker/observe/static/dag-node-purpose.js +0 -5
  118. package/dist/worker/observe/static/views/dag-inspector.js +2 -7
  119. package/dist/worker/observe/static/views/session-timeline.js +46 -15
  120. package/dist/workflows/dag/backend-test-result-contract.js +1 -1
  121. package/dist/workflows/dag/contract-validator-registrations.js +2 -1
  122. package/dist/workflows/dag/frontend-implementation-contract.js +162 -932
  123. package/dist/workflows/dag/frontend-plan-render.js +1 -2
  124. package/dist/workflows/dag/frontend-prewrite-gate.js +349 -255
  125. package/dist/workflows/dag/frontend-recovery-plan.js +10 -17
  126. package/dist/workflows/dag/frontend-recovery-run.js +20 -33
  127. package/dist/workflows/dag/frontend-repair.js +432 -1
  128. package/dist/workflows/dag/frontend-review-context.js +15 -261
  129. package/dist/workflows/dag/frontend-verification-trace.js +24 -249
  130. package/dist/workflows/dag/frontend-worktree-diff.js +17 -250
  131. package/dist/workflows/dag/init-hybrid.js +494 -685
  132. package/dist/workflows/dag/interrupt-request.js +0 -7
  133. package/dist/workflows/dag/node-execution.js +4 -732
  134. package/dist/workflows/dag/prompt.js +1 -34
  135. package/dist/workflows/dag/report.js +1 -37
  136. package/dist/workflows/dag/rerun-feedback.js +1 -256
  137. package/dist/workflows/dag/rerun-plan.js +2 -10
  138. package/dist/workflows/dag/rerun-run.js +0 -23
  139. package/dist/workflows/dag/rerun-task.js +0 -29
  140. package/dist/workflows/dag/retry-policy.js +104 -214
  141. package/dist/workflows/dag/runner.js +122 -429
  142. package/dist/workflows/dag/scheduler.js +16 -114
  143. package/dist/workflows/dag/types.js +12 -239
  144. package/dist/workflows/dag/validate.js +12 -28
  145. package/docs/init-surface.manifest.json +12 -30
  146. package/docs/skills/vetted-skill-registry.md +2 -4
  147. package/docs/templates/README.md +0 -2
  148. package/docs/templates/agent-dag-report.schema.json +2 -8
  149. package/docs/templates/agent-dag.schema.json +1 -1
  150. package/docs/templates/frontend-implementation-contract.schema.json +1 -4
  151. package/harness.json +3 -3
  152. package/package.json +1 -1
  153. package/skills/frontend-bounded-implement/SKILL.md +14 -15
  154. package/skills/frontend-design-review/SKILL.md +41 -22
  155. package/skills/frontend-implementation/SKILL.md +52 -0
  156. package/skills/frontend-implementation/references/code-standards.md +33 -0
  157. package/skills/frontend-implementation/references/design-spec.md +56 -0
  158. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  159. package/skills/frontend-review/SKILL.md +15 -20
  160. package/skills/frontend-review/references/review-findings.md +7 -6
  161. package/skills/frontend-verification/SKILL.md +1 -1
  162. package/skills/loop-agent/references/command-reference.md +0 -1
  163. package/skills/loop-agent/references/hybrid-dag.md +2 -2
  164. package/dist/commands/dag-follow-up.js +0 -138
  165. package/dist/executors/pi-read-budget-policy.js +0 -239
  166. package/dist/worker/console/frontend-human-decision-adapter.js +0 -19
  167. package/dist/worker/console/frontend-split-operation-adapter.js +0 -20
  168. package/dist/worker/console/static/assets/channel-bnXW0U3C.js +0 -1
  169. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-rZpq_twR.js +0 -1
  170. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-rZpq_twR.js +0 -1
  171. package/dist/worker/console/static/assets/index-D3CC4eUz.css +0 -1
  172. package/dist/worker/console/static/assets/index-DLt4ZFvD.js +0 -437
  173. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-zBMYiUyi.js +0 -1
  174. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-d_rms-5i.js +0 -8
  175. package/dist/worker/console/static-src/active-run-badge.js +0 -17
  176. package/dist/worker/materialize/frontend-split-task-materializer.js +0 -72
  177. package/dist/workflows/dag/dag-retry-schema.js +0 -138
  178. package/dist/workflows/dag/frontend-closeout.js +0 -221
  179. package/dist/workflows/dag/frontend-design-policy.js +0 -400
  180. package/dist/workflows/dag/frontend-human-decision.js +0 -182
  181. package/dist/workflows/dag/frontend-provider-capability-matrix.js +0 -159
  182. package/dist/workflows/dag/frontend-read-budgets.js +0 -12
  183. package/dist/workflows/dag/frontend-recovery-capsule.js +0 -455
  184. package/dist/workflows/dag/frontend-recovery-controller.js +0 -202
  185. package/dist/workflows/dag/frontend-recovery-lineage.js +0 -178
  186. package/dist/workflows/dag/frontend-review-findings.js +0 -270
  187. package/dist/workflows/dag/frontend-shadow-dual-write.js +0 -914
  188. package/dist/workflows/dag/frontend-shape-capsule-store.js +0 -191
  189. package/dist/workflows/dag/frontend-shape-facts.js +0 -409
  190. package/dist/workflows/dag/frontend-shape.js +0 -427
  191. package/dist/workflows/dag/frontend-source-fidelity-ledger.js +0 -108
  192. package/dist/workflows/dag/frontend-split-application-service.js +0 -203
  193. package/dist/workflows/dag/frontend-split-orchestrator.js +0 -899
  194. package/dist/workflows/dag/frontend-typed-event-store.js +0 -452
  195. package/dist/workflows/dag/frontend-typed-event-transaction.js +0 -180
  196. package/dist/workflows/dag/frontend-writer-admission.js +0 -285
  197. package/dist/workflows/dag/frontend-writer-status.js +0 -256
  198. package/dist/workflows/dag/recovery-lease.js +0 -80
  199. package/docs/templates/frontend-implementation-dag.json +0 -89
  200. package/docs/templates/spec-registry.schema.json +0 -45
  201. package/skills/frontend-bounded-implement/references/code-standards.md +0 -19
  202. package/skills/frontend-contract/SKILL.md +0 -23
  203. package/skills/frontend-contract/references/contract-protocol.md +0 -34
  204. package/skills/frontend-plan/SKILL.md +0 -26
  205. package/skills/frontend-plan/references/decision-contract.md +0 -37
  206. package/skills/frontend-plan/references/design-decisions.md +0 -17
  207. package/skills/frontend-scout/SKILL.md +0 -25
  208. package/skills/frontend-scout/references/design-evidence.md +0 -16
  209. package/skills/frontend-scout/references/scout-evidence.md +0 -23
@@ -124,13 +124,6 @@ export function isEphemeralToolCachePath(filePath) {
124
124
  if (normalized === ".coverage" || normalized.startsWith(".coverage.")) {
125
125
  return true;
126
126
  }
127
- // vite/vitest runtime caches live under node_modules/.vite (e.g.
128
- // node_modules/.vite/vitest/results.json). node_modules is the gitignored
129
- // dependency tree — tool cache writes there are never a writer's product
130
- // change and must not trip the write guard.
131
- if (normalized.startsWith("node_modules/.vite/")) {
132
- return true;
133
- }
134
127
  if (/(?:^|\/)__pycache__(?:\/|$)/.test(normalized))
135
128
  return true;
136
129
  if (/\.(?:pyc|pyo)$/.test(normalized))
@@ -47,22 +47,10 @@ const RAW_TO_NORMALIZED = {
47
47
  "verify-failure": "validation",
48
48
  "test-failure": "validation",
49
49
  "verification-plan-stale": "validation",
50
- "read-burst": "validation",
51
- // Writer persisted real file changes but the final provider call hit a
52
- // context overflow; verification never ran. Distinct from empty-output so
53
- // the report shows partial success instead of a spec issue.
54
- "partial-success-with-context-overflow": "validation",
55
- // Writer burned an excessive token budget (unconverged read-edit-test loop)
56
- // and still failed; distinct from empty-output for accurate reporting.
57
- "writer-budget-exhausted": "validation",
58
50
  network: "executor",
59
51
  quota: "executor",
60
52
  "rate-limit": "executor",
61
53
  unavailable: "executor",
62
- // Context window overflow (400 request-too-large): the session needs
63
- // compaction / fresh context before retrying — an executor-side
64
- // environment condition, not a plan-quality defect.
65
- "context-overflow": "executor",
66
54
  // Deterministic capability/policy refusal: missing verified launcher JS
67
55
  // entry, capability allowlist rejection, or missing bounded write tool
68
56
  // profile. These are environment/config defects the operator repairs
@@ -12,19 +12,6 @@ export const OPENSPEC_SPEC_DIRS = [
12
12
  "openspec/project-specs",
13
13
  "ai_workspace",
14
14
  ];
15
- /** Default roots used when a frontend task does not declare specRoots. */
16
- export const DEFAULT_FRONTEND_SPEC_ROOTS = OPENSPEC_SPEC_DIRS;
17
- /** A task-owned spec root must be a normalized, repo-relative POSIX directory. */
18
- export function isValidFrontendSpecRoot(value) {
19
- return (Boolean(value) &&
20
- !value.includes("\\") &&
21
- !value.startsWith("/") &&
22
- !value.includes("//") &&
23
- !value.split("/").some((segment) => !segment ||
24
- segment === "." ||
25
- segment === ".." ||
26
- /\s/.test(segment)));
27
- }
28
15
  /** Allowed spec file extensions (case-insensitive). */
29
16
  export const OPENSPEC_SPEC_EXT_RE = /\.(md|mdx|json|yaml|yml)$/i;
30
17
  /**
@@ -56,17 +43,14 @@ export function isGovernanceSubtreePath(filePath, governanceRoot) {
56
43
  * directories. `filePath` must be a repo-relative POSIX path (backslashes are
57
44
  * normalised internally). Directory names are case-sensitive.
58
45
  */
59
- export function isFrontendSpecPath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
46
+ export function isOpenspecSpecPath(filePath) {
60
47
  const normalized = filePath.replaceAll("\\", "/");
61
- for (const dir of specRoots) {
48
+ for (const dir of OPENSPEC_SPEC_DIRS) {
62
49
  if (normalized === dir || normalized.startsWith(dir + "/"))
63
50
  return true;
64
51
  }
65
52
  return false;
66
53
  }
67
- export function isOpenspecSpecPath(filePath) {
68
- return isFrontendSpecPath(filePath);
69
- }
70
54
  /**
71
55
  * Returns `true` when `filename` (the basename only, not a full path) has an
72
56
  * allowed frontend specification extension.
@@ -75,12 +59,9 @@ export function isValidOpenspecExtension(filename) {
75
59
  return OPENSPEC_SPEC_EXT_RE.test(filename);
76
60
  }
77
61
  /** Returns `true` for a supported file inside a canonical openspec spec dir. */
78
- export function isFrontendSpecFilePath(filePath, specRoots = DEFAULT_FRONTEND_SPEC_ROOTS) {
79
- return (isFrontendSpecPath(filePath, specRoots) &&
80
- isValidOpenspecExtension(filePath.replaceAll("\\", "/")));
81
- }
82
62
  export function isOpenspecSpecFilePath(filePath) {
83
- return isFrontendSpecFilePath(filePath);
63
+ return (isOpenspecSpecPath(filePath) &&
64
+ isValidOpenspecExtension(filePath.replaceAll("\\", "/")));
84
65
  }
85
66
  /**
86
67
  * Returns `true` when free-form search input references a canonical frontend
@@ -142,50 +123,3 @@ export function extractTaskSourceOpenspecPaths(markdown, governanceRoot = DEFAUL
142
123
  !isGovernanceSubtreePath(token, governanceRoot))
143
124
  .sort();
144
125
  }
145
- /**
146
- * Extract explicit specification-file citations for a task's frozen spec roots.
147
- * This is intentionally path-only: source preparation decides which roots are
148
- * normative, while DAG execution only consumes that frozen configuration.
149
- */
150
- export function extractTaskSourceFrontendSpecPaths(markdown, specRoots, governanceRoot = DEFAULT_OPENSPEC_GOVERNANCE_ROOT) {
151
- if (!markdown)
152
- return [];
153
- const raw = new Set();
154
- const collect = (token) => {
155
- const normalized = normalizeCitationToken(token);
156
- if (normalized)
157
- raw.add(normalized);
158
- };
159
- for (const match of markdown.matchAll(/`([^`\n]+)`/g))
160
- collect(match[1] ?? "");
161
- for (const match of markdown.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g))
162
- collect(match[1] ?? "");
163
- for (const root of specRoots) {
164
- const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
165
- for (const match of markdown.matchAll(new RegExp(`(?:^|[\\s\"'([>])(${escaped}/[A-Za-z0-9._/-]+)`, "g"))) {
166
- collect(match[1] ?? "");
167
- }
168
- }
169
- return [...raw]
170
- .filter((token) => isFrontendSpecFilePath(token, specRoots) && !isGovernanceSubtreePath(token, governanceRoot))
171
- .sort();
172
- }
173
- /**
174
- * Source-preparation helper for explicitly named normative directories. A root
175
- * is inferred only on a line that declares a spec/规范/design-system intent;
176
- * arbitrary Markdown links never silently broaden task configuration.
177
- */
178
- export function extractDeclaredFrontendSpecRoots(markdown) {
179
- const roots = new Set();
180
- for (const line of markdown.split(/\r?\n/)) {
181
- if (!/(?:规范|规约|标准|设计系统|\bspec(?:ification)?\b|\bdesign[ -]?system\b|\bguidelines?\b|\brules?\b)/i.test(line))
182
- continue;
183
- for (const match of line.matchAll(/`([^`]+)`|\[[^\]]+\]\(([^)\s]+)\)/g)) {
184
- const raw = (match[1] ?? match[2] ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
185
- if (!raw || !isValidFrontendSpecRoot(raw))
186
- continue;
187
- roots.add(isValidOpenspecExtension(raw) ? raw.split("/").slice(0, -1).join("/") : raw);
188
- }
189
- }
190
- return [...roots].filter(isValidFrontendSpecRoot).sort();
191
- }
@@ -2240,13 +2240,6 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2240
2240
  source: "loop-agent",
2241
2241
  note: "server-side read-only run-facts check (ineligible plan / explicit rerun-task recommendation) replaces the browser Human Gate (2026-08-11).",
2242
2242
  },
2243
- {
2244
- command: "loop-agent dag follow-up",
2245
- coverage: "excluded",
2246
- action: null,
2247
- source: "loop-agent",
2248
- exclusionReason: "Frontend review recovery helper is a direct advanced CLI composition (create/import task and optional advance), not an Operator action endpoint.",
2249
- },
2250
2243
  {
2251
2244
  command: "loop-agent dag reconcile-tasks",
2252
2245
  coverage: "human-gated-required",
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, } from "../shared/openspec-spec.js";
2
+ import { isOpenspecSpecFilePath } from "../shared/openspec-spec.js";
3
3
  export const taskFlowSchema = z.enum([
4
4
  "auto",
5
5
  "micro",
@@ -76,27 +76,13 @@ export const frontendOpenspecPolicySchema = z.enum(["cited", "scan-strict"]);
76
76
  */
77
77
  export const frontendOpenspecConfigSchema = z.object({
78
78
  policy: frontendOpenspecPolicySchema.optional().default("cited"),
79
- /**
80
- * Repo-relative directories containing frontend normative files. These are
81
- * frozen in task.json during contract preparation, before DAG generation.
82
- */
83
- specRoots: z
84
- .array(z.string().refine((candidate) => isValidFrontendSpecRoot(candidate), "frontendOpenspec.specRoots entries must be normalized repo-relative directories"))
85
- .optional(),
86
79
  requiredReadPaths: z
87
- .array(z.string())
80
+ .array(z
81
+ .string()
82
+ .refine((candidate) => !candidate.includes("\\") &&
83
+ isOpenspecSpecFilePath(candidate), "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under openspec/schemas/, openspec/project-specs/, or ai_workspace/"))
88
84
  .optional()
89
85
  .default([]),
90
- }).superRefine((value, ctx) => {
91
- const roots = value.specRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
92
- if (new Set(roots).size !== roots.length) {
93
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.specRoots must not contain duplicates", path: ["specRoots"] });
94
- }
95
- for (const required of value.requiredReadPaths) {
96
- if (!isFrontendSpecFilePath(required, roots)) {
97
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "frontendOpenspec.requiredReadPaths entries must be repo-relative supported files under frontendOpenspec.specRoots", path: ["requiredReadPaths"] });
98
- }
99
- }
100
86
  });
101
87
  /** Batch limits and gate profile for the browser-driven frontend test DAG.
102
88
  * Token fields are post-case stop thresholds, not model-provider hard caps. */
@@ -163,6 +149,8 @@ export const backendTestConfigSchema = z
163
149
  executeScope: z.enum(["affected", "all"]).optional(),
164
150
  /** Backend-test-only workspace mutation control; omitted means Git guard. */
165
151
  workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
152
+ /** Per-command timeout for the readiness-authorized pytest/HTML execution node. */
153
+ executeTimeoutMs: z.number().int().min(60_000).max(7_200_000).optional(),
166
154
  })
167
155
  .strict()
168
156
  .optional();
@@ -221,21 +209,6 @@ const taskConfigObjectSchema = z.object({
221
209
  maxFixLoops: z.number().int().min(0).optional().default(2),
222
210
  /** Supervised DAG convergence is opt-in until runtime smoke evidence is stronger. */
223
211
  convergence: convergenceConfigSchema.optional().default({ enabled: false }),
224
- /** Frontend recovery continuation quota. Bounds how many times a failed
225
- * frontend run may auto-recover via M6 recovery operations (1 = current
226
- * default; raise to 2-3 for more tolerance of transient verify failures). */
227
- frontendRecovery: z
228
- .object({
229
- maxContinuations: z
230
- .number()
231
- .int()
232
- .min(1)
233
- .max(5)
234
- .optional()
235
- .default(1),
236
- })
237
- .strict()
238
- .optional(),
239
212
  /** Outer loop auto mode never writes by default; DAG execute requires this policy plus path/writeSet gates. */
240
213
  loopAutoExecutionPolicy: loopAutoExecutionPolicySchema
241
214
  .optional()
@@ -90,10 +90,6 @@ export async function adoptTaskContract(input) {
90
90
  requirementBytes: bytes.requirementBytes,
91
91
  constraintsBytes: bytes.constraintsBytes,
92
92
  referenceManifestBytes: bytes.referenceManifestBytes,
93
- ...(state.ref?.sourceHashes.requirementLedgerSha256 &&
94
- bytes.requirementLedgerBytes !== null
95
- ? { requirementLedgerBytes: bytes.requirementLedgerBytes }
96
- : {}),
97
93
  });
98
94
  const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
99
95
  const canonicalHash = computeCanonicalHash({
@@ -45,10 +45,6 @@ export async function refreshManagedContractAfterImport(input) {
45
45
  requirementBytes: bytes.requirementBytes,
46
46
  constraintsBytes: bytes.constraintsBytes,
47
47
  referenceManifestBytes: bytes.referenceManifestBytes,
48
- ...(state2.ref.sourceHashes.requirementLedgerSha256 &&
49
- bytes.requirementLedgerBytes !== null
50
- ? { requirementLedgerBytes: bytes.requirementLedgerBytes }
51
- : {}),
52
48
  });
53
49
  const taskConfigSha256 = computeTaskConfigSha256(fullConfig);
54
50
  const canonicalHash = computeCanonicalHash({
@@ -184,9 +184,6 @@ export function mergeManagedTaskConfigFields(existing, draft) {
184
184
  command: cmd.command,
185
185
  ...(cmd.timeoutMs !== undefined ? { timeoutMs: cmd.timeoutMs } : {}),
186
186
  })),
187
- ...(draft.frontendOpenspec !== undefined
188
- ? { frontendOpenspec: draft.frontendOpenspec }
189
- : {}),
190
187
  };
191
188
  if (draft.featureId !== undefined) {
192
189
  next.featureId = draft.featureId;
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { backendTestConfigSchema, frontendOpenspecConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
2
+ import { backendTestConfigSchema, taskKindSchema, taskVerifyCommandSchema, } from "../config-types.js";
3
3
  import { TASK_CONFIG_SCHEMA_VERSION, TASK_CONTRACT_CANONICALIZER_VERSION, TASK_CONTRACT_DRAFT_SCHEMA_VERSION, TASK_CONTRACT_PROJECTION_VERSION, TASK_CONTRACT_REF_SCHEMA_VERSION, } from "./constants.js";
4
4
  const sha256Schema = z
5
5
  .string()
@@ -74,7 +74,6 @@ export const taskContractDraftSchema = z
74
74
  openQuestions: z.array(z.string()).optional(),
75
75
  assumptions: z.array(z.string()).optional(),
76
76
  hardConstraints: z.array(z.string()).optional(),
77
- frontendOpenspec: frontendOpenspecConfigSchema.optional(),
78
77
  interviewAssessmentRef: z
79
78
  .object({
80
79
  draftSha256: z.string().min(1),
@@ -3,13 +3,13 @@
3
3
  * Strong evidence only: package.json deps, config files, source imports.
4
4
  * Lockfile-only or directory-name-only must NOT activate adapters.
5
5
  */
6
- import { lstat, readFile, readdir, realpath, stat } from "node:fs/promises";
6
+ import { readFile, readdir, stat } from "node:fs/promises";
7
7
  import path from "node:path";
8
8
  import { z } from "zod";
9
- import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, isValidFrontendSpecRoot, } from "../shared/openspec-spec.js";
9
+ import { DEFAULT_OPENSPEC_GOVERNANCE_ROOT, MAX_OPENSPEC_CANDIDATE_PATHS, OPENSPEC_SPEC_DIRS, OPENSPEC_SPEC_EXT_RE, isGovernanceSubtreePath, isOpenspecSpecFilePath, } from "../shared/openspec-spec.js";
10
10
  const classifiedPathSchema = z
11
11
  .string()
12
- .refine((candidate) => OPENSPEC_SPEC_EXT_RE.test(candidate), "classified path must be a repo-relative supported spec file");
12
+ .refine((candidate) => isOpenspecSpecFilePath(candidate), "classified path must be a repo-relative supported spec file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
13
13
  /**
14
14
  * Stable semantic partition of `designEvidence.normativePaths`. Keys are a
15
15
  * generic directory convention (not project-specific file names); the same
@@ -81,105 +81,6 @@ async function exists(filePath) {
81
81
  return false;
82
82
  }
83
83
  }
84
- export const FRONTEND_SPEC_REGISTRY_PATH = "ai_workspace/loop-agent/spec-registry.json";
85
- const SPEC_SIGNAL_RE = /(?:规范|规约|标准|约定|guideline|standard|convention|design[- ]?system|rule|token|component|style|theme)/i;
86
- const SPEC_EXCLUDE_RE = /(?:^|\/)(?:archive|history|changelog|changes|proposals?|release[-_ ]notes?)(?:\/|$)/i;
87
- /** Stable, bounded heuristic used only for candidate ordering/classification. */
88
- export function scoreFrontendSpecCandidate(filePath, contentPreview = "", options = {}) {
89
- const normalized = filePath.replaceAll("\\", "/");
90
- const lower = normalized.toLowerCase();
91
- const reasons = [];
92
- let score = 0;
93
- if (options.registryMode === "normative") {
94
- score += 100;
95
- reasons.push("registry-normative");
96
- }
97
- else if (options.registryMode === "advisory") {
98
- score += 30;
99
- reasons.push("registry-advisory");
100
- }
101
- if (options.taskRelated) {
102
- score += 25;
103
- reasons.push("task-related");
104
- }
105
- if (/(?:design-system|guidelines?|standards?|conventions?|rules?|specs?)(?:\/|$)/i.test(lower)) {
106
- score += 20;
107
- reasons.push("spec-directory");
108
- }
109
- if (/(?:guidelines?|standards?|conventions?|rules?|tokens?|components?|theme|style)/i.test(path.basename(lower))) {
110
- score += 15;
111
- reasons.push("spec-filename");
112
- }
113
- if (SPEC_SIGNAL_RE.test(contentPreview)) {
114
- score += 20;
115
- reasons.push("spec-content");
116
- }
117
- if (/(?:^|\n)\s*(?:#{1,6}\s*)?(?:rules?|do|don't|必须|禁止|约定|规范)\b/im.test(contentPreview)) {
118
- score += 15;
119
- reasons.push("normative-structure");
120
- }
121
- if (SPEC_EXCLUDE_RE.test(lower)) {
122
- score -= 80;
123
- reasons.push("excluded-history-path");
124
- }
125
- const kind = options.registryMode === "normative" || score >= 80
126
- ? "normative"
127
- : score >= 35
128
- ? "advisory"
129
- : "unknown";
130
- return { path: normalized, kind, score, reasons, source: options.registryMode ? "registry" : "auto-discovered" };
131
- }
132
- /** Read the optional project-level registry without making it mandatory. */
133
- export async function readFrontendSpecRegistry(repoRoot) {
134
- const raw = await readJson(path.join(repoRoot, FRONTEND_SPEC_REGISTRY_PATH));
135
- if (!raw || typeof raw !== "object")
136
- return null;
137
- const value = raw;
138
- if (value.version !== 1 || !Array.isArray(value.roots))
139
- return null;
140
- const roots = value.roots
141
- .filter((item) => Boolean(item) && typeof item === "object")
142
- .map((item) => ({
143
- path: typeof item.path === "string" ? item.path.replaceAll("\\", "/").replace(/\/+$/, "") : "",
144
- scope: Array.isArray(item.scope) ? item.scope.filter((v) => typeof v === "string") : undefined,
145
- mode: (item.mode === "normative" || item.mode === "advisory" ? item.mode : undefined),
146
- description: typeof item.description === "string" ? item.description : undefined,
147
- }))
148
- .filter((item) => isValidFrontendSpecRoot(item.path));
149
- return {
150
- version: 1,
151
- roots,
152
- discovery: value.discovery && typeof value.discovery === "object" ? value.discovery : undefined,
153
- };
154
- }
155
- /**
156
- * Resolve only unambiguous symlink-root aliases. The physical target is never
157
- * emitted as a candidate path: it merely lets task-source wording such as
158
- * `ai_doc/rules.md` resolve to the stable repo-local path `ai_workspace/rules.md`.
159
- */
160
- export async function resolveFrontendSpecRootAliases(repoRoot, specRoots) {
161
- const candidates = [];
162
- for (const logicalRoot of specRoots) {
163
- const full = path.join(repoRoot, logicalRoot);
164
- try {
165
- if (!(await lstat(full)).isSymbolicLink())
166
- continue;
167
- const alias = path.basename(await realpath(full));
168
- if (alias && alias !== path.basename(logicalRoot)) {
169
- candidates.push({ alias, logicalRoot });
170
- }
171
- }
172
- catch {
173
- // Missing/broken links are handled by normal candidate discovery drift.
174
- }
175
- }
176
- const counts = new Map();
177
- for (const item of candidates)
178
- counts.set(item.alias, (counts.get(item.alias) ?? 0) + 1);
179
- return candidates
180
- .filter((item) => counts.get(item.alias) === 1)
181
- .sort((a, b) => a.alias.localeCompare(b.alias));
182
- }
183
84
  /**
184
85
  * Resolve the governance subtree root from harness.json.governanceRoot.
185
86
  * Falls back to DEFAULT_OPENSPEC_GOVERNANCE_ROOT when the manifest is missing,
@@ -204,54 +105,28 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
204
105
  .filter(Boolean);
205
106
  const collected = [];
206
107
  let excludedGovernanceCount = 0;
207
- let scannedFiles = 0;
208
- let maxFilesHit = false;
209
- let scannedBytes = 0;
210
- const warnings = [];
211
- const maxDepth = options.maxDepth ?? 4;
212
- const maxFiles = options.maxFiles ?? MAX_OPENSPEC_CANDIDATE_PATHS;
213
- const maxBytes = options.maxBytes ?? 1024 * 1024;
214
- const timeoutMs = options.timeoutMs ?? 2000;
215
- const startedAt = Date.now();
216
- // Metadata / bookkeeping directories that are not normative design specs:
217
- // changelogs, ADR/history archives, proposals under review, and scratch
218
- // state. Reading them inflates the candidate list (and the plan prompt)
219
- // with files no component selection should cite. A task that genuinely
220
- // needs one can still reference it explicitly — the prewrite gate
221
- // candidates come from this discovery, but explicit task-source citations
222
- // flow through openspecMandatoryPaths independently.
223
- const DEFAULT_EXCLUDED_SPEC_PREFIXES = [
224
- "openspec/changes",
225
- "openspec/project-specs/changes",
226
- "openspec/project-specs/changelog",
227
- "openspec/project-specs/history",
228
- "openspec/project-specs/archive",
229
- "openspec/project-specs/decisions",
230
- "openspec/project-specs/proposals",
231
- ];
232
108
  const isExcluded = (rel) => {
233
109
  if (isGovernanceSubtreePath(rel, governanceRoot))
234
110
  return true;
235
- if (DEFAULT_EXCLUDED_SPEC_PREFIXES.some((prefix) => rel === prefix || rel.startsWith(prefix + "/"))) {
236
- return true;
237
- }
238
111
  return extraExcludedPrefixes.some((prefix) => rel === prefix || rel.startsWith(prefix + "/"));
239
112
  };
240
- for (const specDir of options.specRoots ?? [...OPENSPEC_SPEC_DIRS]) {
113
+ for (const specDir of OPENSPEC_SPEC_DIRS) {
241
114
  const root = path.join(repoRoot, specDir);
242
115
  if (!(await exists(root)))
243
116
  continue;
244
117
  await walk(root, 0);
245
118
  }
246
- // Keep the established default-root priority for deterministic compatibility;
247
- // configured extra roots are appended in lexical order.
119
+ // openspec/schemas/** and openspec/project-specs/** sort first; remaining
120
+ // ai_workspace/** business files follow. Dedupe preserves discovery order.
248
121
  const unique = [...new Set(collected)];
249
- const defaultPaths = OPENSPEC_SPEC_DIRS.flatMap((root) => unique.filter((candidate) => candidate.startsWith(root + "/")).sort());
250
- const customPaths = unique
251
- .filter((candidate) => !defaultPaths.includes(candidate))
122
+ const openspecPaths = unique
123
+ .filter((candidate) => candidate.startsWith("openspec/"))
124
+ .sort();
125
+ const aiWorkspacePaths = unique
126
+ .filter((candidate) => candidate.startsWith("ai_workspace/"))
252
127
  .sort();
253
- const deduped = [...defaultPaths, ...customPaths];
254
- const truncated = maxFilesHit || deduped.length > MAX_OPENSPEC_CANDIDATE_PATHS;
128
+ const deduped = [...openspecPaths, ...aiWorkspacePaths];
129
+ const truncated = deduped.length > MAX_OPENSPEC_CANDIDATE_PATHS;
255
130
  const paths = truncated
256
131
  ? deduped.slice(0, MAX_OPENSPEC_CANDIDATE_PATHS)
257
132
  : deduped;
@@ -261,19 +136,10 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
261
136
  excludedGovernanceCount,
262
137
  truncated,
263
138
  maxCandidates: MAX_OPENSPEC_CANDIDATE_PATHS,
264
- warnings,
265
139
  };
266
140
  async function walk(dir, depth) {
267
- if (Date.now() - startedAt >= timeoutMs) {
268
- if (!warnings.includes("timeout-exceeded"))
269
- warnings.push("timeout-exceeded");
270
- return;
271
- }
272
- if (depth > maxDepth) {
273
- if (!warnings.includes("max-depth-exceeded"))
274
- warnings.push("max-depth-exceeded");
141
+ if (depth > 4)
275
142
  return;
276
- }
277
143
  let entries = [];
278
144
  try {
279
145
  entries = (await readdir(dir)).sort();
@@ -282,31 +148,12 @@ export async function listOpenspecDiscovery(repoRoot, options = {}) {
282
148
  return;
283
149
  }
284
150
  for (const name of entries) {
285
- if (Date.now() - startedAt >= timeoutMs) {
286
- if (!warnings.includes("timeout-exceeded"))
287
- warnings.push("timeout-exceeded");
288
- return;
289
- }
290
- if (scannedFiles >= maxFiles) {
291
- if (!warnings.includes("max-files-exceeded"))
292
- warnings.push("max-files-exceeded");
293
- maxFilesHit = true;
294
- return;
295
- }
296
151
  const full = path.join(dir, name);
297
152
  try {
298
153
  const info = await stat(full);
299
154
  if (info.isDirectory())
300
155
  await walk(full, depth + 1);
301
156
  else if (OPENSPEC_SPEC_EXT_RE.test(name)) {
302
- const size = info.size;
303
- if (scannedBytes + size > maxBytes) {
304
- if (!warnings.includes("max-bytes-exceeded"))
305
- warnings.push("max-bytes-exceeded");
306
- return;
307
- }
308
- scannedFiles++;
309
- scannedBytes += size;
310
157
  const rel = path.relative(repoRoot, full).replace(/\\/g, "/");
311
158
  if (isExcluded(rel)) {
312
159
  excludedGovernanceCount++;
@@ -394,14 +241,6 @@ function emptyClassified() {
394
241
  * project-specific file names. Basename stems use word-boundary matching so
395
242
  * e.g. `*api*` (case-insensitive) maps to `rule.api` without hardcoding names.
396
243
  */
397
- /** True when any path segment (other than the file itself) carries a
398
- * component/theme kind marker. Directory-organized libraries such as
399
- * `ui/ucp-components-md/AdvancedSearch.md` rely on this; templates/ and
400
- * rules/ are handled by their own branches before this fallback runs. */
401
- function segmentMatchesComponentKind(segments) {
402
- const dirSegments = segments.slice(0, -1);
403
- return dirSegments.some((segment) => /\bcomponents?\b/.test(segment.toLowerCase()));
404
- }
405
244
  function classifyOpenspecPaths(paths) {
406
245
  const classified = emptyClassified();
407
246
  const ruleStem = (basename) => {
@@ -421,7 +260,7 @@ function classifyOpenspecPaths(paths) {
421
260
  return "other";
422
261
  };
423
262
  for (const candidate of paths) {
424
- if (!OPENSPEC_SPEC_EXT_RE.test(candidate))
263
+ if (!isOpenspecSpecFilePath(candidate))
425
264
  continue;
426
265
  const segments = candidate.split("/");
427
266
  const basename = segments[segments.length - 1] ?? "";
@@ -440,17 +279,8 @@ function classifyOpenspecPaths(paths) {
440
279
  classified.theme.push(candidate);
441
280
  else if (/\bcomponents?\b/.test(lower))
442
281
  classified.component.push(candidate);
443
- else if (segmentMatchesComponentKind(segments)) {
444
- // Directory-organized component libraries (e.g.
445
- // ui/ucp-components-md/AdvancedSearch.md) carry the kind in the
446
- // parent directory name, not the file name. The parent-dir rule
447
- // is a fallback so such layouts still land in the component
448
- // bucket instead of being dropped into uiOther.
449
- classified.component.push(candidate);
450
- }
451
- else {
282
+ else
452
283
  classified.uiOther.push(candidate);
453
- }
454
284
  }
455
285
  else {
456
286
  classified.advisoryOther.push(candidate);
@@ -495,7 +325,6 @@ function toOpenspecDiscovery(discovery, designEvidence) {
495
325
  excludedGovernanceCount: discovery.excludedGovernanceCount,
496
326
  truncated: discovery.truncated,
497
327
  maxCandidates: discovery.maxCandidates,
498
- warnings: discovery.warnings,
499
328
  };
500
329
  }
501
330
  export function buildAdapterGuidance(capability) {
@@ -515,7 +344,7 @@ export function buildAdapterGuidance(capability) {
515
344
  lines.push("Vue adapter: prefer Composition API if project already uses it; reuse Pinia/Vue Router/Vue Query only when evidenced; Vitest + Vue Test Utils when present.");
516
345
  }
517
346
  else {
518
- lines.push("Unknown framework: use only the node-scoped generic frontend guidance; do not assume React/Vue/Next APIs.");
347
+ lines.push("Unknown framework: use generic frontend-implementation skill only; do not assume React/Vue/Next APIs.");
519
348
  }
520
349
  if (capability.a11y.status === "present") {
521
350
  lines.push(`A11y tools present: ${capability.a11y.tools.join(", ")}. Report static/component-level evidence only; Browser a11y remains not-run.`);
@@ -532,25 +361,13 @@ export function buildAdapterGuidance(capability) {
532
361
  * Discover frontend project capability from repo root.
533
362
  * Strong evidence: package.json direct deps + config/bootstrap files.
534
363
  */
535
- export async function discoverFrontendProjectCapability(repoRoot, options = {}) {
536
- const registry = await readFrontendSpecRegistry(repoRoot);
537
- const registryRoots = registry?.roots
538
- .filter((root) => !root.scope || root.scope.includes("frontend"))
539
- .map((root) => root.path) ?? [];
540
- const discoveryOptions = {
541
- ...options,
542
- specRoots: [...new Set([...OPENSPEC_SPEC_DIRS, ...(options.specRoots ?? []), ...registryRoots])],
543
- maxDepth: registry?.discovery?.maxDepth ?? options.maxDepth,
544
- maxFiles: registry?.discovery?.maxFiles ?? options.maxFiles,
545
- maxBytes: registry?.discovery?.maxBytes ?? options.maxBytes,
546
- timeoutMs: registry?.discovery?.timeoutMs ?? options.timeoutMs,
547
- };
364
+ export async function discoverFrontendProjectCapability(repoRoot) {
548
365
  const evidencePaths = [];
549
366
  const reasons = [];
550
367
  const pkgPath = path.join(repoRoot, "package.json");
551
368
  const pkgRaw = await readJson(pkgPath);
552
369
  if (!pkgRaw || typeof pkgRaw !== "object") {
553
- const discovery = await listOpenspecDiscovery(repoRoot, discoveryOptions);
370
+ const discovery = await listOpenspecDiscovery(repoRoot);
554
371
  const designEvidence = buildDesignEvidence(discovery.paths);
555
372
  const base = {
556
373
  schemaVersion: 1,
@@ -723,7 +540,7 @@ export async function discoverFrontendProjectCapability(repoRoot, options = {})
723
540
  router = router ?? "next-router";
724
541
  if (hasDep(deps, "vue-router"))
725
542
  router = "vue-router";
726
- const discovery = await listOpenspecDiscovery(repoRoot, discoveryOptions);
543
+ const discovery = await listOpenspecDiscovery(repoRoot);
727
544
  const designEvidence = buildDesignEvidence(discovery.paths);
728
545
  if (discovery.paths.length)
729
546
  evidencePaths.push(...discovery.paths.slice(0, 5));
@@ -72,7 +72,7 @@ export function getTaskPaths(repoRoot, taskId) {
72
72
  dagDraftPath: path.join(taskDir, 'dag.json'),
73
73
  };
74
74
  }
75
- export async function createTask(repoRoot, taskId, title, options) {
75
+ export async function createTask(repoRoot, taskId, title) {
76
76
  const validation = validateNewTaskId(taskId);
77
77
  for (const warning of validation.warnings) {
78
78
  console.warn(`[new-task] warning: ${warning}`);
@@ -97,10 +97,7 @@ export async function createTask(repoRoot, taskId, title, options) {
97
97
  taskId,
98
98
  title: title ?? taskId,
99
99
  sourceFiles: [],
100
- taskKind: options?.taskKind ?? 'standard',
101
- ...(options?.taskKindExplicit !== undefined
102
- ? { taskKindExplicit: options.taskKindExplicit }
103
- : {}),
100
+ taskKind: 'standard',
104
101
  referenceRepos: [],
105
102
  referenceDocs: [],
106
103
  allowedPaths: [],
@@ -17,10 +17,10 @@ function resolveTaskKind(flags, baseDraft, existing) {
17
17
  const parsed = taskKindSchema.safeParse(candidate);
18
18
  return parsed.success ? parsed.data : String(candidate);
19
19
  }
20
- function resolveTaskKindExplicit(flags, baseDraft, existing) {
20
+ function resolveTaskKindExplicit(flags, baseDraft) {
21
21
  if (flags.taskKindExplicit !== undefined)
22
22
  return flags.taskKindExplicit;
23
- return (baseDraft?.taskKindExplicit ?? existing?.taskKindExplicit ?? false);
23
+ return baseDraft?.taskKindExplicit ?? false;
24
24
  }
25
25
  /**
26
26
  * Resolve managed backend-test layout config.
@@ -122,7 +122,7 @@ export function buildPrepareDraft(input) {
122
122
  const { taskId, existingTaskConfig, baseDraft, facts, flags, references } = input;
123
123
  const title = resolveTitle(taskId, flags, facts, baseDraft, existingTaskConfig);
124
124
  const taskKind = resolveTaskKind(flags, baseDraft, existingTaskConfig);
125
- const taskKindExplicit = resolveTaskKindExplicit(flags, baseDraft, existingTaskConfig);
125
+ const taskKindExplicit = resolveTaskKindExplicit(flags, baseDraft);
126
126
  const featureId = trimNonEmpty(flags.featureId) ??
127
127
  trimNonEmpty(baseDraft?.featureId) ??
128
128
  trimNonEmpty(existingTaskConfig?.featureId) ??