frontend-harness 0.1.0 → 0.2.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 (68) hide show
  1. package/AGENTS.md +9 -5
  2. package/CLAUDE.md +9 -5
  3. package/README.md +56 -11
  4. package/dist/cli/index.js +33 -7
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/runtime/clean.js +6 -1
  7. package/dist/runtime/clean.js.map +1 -1
  8. package/dist/runtime/command-taxonomy.js +4 -1
  9. package/dist/runtime/command-taxonomy.js.map +1 -1
  10. package/dist/runtime/common/naming.d.ts +2 -0
  11. package/dist/runtime/common/naming.js +13 -0
  12. package/dist/runtime/common/naming.js.map +1 -0
  13. package/dist/runtime/common/parsing.d.ts +11 -0
  14. package/dist/runtime/common/parsing.js +30 -0
  15. package/dist/runtime/common/parsing.js.map +1 -0
  16. package/dist/runtime/context.js +3 -2
  17. package/dist/runtime/context.js.map +1 -1
  18. package/dist/runtime/knowledge.js +16 -12
  19. package/dist/runtime/knowledge.js.map +1 -1
  20. package/dist/runtime/plan/component-resolver.d.ts +8 -0
  21. package/dist/runtime/plan/component-resolver.js +344 -0
  22. package/dist/runtime/plan/component-resolver.js.map +1 -0
  23. package/dist/runtime/plan/guidance.d.ts +3 -0
  24. package/dist/runtime/plan/guidance.js +119 -0
  25. package/dist/runtime/plan/guidance.js.map +1 -0
  26. package/dist/runtime/plan/proposal.d.ts +2 -0
  27. package/dist/runtime/plan/proposal.js +251 -0
  28. package/dist/runtime/plan/proposal.js.map +1 -0
  29. package/dist/runtime/plan/workflow.d.ts +8 -0
  30. package/dist/runtime/plan/workflow.js +228 -0
  31. package/dist/runtime/plan/workflow.js.map +1 -0
  32. package/dist/runtime/plan.d.ts +4 -3
  33. package/dist/runtime/plan.js +123 -448
  34. package/dist/runtime/plan.js.map +1 -1
  35. package/dist/runtime/policy-provenance.js +30 -17
  36. package/dist/runtime/policy-provenance.js.map +1 -1
  37. package/dist/runtime/project-discovery.js +12 -3
  38. package/dist/runtime/project-discovery.js.map +1 -1
  39. package/dist/runtime/project-paths.js +3 -0
  40. package/dist/runtime/project-paths.js.map +1 -1
  41. package/dist/runtime/protocol-init.js +7 -4
  42. package/dist/runtime/protocol-init.js.map +1 -1
  43. package/dist/runtime/repair-decision.js +14 -27
  44. package/dist/runtime/repair-decision.js.map +1 -1
  45. package/dist/runtime/repair-packet.js +8 -1
  46. package/dist/runtime/repair-packet.js.map +1 -1
  47. package/dist/runtime/scaffold/vue-template.d.ts +7 -0
  48. package/dist/runtime/scaffold/vue-template.js +167 -0
  49. package/dist/runtime/scaffold/vue-template.js.map +1 -0
  50. package/dist/runtime/scaffold.d.ts +21 -0
  51. package/dist/runtime/scaffold.js +80 -0
  52. package/dist/runtime/scaffold.js.map +1 -0
  53. package/dist/runtime/skills.js +3 -3
  54. package/dist/runtime/skills.js.map +1 -1
  55. package/dist/runtime/state.js +32 -16
  56. package/dist/runtime/state.js.map +1 -1
  57. package/dist/runtime/units.js +36 -1
  58. package/dist/runtime/units.js.map +1 -1
  59. package/dist/runtime/verification-commands.js +8 -4
  60. package/dist/runtime/verification-commands.js.map +1 -1
  61. package/dist/runtime/verify.js +76 -17
  62. package/dist/runtime/verify.js.map +1 -1
  63. package/dist/schemas/types.d.ts +64 -6
  64. package/dist/schemas/validation.js +4 -0
  65. package/dist/schemas/validation.js.map +1 -1
  66. package/dist/storage/json.js +6 -1
  67. package/dist/storage/json.js.map +1 -1
  68. package/package.json +3 -4
@@ -1,23 +1,28 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
2
  import { harnessPath, relativeHarnessPath } from "../storage/paths.js";
4
3
  import { writeJson, writeText } from "../storage/json.js";
5
4
  import { updatePlannedState } from "./state.js";
6
- import { resolveComponentPlacement } from "./project-discovery.js";
7
5
  import { buildComponentGraph } from "./graph.js";
8
6
  import { resolveProjectRelativePath } from "./project-paths.js";
9
7
  import { buildPolicyProvenance } from "./policy-provenance.js";
8
+ import { inferPlanComponents, resolveComponentSource, resolvePlanComponents } from "./plan/component-resolver.js";
9
+ import { buildExecutionGuidance, renderGuidance } from "./plan/guidance.js";
10
+ import { readAgentPlanningProposal } from "./plan/proposal.js";
11
+ import { inferFrontendWorkflow, inferTaskIntent, verificationForWorkflow } from "./plan/workflow.js";
12
+ import { unitIdFromName } from "./common/naming.js";
10
13
  export function createPlan(projectRoot, input) {
11
14
  if (!input.taskText.trim()) {
12
15
  throw new Error("plan requires a task");
13
16
  }
14
17
  const normalizedInput = normalizePlanInput(projectRoot, input);
15
- const task = understandTask(projectRoot, normalizedInput);
16
- const components = inferComponents(projectRoot, task);
17
- const units = buildExecutionUnits(components, task);
18
+ const proposal = normalizedInput.agentProposal ? readAgentPlanningProposal(projectRoot, normalizedInput.agentProposal) : null;
19
+ const task = understandTask(projectRoot, normalizedInput, proposal);
20
+ const resolved = resolvePlan(projectRoot, task, proposal);
21
+ const components = resolved.components;
22
+ const units = buildExecutionUnits(components, task, proposal);
18
23
  const componentGraph = buildComponentGraph(components, units);
19
24
  const executionGuidance = buildExecutionGuidance();
20
- const guidance = renderGuidance(task, components, units, executionGuidance);
25
+ const guidance = renderGuidance(task, components, units, executionGuidance, resolved.meta);
21
26
  const output = {
22
27
  task,
23
28
  components,
@@ -25,7 +30,8 @@ export function createPlan(projectRoot, input) {
25
30
  executionGuidance,
26
31
  componentGraphPath: relativeHarnessPath("component-graph", "latest.json"),
27
32
  guidancePath: relativeHarnessPath("guidance", "latest.md"),
28
- statePath: relativeHarnessPath("state.json")
33
+ statePath: relativeHarnessPath("state.json"),
34
+ meta: resolved.meta
29
35
  };
30
36
  writeJson(harnessPath(projectRoot, "plans", "latest.json"), output);
31
37
  writeJson(harnessPath(projectRoot, "execution-units", "latest.json"), {
@@ -36,19 +42,20 @@ export function createPlan(projectRoot, input) {
36
42
  updatePlannedState(projectRoot, task, components, units);
37
43
  return output;
38
44
  }
39
- export function checkAgentPlanningDecision(projectRoot, file) {
45
+ export function checkAgentPlanningProposal(projectRoot, file) {
40
46
  if (!file) {
41
- throw new Error("decision check requires --file <agent-decision.json>");
47
+ throw new Error("proposal check requires --file <agent-proposal.json>");
42
48
  }
43
49
  const artifactPath = file;
44
50
  try {
45
- const relativePath = validateRequiredReferencedFile(projectRoot, file, "agent decision");
46
- const decision = readAgentPlanningDecision(projectRoot, relativePath);
51
+ const relativePath = validateRequiredReferencedFile(projectRoot, file, "agent proposal");
52
+ const proposal = readAgentPlanningProposal(projectRoot, relativePath);
53
+ validateProposalTargets(projectRoot, proposal);
47
54
  return {
48
55
  status: "passed",
49
56
  artifactPath: relativePath,
50
- contractVersion: decision.contractVersion ?? null,
51
- decision,
57
+ contractVersion: proposal.contractVersion ?? null,
58
+ proposal,
52
59
  errors: []
53
60
  };
54
61
  }
@@ -57,7 +64,7 @@ export function checkAgentPlanningDecision(projectRoot, file) {
57
64
  status: "failed",
58
65
  artifactPath,
59
66
  contractVersion: null,
60
- decision: null,
67
+ proposal: null,
61
68
  errors: [error instanceof Error ? error.message : String(error)]
62
69
  };
63
70
  }
@@ -67,7 +74,7 @@ function normalizePlanInput(projectRoot, input) {
67
74
  taskText: input.taskText,
68
75
  ...optionalString("prd", validateReferencedFile(projectRoot, input.prd, "PRD")),
69
76
  ...optionalString("ui", validateReferencedFile(projectRoot, input.ui, "UI")),
70
- ...optionalString("agentDecision", validateReferencedFile(projectRoot, input.agentDecision, "agent decision"))
77
+ ...optionalString("agentProposal", validateReferencedFile(projectRoot, input.agentProposal, "agent proposal"))
71
78
  };
72
79
  }
73
80
  function validateRequiredReferencedFile(projectRoot, file, label) {
@@ -83,14 +90,11 @@ function validateReferencedFile(projectRoot, file, label) {
83
90
  }
84
91
  return relativePath;
85
92
  }
86
- function understandTask(projectRoot, input) {
93
+ function understandTask(projectRoot, input, proposal) {
87
94
  const text = input.taskText.trim();
88
- const decision = input.agentDecision
89
- ? readAgentPlanningDecision(projectRoot, input.agentDecision)
90
- : null;
91
95
  const scope = text;
92
- const decisionConstraints = decision?.constraintHints ?? [];
93
- const frontendWorkflow = inferFrontendWorkflow(scope, input);
96
+ const proposalConstraints = proposal?.constraintHints ?? [];
97
+ const frontendWorkflow = inferFrontendWorkflow(scope, input, proposal);
94
98
  const intent = inferTaskIntent(scope, frontendWorkflow.kind);
95
99
  return {
96
100
  intent,
@@ -98,206 +102,118 @@ function understandTask(projectRoot, input) {
98
102
  constraints: [
99
103
  "Use the agent-invoked harness loop.",
100
104
  "CLI owns project facts and verification.",
101
- "System owns deterministic planning; agent planning input is limited to bounded hints.",
105
+ "System owns final planning decisions; agent proposals are bounded inputs resolved through policy.",
102
106
  ...frontendWorkflow.implementationConstraints,
103
- ...decisionConstraints
107
+ ...proposalConstraints
104
108
  ],
105
109
  frontendWorkflow,
106
110
  policyProvenance: buildPolicyProvenance(projectRoot, scope, frontendWorkflow.kind),
107
111
  inputs: {
108
112
  ...(input.prd ? { prd: input.prd } : {}),
109
113
  ...(input.ui ? { ui: input.ui } : {}),
110
- ...(input.agentDecision ? { agentDecision: input.agentDecision } : {})
114
+ ...(input.agentProposal ? { agentProposal: input.agentProposal } : {})
111
115
  },
112
- ...buildAgentPlanningMetadata(input.agentDecision, decision)
116
+ ...buildAgentPlanningMetadata(input.agentProposal, proposal)
113
117
  };
114
118
  }
115
- function inferTaskIntent(scope, workflowKind) {
116
- const normalized = scope.toLowerCase();
117
- if (workflowKind === "bug_fix" || /\b(fix|bug)\b|缺陷|修复|错误|报错/.test(normalized)) {
118
- return "fix_bug";
119
- }
120
- if (/\brefactor\b|重构/.test(normalized)) {
121
- return "refactor";
122
- }
123
- if (workflowKind === "frontend_test" || /\b(test|e2e|playwright|vitest)\b|测试|端到端|集成测试/.test(normalized)) {
124
- return "add_test";
125
- }
126
- return "implement_page";
127
- }
128
- function buildAgentPlanningMetadata(decisionPath, decision) {
129
- if (!decisionPath) {
119
+ function buildAgentPlanningMetadata(proposalPath, proposal) {
120
+ if (!proposalPath) {
130
121
  return {
131
122
  reasoningMode: "deterministic"
132
123
  };
133
124
  }
134
125
  return {
135
126
  reasoningMode: "agent_assisted",
136
- agentDecision: {
137
- summary: buildAgentDecisionSummary(decisionPath),
138
- artifactPath: decisionPath ?? null,
139
- contractVersion: decision?.contractVersion ?? null,
140
- intentSuggestion: decision?.intentSuggestion ?? null,
141
- ...(decision?.constraintHints ? { constraintHints: decision.constraintHints } : {}),
142
- ...(decision?.componentHints ? { componentHints: decision.componentHints } : {})
127
+ agentProposal: {
128
+ summary: buildAgentProposalSummary(proposalPath),
129
+ artifactPath: proposalPath ?? null,
130
+ contractVersion: proposal?.contractVersion ?? null,
131
+ intentSuggestion: proposal?.intentSuggestion ?? null,
132
+ ...(proposal?.constraintHints ? { constraintHints: proposal.constraintHints } : {}),
133
+ ...(proposal?.componentHints ? { componentHints: proposal.componentHints } : {}),
134
+ ...(proposal?.planningEvidence ? { planningEvidence: proposal.planningEvidence } : {})
143
135
  }
144
136
  };
145
137
  }
146
- function buildAgentDecisionSummary(decisionPath) {
147
- return decisionPath
148
- ? `Planning uses structured decision input from ${decisionPath}.`
149
- : "Planning uses structured decision input.";
150
- }
151
- function inferComponents(projectRoot, task) {
152
- const baseName = toPascalCase(task.scope) || "PlannedComponent";
153
- const placement = resolveComponentPlacement(projectRoot, task.scope);
154
- const componentKinds = inferComponentKinds(task.scope);
155
- return componentKinds.map((kind) => {
156
- const name = componentName(baseName, kind.suffix);
157
- return {
158
- name,
159
- file: `${placement.sourceRoot}/${name}${placement.extension}`,
160
- responsibility: task.intent === "fix_bug" ? "bug fix target" : kind.responsibility,
161
- status: "unknown"
162
- };
163
- });
164
- }
165
- function inferComponentKinds(scope) {
166
- const normalized = scope.toLowerCase();
167
- const asksForTable = /\b(table|grid|list)\b/.test(normalized);
168
- const asksForForm = /\b(form|create|edit|filter|search)\b/.test(normalized);
169
- const asksForPage = /\b(page|screen|dashboard|view)\b/.test(normalized);
170
- if (asksForPage && asksForTable && asksForForm) {
171
- return [
172
- {
173
- suffix: "Page",
174
- responsibility: "page composition and data-flow boundary"
175
- },
176
- {
177
- suffix: "Table",
178
- responsibility: "tabular list rendering"
138
+ function buildAgentProposalSummary(proposalPath) {
139
+ return proposalPath
140
+ ? `Planning resolves structured agent proposal input from ${proposalPath}.`
141
+ : "Planning resolves structured agent proposal input.";
142
+ }
143
+ function resolvePlan(projectRoot, task, proposal) {
144
+ const evidenceTargets = validateProposalTargets(projectRoot, proposal);
145
+ const systemComponents = inferPlanComponents(projectRoot, task, evidenceTargets);
146
+ const warnings = [];
147
+ const resolvedComponents = resolvePlanComponents(projectRoot, systemComponents, proposal?.componentHints, task, warnings);
148
+ const components = resolvedComponents.components;
149
+ const evidenceWorkflowAccepted = Boolean(proposal?.planningEvidence?.workflowCandidates?.some((candidate) => {
150
+ return candidate.kind === task.frontendWorkflow?.kind && candidate.confidence >= 0.75;
151
+ }));
152
+ const componentSource = resolveComponentSource(resolvedComponents.trace);
153
+ return {
154
+ components,
155
+ meta: {
156
+ decisionTrace: {
157
+ workflow: {
158
+ source: evidenceWorkflowAccepted ? "agent" : "system",
159
+ reason: evidenceWorkflowAccepted
160
+ ? `agent planningEvidence workflow accepted: ${task.frontendWorkflow?.kind}`
161
+ : `system classified workflow as ${task.frontendWorkflow?.kind ?? "unknown"}`
162
+ },
163
+ targets: {
164
+ source: evidenceTargets.length ? "merged" : "system",
165
+ reason: evidenceTargets.length
166
+ ? "safe agent planningEvidence targetCandidates were merged into planning boundaries"
167
+ : "system inferred planning boundary"
168
+ },
169
+ components: {
170
+ source: componentSource,
171
+ reason: componentSource === "system"
172
+ ? "system component boundaries retained"
173
+ : componentSource === "agent"
174
+ ? "agent componentHints expanded the UI boundary"
175
+ : "agent componentHints merged into system UI boundary"
176
+ },
177
+ componentHints: resolvedComponents.trace,
178
+ ...(proposal?.planningEvidence
179
+ ? {
180
+ agentEvidence: {
181
+ source: "merged",
182
+ reason: describePlanningEvidence(proposal.planningEvidence)
183
+ }
184
+ }
185
+ : {})
179
186
  },
180
- {
181
- suffix: "Form",
182
- responsibility: "form or filter interaction"
183
- }
184
- ];
185
- }
186
- return [
187
- {
188
- suffix: "",
189
- responsibility: "planned UI/component implementation"
190
- }
191
- ];
192
- }
193
- function inferFrontendWorkflow(scope, input) {
194
- const normalized = scope.toLowerCase();
195
- const hasUiSource = Boolean(input.ui) || /\b(ui|stitch|figma|html|design|page|screen|component|view|dashboard)\b|页面|界面|组件|视图|看板|设计稿/.test(normalized);
196
- const hasPrdSource = Boolean(input.prd) || /\b(prd|product|requirement|requirements|knowledge|document)\b|产品|需求|知识库|文档/.test(normalized);
197
- const hasBugSignal = /\b(bug|fix|regression|error)\b|缺陷|修复|错误|报错/.test(normalized);
198
- const hasTestSignal = /\b(e2e|integration test|test|playwright|vitest)\b|测试|端到端|集成测试/.test(normalized);
199
- const hasApiSignal = /\b(api|swagger|endpoint|interface|request|dto)\b|接口|请求|对接|联调/.test(normalized);
200
- const hasRequirementChange = /\b(requirement changes?|changed [a-z0-9 -]*requirements?|update [a-z0-9 -]*requirements?|sync [a-z0-9 -]*requirements?)\b|需求变更|变更需求|需求[^,。,.]*同步|同步[^,。,.]*需求/.test(normalized);
201
- const kind = hasBugSignal
202
- ? "bug_fix"
203
- : hasRequirementChange
204
- ? "requirement_change"
205
- : hasTestSignal
206
- ? "frontend_test"
207
- : hasApiSignal
208
- ? "api_integration"
209
- : hasPrdSource && !hasUiSource
210
- ? "prd_knowledge"
211
- : "ui_implementation";
212
- return workflowPolicy(kind, { hasPrdSource, hasUiSource });
213
- }
214
- function workflowPolicy(kind, inputSignals) {
215
- const policies = {
216
- ui_implementation: {
217
- kind,
218
- summary: "Implement or adapt frontend UI while preserving project semantics, component boundaries, and validation evidence.",
219
- requiredKnowledgeActions: inputSignals.hasUiSource
220
- ? ["Capture reusable UI semantics, component responsibilities, and design assumptions in project knowledge when they affect future work."]
221
- : ["Reuse existing project knowledge before inventing new UI semantics."],
222
- implementationConstraints: [
223
- "Treat imported MCP/Stitch/Figma/HTML output as source material, not project-ready code.",
224
- "Normalize generated UI into project components, naming, state, routing, and styling conventions."
225
- ],
226
- verificationFocus: ["typecheck", "component behavior", "responsive rendering", "visual regressions when UI source is provided"]
227
- },
228
- prd_knowledge: {
229
- kind,
230
- summary: "Convert PRD or requirement material into durable project knowledge before implementation.",
231
- requiredKnowledgeActions: [
232
- "Promote stable product concepts, terminology, permissions, workflows, and acceptance rules into .frontend-harness/knowledge/.",
233
- "Keep raw PRD text separate from distilled project knowledge."
234
- ],
235
- implementationConstraints: [
236
- "Do not implement product behavior until PRD-derived assumptions are explicit.",
237
- "Mark unknown product decisions as blockers or assumptions instead of guessing."
238
- ],
239
- verificationFocus: ["knowledge coverage", "acceptance criteria traceability", "terminology consistency"]
240
- },
241
- api_integration: {
242
- kind,
243
- summary: "Integrate frontend behavior with API contracts using project knowledge plus Swagger/API evidence.",
244
- requiredKnowledgeActions: [
245
- "Reference relevant domain knowledge before mapping request parameters, DTOs, and response states.",
246
- "Promote durable API assumptions when Swagger or backend behavior clarifies product rules."
247
- ],
248
- implementationConstraints: [
249
- "Do not invent API fields, enum values, pagination contracts, or error shapes.",
250
- "Keep request mapping, loading, empty, and error states inside the project API/state conventions."
251
- ],
252
- verificationFocus: ["typecheck", "API mapping", "loading/empty/error states", "integration tests or mocks"]
253
- },
254
- frontend_test: {
255
- kind,
256
- summary: "Add or update frontend E2E/integration coverage for user-visible behavior.",
257
- requiredKnowledgeActions: [
258
- "Use project knowledge to choose behavior-level assertions instead of implementation-detail assertions.",
259
- "Promote durable testing conventions only when they apply beyond the current task."
260
- ],
261
- implementationConstraints: [
262
- "Prefer behavior-focused integration or E2E tests over brittle snapshot-only checks.",
263
- "Keep fixtures and mocks aligned with known API/product contracts."
264
- ],
265
- verificationFocus: ["targeted test", "typecheck", "critical user path", "failure diagnostics"]
266
- },
267
- bug_fix: {
268
- kind,
269
- summary: "Fix a frontend bug with evidence, minimal scope, and regression coverage proportional to risk.",
270
- requiredKnowledgeActions: [
271
- "Check project knowledge for intended behavior before changing code.",
272
- "Promote the resolved product or technical rule if the bug revealed durable knowledge."
273
- ],
274
- implementationConstraints: [
275
- "Reproduce or explain the bug before patching when evidence is available.",
276
- "Keep the fix scoped to the failing behavior and avoid unrelated refactors."
277
- ],
278
- verificationFocus: ["regression test", "typecheck", "affected workflow", "repair evidence"]
279
- },
280
- requirement_change: {
281
- kind,
282
- summary: "Apply requirement changes and keep implementation, tests, and knowledge synchronized.",
283
- requiredKnowledgeActions: [
284
- "Update project knowledge for changed product rules before or alongside code changes.",
285
- "Record obsolete assumptions so future agents do not reuse stale behavior."
286
- ],
287
- implementationConstraints: [
288
- "Identify impacted UI, API mapping, tests, and knowledge before editing.",
289
- "Do not leave previous requirement behavior undocumented when it changes."
290
- ],
291
- verificationFocus: ["changed acceptance criteria", "knowledge sync", "affected regression tests", "typecheck"]
187
+ warnings
292
188
  }
293
189
  };
294
- return policies[kind];
295
190
  }
296
- function componentName(baseName, suffix) {
297
- return suffix && !baseName.endsWith(suffix) ? `${baseName}${suffix}` : baseName;
298
- }
299
- function buildExecutionUnits(components, task) {
300
- const units = components.map((component) => toUnit(component, task));
191
+ function validateProposalTargets(projectRoot, proposal) {
192
+ const evidenceTargets = (proposal?.planningEvidence?.targetCandidates ?? [])
193
+ .map((candidate, index) => ({
194
+ path: candidate.path,
195
+ confidence: candidate.confidence,
196
+ label: `agent proposal planningEvidence.targetCandidates[${index}].path`
197
+ }));
198
+ const safeTargets = evidenceTargets.map((target) => ({
199
+ path: resolveProjectRelativePath(projectRoot, target.path, target.label).relativePath,
200
+ confidence: target.confidence
201
+ }));
202
+ return [...new Set(safeTargets
203
+ .filter((target) => target.confidence >= 0.7)
204
+ .map((target) => target.path))];
205
+ }
206
+ function describePlanningEvidence(evidence) {
207
+ return [
208
+ `${evidence.workflowCandidates?.length ?? 0} workflow candidate(s)`,
209
+ `${evidence.targetCandidates?.length ?? 0} target candidate(s)`,
210
+ `${evidence.verificationCandidates?.length ?? 0} verification candidate(s)`,
211
+ `${evidence.ambiguities?.length ?? 0} ambiguity item(s)`
212
+ ].join(", ");
213
+ }
214
+ function buildExecutionUnits(components, task, proposal) {
215
+ const verification = resolveVerificationFocus(task, proposal);
216
+ const units = components.map((component) => toUnit(component, task, verification));
301
217
  const pageUnit = units.find((unit) => unit.id.endsWith("-page"));
302
218
  if (pageUnit && units.length > 1) {
303
219
  pageUnit.dependsOn = units
@@ -306,266 +222,25 @@ function buildExecutionUnits(components, task) {
306
222
  }
307
223
  return units;
308
224
  }
309
- function toUnit(component, task) {
225
+ function toUnit(component, task, verification) {
310
226
  return {
311
227
  id: unitIdFromName(component.name),
312
228
  file: component.file,
313
229
  task: `${task.intent}: ${task.scope}`,
314
- verification: verificationForWorkflow(task.frontendWorkflow?.kind),
230
+ verification,
315
231
  dependsOn: [],
316
232
  parallelGroup: null
317
233
  };
318
234
  }
319
- function verificationForWorkflow(kind) {
320
- if (kind === "frontend_test") {
321
- return ["targeted test", "typecheck"];
322
- }
323
- if (kind === "bug_fix") {
324
- return ["regression test", "typecheck"];
325
- }
326
- if (kind === "api_integration") {
327
- return ["typecheck", "integration test or API mock"];
328
- }
329
- if (kind === "requirement_change") {
330
- return ["typecheck", "affected regression test"];
331
- }
332
- if (kind === "prd_knowledge") {
333
- return ["knowledge review"];
334
- }
335
- return ["typecheck"];
336
- }
337
- function readAgentPlanningDecision(projectRoot, file) {
338
- const fullPath = path.resolve(projectRoot, file);
339
- let parsed;
340
- try {
341
- parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
342
- }
343
- catch (error) {
344
- const detail = error instanceof Error ? error.message : String(error);
345
- throw new Error(`agent decision file must contain valid JSON: ${file}: ${detail}`);
346
- }
347
- return parseAgentPlanningDecision(parsed, file);
348
- }
349
- function parseAgentPlanningDecision(value, file) {
350
- if (!isRecord(value) || Array.isArray(value)) {
351
- throw new Error(`agent decision file must contain a JSON object: ${file}`);
352
- }
353
- if ("decision" in value || "contractVersion" in value) {
354
- return parseAgentPlanningDecisionEnvelope(value, file);
355
- }
356
- const allowedKeys = new Set(["intentSuggestion", "constraintHints", "componentHints"]);
357
- return parseAgentPlanningDecisionObject(value, allowedKeys, "agent decision file");
358
- }
359
- function parseAgentPlanningDecisionEnvelope(value, file) {
360
- const allowedKeys = new Set(["contractVersion", "decision"]);
361
- for (const key of Object.keys(value)) {
362
- if (!allowedKeys.has(key)) {
363
- throw new Error(`agent decision file contains unsupported field: ${key}`);
364
- }
365
- }
366
- if (value["contractVersion"] !== 1) {
367
- throw new Error("agent decision contractVersion must be 1");
368
- }
369
- if (!isRecord(value["decision"]) || Array.isArray(value["decision"])) {
370
- throw new Error(`agent decision file decision must be a JSON object: ${file}`);
371
- }
372
- const decision = parseAgentPlanningDecisionObject(value["decision"], new Set(["intentSuggestion", "constraintHints", "componentHints"]), "agent decision file decision");
373
- return {
374
- contractVersion: 1,
375
- ...decision
376
- };
377
- }
378
- function parseAgentPlanningDecisionObject(value, allowedKeys, label) {
379
- for (const key of Object.keys(value)) {
380
- if (!allowedKeys.has(key)) {
381
- throw new Error(`${label} contains unsupported field: ${key}`);
382
- }
383
- }
384
- const decision = {};
385
- if (value["intentSuggestion"] !== undefined) {
386
- decision.intentSuggestion = parseBoundedString(value["intentSuggestion"], "intentSuggestion");
387
- }
388
- if (value["constraintHints"] !== undefined) {
389
- decision.constraintHints = parseStringArray(value["constraintHints"], "constraintHints");
390
- }
391
- if (value["componentHints"] !== undefined) {
392
- decision.componentHints = parseComponentHints(value["componentHints"]);
393
- }
394
- return decision;
395
- }
396
- function parseStringArray(value, label) {
397
- if (!Array.isArray(value)) {
398
- throw new Error(`agent decision ${label} must be an array of strings`);
399
- }
400
- if (value.length > 10) {
401
- throw new Error(`agent decision ${label} cannot contain more than 10 items`);
402
- }
403
- return value.map((item, index) => parseBoundedString(item, `${label}[${index}]`));
404
- }
405
- function parseComponentHints(value) {
406
- if (!Array.isArray(value)) {
407
- throw new Error("agent decision componentHints must be an array");
408
- }
409
- if (value.length > 10) {
410
- throw new Error("agent decision componentHints cannot contain more than 10 items");
411
- }
412
- return value.map((item, index) => {
413
- if (!isRecord(item) || Array.isArray(item)) {
414
- throw new Error(`agent decision componentHints[${index}] must be an object`);
415
- }
416
- const allowedKeys = new Set(["name", "responsibility"]);
417
- for (const key of Object.keys(item)) {
418
- if (!allowedKeys.has(key)) {
419
- throw new Error(`agent decision componentHints[${index}] contains unsupported field: ${key}`);
420
- }
421
- }
422
- return {
423
- ...(item["name"] !== undefined ? { name: parseBoundedString(item["name"], `componentHints[${index}].name`) } : {}),
424
- ...(item["responsibility"] !== undefined
425
- ? { responsibility: parseBoundedString(item["responsibility"], `componentHints[${index}].responsibility`) }
426
- : {})
427
- };
428
- });
235
+ function resolveVerificationFocus(task, proposal) {
236
+ const base = verificationForWorkflow(task.frontendWorkflow?.kind);
237
+ const evidence = (proposal?.planningEvidence?.verificationCandidates ?? [])
238
+ .map((candidate) => candidate.kind.trim())
239
+ .filter(Boolean)
240
+ .slice(0, 10);
241
+ return [...new Set([...base, ...evidence])];
429
242
  }
430
243
  function optionalString(key, value) {
431
244
  return value === undefined ? {} : { [key]: value };
432
245
  }
433
- function parseBoundedString(value, label) {
434
- if (typeof value !== "string") {
435
- throw new Error(`agent decision ${label} must be a string`);
436
- }
437
- const trimmed = value.trim();
438
- if (!trimmed) {
439
- throw new Error(`agent decision ${label} cannot be empty`);
440
- }
441
- if (trimmed.length > 500) {
442
- throw new Error(`agent decision ${label} cannot exceed 500 characters`);
443
- }
444
- return trimmed;
445
- }
446
- function isRecord(value) {
447
- return typeof value === "object" && value !== null;
448
- }
449
- function unitIdFromName(name) {
450
- return `unit-${name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}`;
451
- }
452
- function buildExecutionGuidance() {
453
- return [
454
- {
455
- order: 1,
456
- action: "inspect_plan",
457
- description: `Read ${relativeHarnessPath("plans", "latest.json")} and ${relativeHarnessPath("execution-units", "latest.json")} before editing.`,
458
- command: null,
459
- condition: null
460
- },
461
- {
462
- order: 2,
463
- action: "edit_files",
464
- description: "Edit only the project files required by the planned execution units.",
465
- command: null,
466
- condition: null
467
- },
468
- {
469
- order: 3,
470
- action: "record_changes",
471
- description: "Record each changed project file so the harness state reflects the execution boundary.",
472
- command: "frontend-harness state record-change <file>",
473
- condition: "after editing"
474
- },
475
- {
476
- order: 4,
477
- action: "verify",
478
- description: "Run the configured verification commands and inspect the JSON result.",
479
- command: "frontend-harness verify --json",
480
- condition: "after recording changes"
481
- },
482
- {
483
- order: 5,
484
- action: "fix_failed_verification",
485
- description: `If verification fails, generate and inspect ${relativeHarnessPath("repair", "latest.json")} before patching the relevant project files, recording new changes, and rerunning verification.`,
486
- command: "frontend-harness repair packet --json",
487
- condition: "when verification status is failed or error"
488
- }
489
- ];
490
- }
491
- function renderGuidance(task, components, units, executionGuidance) {
492
- return `# Agent Execution Guidance
493
-
494
- ## Task
495
-
496
- - Intent: ${task.intent}
497
- - Scope: ${task.scope}
498
-
499
- ## Frontend Workflow Policy
500
-
501
- - Kind: ${task.frontendWorkflow?.kind ?? "unknown"}
502
- - Summary: ${task.frontendWorkflow?.summary ?? "No workflow policy inferred."}
503
- - Knowledge: ${(task.frontendWorkflow?.requiredKnowledgeActions ?? []).join(" ")}
504
- - Verification focus: ${(task.frontendWorkflow?.verificationFocus ?? []).join(", ")}
505
-
506
- ## Project References
507
-
508
- ${renderProjectReferences(task)}
509
-
510
- ## Loop
511
-
512
- ${executionGuidance.map(renderExecutionGuidanceStep).join("\n")}
513
-
514
- ## Components
515
-
516
- ${components.map((component) => `- ${component.name}: ${component.file} (${component.responsibility})`).join("\n")}
517
-
518
- ## Units
519
-
520
- ${units.map(renderUnitGuidance).join("\n")}
521
- `;
522
- }
523
- function renderProjectReferences(task) {
524
- const provenance = task.policyProvenance;
525
- if (!provenance) {
526
- return "- No project-local skills or knowledge selected.";
527
- }
528
- const lines = [];
529
- if (provenance.skillRoutes.length) {
530
- lines.push("Selected skills:");
531
- for (const route of provenance.skillRoutes) {
532
- lines.push(`- ${route.name}: ${route.path}; source: ${route.source}; score: ${route.score}; reasons: ${route.reasons.join(", ")}`);
533
- }
534
- }
535
- else {
536
- lines.push("Selected skills: none.");
537
- }
538
- if (provenance.knowledgeReferences.length) {
539
- lines.push("Selected knowledge:");
540
- for (const knowledgePath of provenance.knowledgeReferences) {
541
- const sha256 = provenance.knowledgeSha256[knowledgePath];
542
- lines.push(`- ${knowledgePath}${sha256 ? `; sha256: ${sha256}` : ""}`);
543
- }
544
- }
545
- else {
546
- lines.push("Selected knowledge: none.");
547
- }
548
- lines.push(`Selection reason: ${provenance.selectionReason}`);
549
- return lines.join("\n");
550
- }
551
- function renderExecutionGuidanceStep(step) {
552
- const command = step.command ? ` Command: \`${step.command}\`.` : "";
553
- const condition = step.condition ? ` Condition: ${step.condition}.` : "";
554
- return `${step.order}. ${step.description}${command}${condition}`;
555
- }
556
- function renderUnitGuidance(unit) {
557
- const dependencies = JSON.stringify(unit.dependsOn);
558
- const parallelGroup = JSON.stringify(unit.parallelGroup);
559
- const independent = unit.dependsOn.length === 0;
560
- return `- ${unit.id}: ${unit.task}; file ${unit.file}; independent: ${independent}; dependsOn: ${dependencies}; parallelGroup: ${parallelGroup}; verification focus: ${unit.verification.join(", ")}`;
561
- }
562
- function toPascalCase(value) {
563
- return value
564
- .replace(/[^a-zA-Z0-9]+/g, " ")
565
- .trim()
566
- .split(/\s+/)
567
- .slice(0, 4)
568
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
569
- .join("");
570
- }
571
246
  //# sourceMappingURL=plan.js.map