@tea-agent/loop-agent 0.38.0 → 0.39.0-next.10
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.
- package/CHANGELOG.md +79 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init-upgrade.js +97 -19
- package/dist/commands/init.js +14 -2
- package/dist/executors/dag-pi-executor.js +1 -0
- package/dist/governance/manifest-types.js +2 -0
- package/dist/infrastructure/harness/atomic-write.js +2 -1
- package/dist/shared/openspec-spec.js +76 -0
- package/dist/task/config-types.js +21 -0
- package/dist/task/frontend-project-capability.js +292 -38
- package/dist/worker/console/chat/turn-process.js +7 -20
- package/dist/worker/console/harness-pi-model-matrix.js +146 -0
- package/dist/worker/console/operator-actions.js +49 -0
- package/dist/worker/console/operator-user-error.js +19 -0
- package/dist/worker/console/pi-readiness.js +7 -1
- package/dist/worker/console/security.js +20 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DSvF6RXQ.js → abnfDiagram-N423BO3Z-B1E-cK2K.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DuI4ogtJ.js → arc-BGzjO9rd.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DdBbgD4y.js → architectureDiagram-T3A2C74G-DLdhmjAc.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-xQC2HELm.js → blockDiagram-VBNYF7ZC-MYV8D865.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DPMSgwIZ.js → c4Diagram-5PPSVZJV-BzDorEwd.js} +1 -1
- package/dist/worker/console/static/assets/channel-jVvdIfCV.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-WTEKTxin.js → chunk-2GRJ4B5K-C1X8DD9m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B6vZ0Pv_.js → chunk-2Q5K7J3B-_b3zpUSR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BrerNAix.js → chunk-5RXB4S5H-B1nq6qNo.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-D_ch2aaI.js → chunk-5VM5RSS4-BHy2jQUr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-VvfacNrq.js → chunk-6Q2QTUOP-BDoLBPGY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-B-uGFIg2.js → chunk-GF5L2VYU-SDhf5BHa.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BlysCtod.js → chunk-JWPE2WC7-B2X9WLQ5.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BjAUAfcT.js → chunk-KBJHAD2P-Y1pEsL6r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BeZnBmb-.js → chunk-RYQCIY6F-BhlwtGnw.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bbfe3UJG.js → chunk-XXDRQBXY-lFslfIU9.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-VqdMg0Mg.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-VqdMg0Mg.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BKJQnYVM.js → cose-bilkent-JH36ORCC-ms_QSyzO.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-B1wtI3hU.js → cynefin-VYW2F7L2-DVBoJPNo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-DCF6DBSu.js → cynefinDiagram-MW4NZA55-ClesFZ8l.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJyNGRlP.js → dagre-VZM6K2ZE-CwcFFdXS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B7j1-VsZ.js → diagram-7IWD3JNH-CK6fAQka.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BREJ140a.js → diagram-B4RE2ZJO-CxBy8J_n.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CDPHmHGH.js → diagram-LBJQPF4R-Cn6E66dO.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtZLrez1.js → diagram-Q27KOJAE-Cep_FINW.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-mzybu8oU.js → diagram-UB23O5K3-gzv9ZY8Y.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Dk3I2uO6.js → ebnfDiagram-BXEA7PRR-CZVlOlaj.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BT97u_tQ.js → erDiagram-JOGREHBK-EGJvKAH5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CMOtYzui.js → flowDiagram-UKHOOZJN-DhfPgO79.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DiNydkuY.js → ganttDiagram-PKOTCBZU-BzJfLMVN.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js → gitGraphDiagram-DS77QQ5N-C5uHg3cm.js} +1 -1
- package/dist/worker/console/static/assets/{index-DQxiso1w.js → index-0DWC8Vk9.js} +67 -67
- package/dist/worker/console/static/assets/index-DAouGklf.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-AsGIGKKB.js → infoDiagram-6WML65LV-BFEs1rSc.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BL8ITZh3.js → ishikawaDiagram-WSZJBQD7-DOY3aNcc.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-VPKAUX8M.js → journeyDiagram-NVQOT4AX-CvEVBETJ.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BL56hfGp.js → kanban-definition-27J2QSJJ-CvRuqo46.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BmSG3nIZ.js → linear-B8zO1HYx.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DWGK9kwP.js → mermaid.core-FLK4IOPu.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS--vvKLjo0.js → mindmap-definition-FAOFIHXS-CwQ-RWrj.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-B41mxb2-.js → pegDiagram-VL7TDLO6-H_S6wWna.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DTlu77sw.js → pieDiagram-7S7Q4E2Y-CZUOcDQQ.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Bk5A7alv.js → quadrantDiagram-CIZ2JOQS-CeaPaSv9.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Csf9jzQy.js → railroadDiagram-AXF67PYL-Cpcs9ua-.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CeptoSNx.js → requirementDiagram-LRYGKXZP-D0_Aeyk6.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-A-DLCLer.js → sankeyDiagram-W5VNT64P-73kWjUq_.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-22VbPdOI.js → sequenceDiagram-SI44F4Z6-41K29Ot4.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DWvAyBC9.js → sizeCapture-X5ZJPWSS-D1nO0oh9.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-qICZ75yT.js → stateDiagram-OKZ733FA-C77XHgDB.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B9GimbZV.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-xi7lcMyp.js → swimlanes-SLNWSIFB-DC0D7jEO.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-yxfxvdqm.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Bu6Nl6O2.js → timeline-definition-Z64GVDOM-BLDHd_Uf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BzMz4Bn9.js → vennDiagram-T6HMQDX7-eh6Y_q96.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CxaJnzc5.js → wardleyDiagram-T6FBY63Y-Cj4Cy9tr.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-5JK-omQK.js → xychartDiagram-ELKLHX3M-DUIjXY1m.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +51 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +14 -2
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +15 -0
- package/dist/workflows/dag/failure-routing.js +9 -4
- package/dist/workflows/dag/frontend-implementation-contract.js +118 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +411 -20
- package/dist/workflows/dag/init-hybrid.js +271 -26
- package/dist/workflows/dag/lifecycle.js +4 -0
- package/dist/workflows/dag/node-execution.js +37 -7
- package/dist/workflows/dag/report.js +6 -0
- package/dist/workflows/dag/retry-policy.js +15 -0
- package/dist/workflows/dag/types.js +49 -4
- package/dist/workflows/dag/validate.js +7 -2
- package/docs/templates/frontend-design-contract.md +40 -14
- package/docs/templates/frontend-implementation-contract.schema.json +9 -2
- package/docs/templates/frontend-task-constraints.md +40 -13
- package/docs/templates/frontend-task-requirement.md +54 -20
- package/docs/templates/harness.schema.json +8 -0
- package/harness.json +11 -2
- package/package.json +1 -1
- package/skills/frontend-design-review/references/review-checklist.md +2 -0
- package/skills/frontend-implementation/references/design-spec.md +16 -19
- package/skills/frontend-implementation/references/node-contracts.md +15 -11
- package/dist/worker/console/static/assets/channel-DLVz9TYW.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +0 -1
- package/dist/worker/console/static/assets/index-_j0Hzo69.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +0 -8
|
@@ -191,6 +191,42 @@ const gap = z
|
|
|
191
191
|
blocking: z.boolean(),
|
|
192
192
|
})
|
|
193
193
|
.strict();
|
|
194
|
+
/**
|
|
195
|
+
* Structured component/token selection contract (ADR 0016). Every user-visible
|
|
196
|
+
* UI purpose the plan touches must be declared here so the prewrite gate can
|
|
197
|
+
* deterministically cross-check that the selection comes from the frontend
|
|
198
|
+
* specification (openspec/ai_workspace component + theme specs) instead of
|
|
199
|
+
* relying on design-review's soft model judgment.
|
|
200
|
+
*
|
|
201
|
+
* - `specified`: the frontend spec mandates this component for the purpose →
|
|
202
|
+
* `specReference` is required (path/section/line hit).
|
|
203
|
+
* - `reuse-existing`: reuse an existing repo component/convention not named by
|
|
204
|
+
* the spec → `specReference` may be null, rationale explains the basis.
|
|
205
|
+
* - `new`: neither spec nor existing code fits → `specReference` must be null,
|
|
206
|
+
* rationale must declare the deviation (design-review approves it).
|
|
207
|
+
*
|
|
208
|
+
* `specReference` is nullable + optional because `stripNullValuesDeep` strips
|
|
209
|
+
* null object values before validation, so a `new` choice's `specReference:
|
|
210
|
+
* null` becomes an absent field; the superRefine below treats absent and null
|
|
211
|
+
* identically.
|
|
212
|
+
*/
|
|
213
|
+
const uiComponentSpecReferenceSchema = z
|
|
214
|
+
.object({
|
|
215
|
+
path: safePath,
|
|
216
|
+
section: z.string(),
|
|
217
|
+
line: z.number().int().nullable().optional(),
|
|
218
|
+
})
|
|
219
|
+
.strict();
|
|
220
|
+
const uiComponentChoiceSchema = z
|
|
221
|
+
.object({
|
|
222
|
+
purpose: z.string().min(1),
|
|
223
|
+
component: z.string().min(1),
|
|
224
|
+
decision: z.enum(["specified", "reuse-existing", "new"]),
|
|
225
|
+
specReference: uiComponentSpecReferenceSchema.nullable().optional(),
|
|
226
|
+
rationale: z.string().min(1),
|
|
227
|
+
})
|
|
228
|
+
.strict();
|
|
229
|
+
const uiComponentChoicesSchema = z.array(uiComponentChoiceSchema);
|
|
194
230
|
/**
|
|
195
231
|
* Normalize unstable model emissions before the strict schema sees them:
|
|
196
232
|
* recursively drop null values (object entries and array elements). A null for
|
|
@@ -316,6 +352,12 @@ export const frontendImplementationContractSchema = z
|
|
|
316
352
|
})
|
|
317
353
|
.strict()).min(1),
|
|
318
354
|
evidenceGaps: z.array(gap),
|
|
355
|
+
implementationSteps: z.array(z.string().min(1)).optional(),
|
|
356
|
+
stylingStrategy: z.string().min(1).optional(),
|
|
357
|
+
uiComponentChoices: uiComponentChoicesSchema.optional(),
|
|
358
|
+
dependencyPolicy: z.string().min(1).optional(),
|
|
359
|
+
residualRisks: z.array(z.string().min(1)).optional(),
|
|
360
|
+
realIntegrationGap: z.string().min(1).optional(),
|
|
319
361
|
})
|
|
320
362
|
.strict()
|
|
321
363
|
.superRefine((value, ctx) => {
|
|
@@ -391,6 +433,26 @@ export const frontendImplementationContractSchema = z
|
|
|
391
433
|
path: ["uiStates"],
|
|
392
434
|
});
|
|
393
435
|
}
|
|
436
|
+
for (const [index, choice] of (value.uiComponentChoices ?? []).entries()) {
|
|
437
|
+
if (choice.decision === "specified") {
|
|
438
|
+
if (!choice.specReference || !choice.specReference.path) {
|
|
439
|
+
ctx.addIssue({
|
|
440
|
+
code: "custom",
|
|
441
|
+
message: "specified component choice requires a non-empty specReference.path",
|
|
442
|
+
path: ["uiComponentChoices", index, "specReference"],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
else if (choice.decision === "new") {
|
|
447
|
+
if (choice.specReference) {
|
|
448
|
+
ctx.addIssue({
|
|
449
|
+
code: "custom",
|
|
450
|
+
message: "new component choice must not carry a specReference",
|
|
451
|
+
path: ["uiComponentChoices", index, "specReference"],
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
394
456
|
if (value.mockApi.strategy !== "not-needed") {
|
|
395
457
|
if (value.mockApi.endpoints.length === 0) {
|
|
396
458
|
ctx.addIssue({
|
|
@@ -426,6 +488,44 @@ export const frontendImplementationContractSchema = z
|
|
|
426
488
|
});
|
|
427
489
|
}
|
|
428
490
|
}));
|
|
491
|
+
function isPlainObject(value) {
|
|
492
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Apply an RFC 7386 merge-patch on a target contract without mutating it.
|
|
496
|
+
* `null` removes the key; plain objects merge recursively when the target value
|
|
497
|
+
* is also a plain object; arrays and scalars replace the whole value. The
|
|
498
|
+
* result is re-validated by analyzeFrontendImplementationContract before it is
|
|
499
|
+
* ever materialized, so a patch that removes a required field still fails
|
|
500
|
+
* closed at the gate.
|
|
501
|
+
*/
|
|
502
|
+
export function applyFrontendImplementationContractPatch(target, patch) {
|
|
503
|
+
if (!isPlainObject(patch)) {
|
|
504
|
+
throw new Error("frontend revision patch must be a JSON object");
|
|
505
|
+
}
|
|
506
|
+
const merge = (base, delta) => {
|
|
507
|
+
if (delta === null)
|
|
508
|
+
return undefined;
|
|
509
|
+
if (!isPlainObject(delta))
|
|
510
|
+
return delta;
|
|
511
|
+
const result = isPlainObject(base)
|
|
512
|
+
? { ...base }
|
|
513
|
+
: {};
|
|
514
|
+
for (const [key, value] of Object.entries(delta)) {
|
|
515
|
+
if (value === null) {
|
|
516
|
+
delete result[key];
|
|
517
|
+
}
|
|
518
|
+
else if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
519
|
+
result[key] = merge(result[key], value);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
result[key] = value;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return result;
|
|
526
|
+
};
|
|
527
|
+
return merge(target, patch);
|
|
528
|
+
}
|
|
429
529
|
export async function assertFrontendSourceBindingFresh(input) {
|
|
430
530
|
for (const source of input.binding.sources) {
|
|
431
531
|
const absolute = resolveDagTaskSourcePath({
|
|
@@ -648,6 +748,18 @@ function asStringArray(value) {
|
|
|
648
748
|
.map((item) => asString(item))
|
|
649
749
|
.filter((item) => item.length > 0);
|
|
650
750
|
}
|
|
751
|
+
/**
|
|
752
|
+
* Pass structured component choices through the free-form compatibility
|
|
753
|
+
* branch. The strict-shape branch already spreads unknown fields, so only this
|
|
754
|
+
* explicit constructor needs to re-emit `uiComponentChoices`; a missing/empty
|
|
755
|
+
* array normalizes to undefined (absent) so the optional schema field stays
|
|
756
|
+
* compatible with legacy free-form contracts.
|
|
757
|
+
*/
|
|
758
|
+
function normalizeUiComponentChoices(value) {
|
|
759
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
760
|
+
return undefined;
|
|
761
|
+
return value;
|
|
762
|
+
}
|
|
651
763
|
function normalizeFrontendRoute(value) {
|
|
652
764
|
const route = asString(value).replaceAll("\\", "/").replace(/^\/+/, "");
|
|
653
765
|
if (!route || route === "." || route.split("/").includes(".."))
|
|
@@ -1331,6 +1443,12 @@ export function coerceFrontendImplementationContractInput(value, canonicalBindin
|
|
|
1331
1443
|
},
|
|
1332
1444
|
verificationTargets,
|
|
1333
1445
|
evidenceGaps,
|
|
1446
|
+
implementationSteps: asStringArray(record.implementationSteps),
|
|
1447
|
+
stylingStrategy: asString(record.stylingStrategy) || undefined,
|
|
1448
|
+
dependencyPolicy: asString(record.dependencyPolicy) || undefined,
|
|
1449
|
+
residualRisks: asStringArray(record.residualRisks),
|
|
1450
|
+
realIntegrationGap: asString(record.realIntegrationGap) || undefined,
|
|
1451
|
+
uiComponentChoices: normalizeUiComponentChoices(record.uiComponentChoices),
|
|
1334
1452
|
};
|
|
1335
1453
|
}
|
|
1336
1454
|
/**
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic Markdown renderer for a canonical frontend implementation
|
|
3
|
+
* contract. The prewrite gate materializes this as `contracts/frontend-plan.md`
|
|
4
|
+
* next to the contract JSON, so plan prose is never produced by the model — it
|
|
5
|
+
* is derived from the single machine contract at materialization time.
|
|
6
|
+
*
|
|
7
|
+
* The function is pure: the same contract always renders byte-identical
|
|
8
|
+
* Markdown, and absent optional fields render as placeholders without throwing.
|
|
9
|
+
*/
|
|
10
|
+
function bulletList(items) {
|
|
11
|
+
if (!items || items.length === 0)
|
|
12
|
+
return "";
|
|
13
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
14
|
+
}
|
|
15
|
+
function requirementsCoverage(contract) {
|
|
16
|
+
const lines = contract.requirements.map((requirement) => `- ${requirement.id}: ${requirement.expectedOutcome}`);
|
|
17
|
+
return lines.length > 0 ? lines.join("\n") : "_(no requirements)_";
|
|
18
|
+
}
|
|
19
|
+
function uiStateHandling(contract) {
|
|
20
|
+
const lines = contract.uiStates.map((state) => {
|
|
21
|
+
if (state.applicable) {
|
|
22
|
+
return `- ${state.name}: ${state.expectedBehavior ?? ""}`;
|
|
23
|
+
}
|
|
24
|
+
return `- ${state.name}: not applicable — ${state.notApplicableReason ?? ""}`;
|
|
25
|
+
});
|
|
26
|
+
return lines.length > 0 ? lines.join("\n") : "_(none)_";
|
|
27
|
+
}
|
|
28
|
+
function interactionNotes(contract) {
|
|
29
|
+
const lines = contract.interactions.map((interaction) => `- ${interaction.name}: ${interaction.trigger} → ${interaction.expectedBehavior}`);
|
|
30
|
+
return lines.length > 0 ? lines.join("\n") : "_(none)_";
|
|
31
|
+
}
|
|
32
|
+
function verificationPlan(contract) {
|
|
33
|
+
const lines = contract.verificationTargets.map((target) => `- ${target.id}: ${target.commandLabel} (${target.file})`);
|
|
34
|
+
return lines.length > 0 ? lines.join("\n") : "_(none)_";
|
|
35
|
+
}
|
|
36
|
+
function componentSelection(contract) {
|
|
37
|
+
const choices = contract.uiComponentChoices ?? [];
|
|
38
|
+
if (choices.length === 0)
|
|
39
|
+
return "_(not specified)_";
|
|
40
|
+
return choices
|
|
41
|
+
.map((choice) => {
|
|
42
|
+
const specReference = choice.specReference;
|
|
43
|
+
const refText = specReference
|
|
44
|
+
? ` ${specReference.path}${specReference.section ? `#${specReference.section}` : ""}${specReference.line != null ? `:${specReference.line}` : ""}`
|
|
45
|
+
: "";
|
|
46
|
+
return `- ${choice.purpose} → ${choice.component} (${choice.decision})${refText ? `:${refText}` : ""} — ${choice.rationale}`;
|
|
47
|
+
})
|
|
48
|
+
.join("\n");
|
|
49
|
+
}
|
|
50
|
+
export function renderFrontendPlanMarkdown(contract) {
|
|
51
|
+
return [
|
|
52
|
+
"# Frontend Implementation Plan",
|
|
53
|
+
"",
|
|
54
|
+
"## Requirement Coverage",
|
|
55
|
+
requirementsCoverage(contract),
|
|
56
|
+
"",
|
|
57
|
+
"## Implementation Steps",
|
|
58
|
+
bulletList(contract.implementationSteps) || "_(not specified)_",
|
|
59
|
+
"",
|
|
60
|
+
"## Target Files",
|
|
61
|
+
bulletList(contract.targets.files) || "_(none)_",
|
|
62
|
+
"",
|
|
63
|
+
"## UI State Handling",
|
|
64
|
+
uiStateHandling(contract),
|
|
65
|
+
"",
|
|
66
|
+
"## Styling & Component Strategy",
|
|
67
|
+
contract.stylingStrategy || "_(not specified)_",
|
|
68
|
+
"",
|
|
69
|
+
"## Component Selection (spec conformance)",
|
|
70
|
+
componentSelection(contract),
|
|
71
|
+
"",
|
|
72
|
+
"## Interaction Notes",
|
|
73
|
+
interactionNotes(contract),
|
|
74
|
+
"",
|
|
75
|
+
"## Mock & API Strategy",
|
|
76
|
+
contract.mockApi.strategy,
|
|
77
|
+
"",
|
|
78
|
+
"## Dependency Policy",
|
|
79
|
+
contract.dependencyPolicy || "_(not specified)_",
|
|
80
|
+
"",
|
|
81
|
+
"## Verification Plan",
|
|
82
|
+
verificationPlan(contract),
|
|
83
|
+
"",
|
|
84
|
+
"## Real Integration Gap",
|
|
85
|
+
contract.realIntegrationGap || "_(not specified)_",
|
|
86
|
+
"",
|
|
87
|
+
"## Residual Risks",
|
|
88
|
+
bulletList(contract.residualRisks) || "_(none)_",
|
|
89
|
+
].join("\n") + "\n";
|
|
90
|
+
}
|