@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.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.
- package/CHANGELOG.md +18 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +762 -252
- package/dist/executors/pi-executor.js +20 -1
- package/dist/executors/pi-sdk-executor.js +64 -1
- package/dist/executors/shell-executor.js +5 -2
- package/dist/shared/frontend-execution-policy.js +22 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +26 -7
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +260 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +746 -265
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +48 -10
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +9 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-scopes.js +117 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +16 -10
- package/dist/workflows/dag/node-execution.js +32 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/package.json +1 -1
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +2 -2
- package/skills/frontend-plan/references/decision-contract.md +18 -5
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { frontendExecutionPolicySchema } from "../../shared/frontend-execution-policy.js";
|
|
2
3
|
import { access, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
5
|
import { deflateRawSync } from "node:zlib";
|
|
@@ -3061,7 +3062,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3061
3062
|
.filter(Boolean)
|
|
3062
3063
|
.join("\n\n"),
|
|
3063
3064
|
scout: [
|
|
3064
|
-
buildSourceContextBlock(sources, { includeReferenceDocuments: false }),
|
|
3065
|
+
buildSourceContextBlock(sources, { includeRequirementExcerpt: false, includeConstraintExcerpt: false, includeReferenceDocuments: false }),
|
|
3065
3066
|
capabilityContextBlock,
|
|
3066
3067
|
]
|
|
3067
3068
|
.filter(Boolean)
|
|
@@ -3342,11 +3343,11 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3342
3343
|
allowedPaths: readOnlyPaths,
|
|
3343
3344
|
forbiddenPaths,
|
|
3344
3345
|
skills: FRONTEND_CONTRACT_SKILLS,
|
|
3345
|
-
outputContract: "
|
|
3346
|
+
outputContract: "Incremental typed requirement facts; narrative is display-only. Submit through the incremental typed tools record_requirement / record_constraint / record_evidence_expectation / record_handoff_intent / record_open_question / record_split_proposal / record_ui_state / record_required_deliverables / record_openspec_selection, then complete each input scope with complete_contract_scope, then call finalize_contract; correct rejected calls until one successful terminal. record_requirement takes the canonical ledger requirement id and optional execution:{groupId,kind,summary} — the runtime owns the authoritative text, source spans, fragment bindings, and disposition. UI-visible or interactive requirements register a non-blocking frontend-test handoff intent, and any source-declared UI-state table is extracted verbatim through record_ui_state. End finalize_contract with a single contract disposition of ready | ready-with-assumptions | blocked. Cover scope, non-goals, acceptance criteria, UI states, target runtime environment, risks, and verification expectations; do not fix target files, components, or implementation methods as requirements. When OpenSpec candidates exist, classify only the ones you actually use: call record_openspec_selection once per required/relevant path; never enumerate irrelevant candidates (unmentioned defaults to irrelevant) and never emit a fenced selection JSON. No file writes.",
|
|
3346
3347
|
subtask_prompt: [
|
|
3347
|
-
"OUTPUT BUDGET DISCIPLINE
|
|
3348
|
-
"
|
|
3349
|
-
"Confirm each requirement by the SAME id as the ledger canonical requirement it covers (sourceBinding.requirementIds, e.g. AC-001) — do NOT invent new REQ/BR prefixed ids for canonical requirements: the compiled contract must match the ledger canonical requirement ids exactly or schema validation rejects it (unknown requirement id). record_requirement takes
|
|
3348
|
+
"OUTPUT BUDGET DISCIPLINE: provider capacity is discovered at runtime; use small records — NEVER attempt to emit the whole contract in one response; a single large JSON dump will be truncated and rejected. Incremental submission through the typed tools is the ONLY supported output mode. Start submitting with the FIRST tool call: after each read, call record_requirement for the requirements you have already confirmed, one or a few per call. Every tool-call round MUST make progress by submitting at least one record_* fact. Do not re-read the same source file that is already materialized in this session; read each file at most once.",
|
|
3349
|
+
"Consume the complete injected input scope and produce a concise frontend implementation contract as typed requirement facts from complete injected scopes.",
|
|
3350
|
+
"Confirm each requirement by the SAME id as the ledger canonical requirement it covers (sourceBinding.requirementIds, e.g. AC-001) — do NOT invent new REQ/BR prefixed ids for canonical requirements: the compiled contract must match the ledger canonical requirement ids exactly or schema validation rejects it (unknown requirement id). record_requirement takes the canonical id and optional execution:{groupId,kind,summary}; the runtime commits the authoritative text and sourceFragmentIds from the frozen ledger. Never pass text/statement/sourceFragmentIds yourself — model rewrites and JSON-stringified fragment arrays are rejected.",
|
|
3350
3351
|
"Requirement semantics, source spans, dispositions, and fragment bindings are ledger/runtime-owned. If a canonical requirement is genuinely blocked, say so in the Markdown contract narrative and finalize with the matching disposition instead of trying to encode it in the requirement fact.",
|
|
3351
3352
|
"Register evidence expectations for each requirement across static, behavior, Mock, and real integration as required | optional | not-applicable; required must follow from user requirements, task risk, or project governance, never from model convenience. For UI-visible or interactive requirements, register a non-blocking frontend-test handoff intent.",
|
|
3352
3353
|
'Use record_evidence_expectation with {requirementId,evidence:{static,behavior,mock,"real-integration"}}; every lane is required | optional | not-applicable. Requirement text and provenance remain runtime-owned.',
|
|
@@ -3406,17 +3407,17 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3406
3407
|
retryOnInvalid: true,
|
|
3407
3408
|
skeleton: frontendContractSkeleton,
|
|
3408
3409
|
},
|
|
3409
|
-
outputContract: "Typed decision patch only: map frozen requirements to implementation/verification targets and select the needed component, state, data/Mock, styling, and dependency decisions. Use only the record_* tools needed to express those decisions, then call finalize_plan
|
|
3410
|
+
outputContract: "Typed decision patch only: map frozen requirements to implementation/verification targets and select the needed component, state, data/Mock, styling, and dependency decisions. Use only the record_* tools needed to express those decisions, then call finalize_plan; correct rejected facts until one successful terminal. Contract owns requirement semantics; Scout owns repository discovery; deterministic runtime owns schema, protected fields, path containment, and command validation. No Markdown narrative or file writes.",
|
|
3410
3411
|
subtask_prompt: [
|
|
3411
3412
|
"Plan only the delta between the frozen frontend-contract-pi facts and frontend-scout-pi target surface. Do not reinterpret the task, repeat requirements, search the repository, or choose implementation order.",
|
|
3412
3413
|
"Record only: requirement-to-file/verification coverage; component/styling choices; applicable UI state and interaction behavior; data/Mock strategy; and a dependency policy or genuine evidence gap. Reuse Scout paths. If scope is missing, record a blocking gap instead of inventing a path.",
|
|
3413
3414
|
"Use the typed tool schemas as the field contract. Runtime owns schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff, aliases, command allowlisting, path containment, and final validation; do not restate those rules or emit a full JSON contract.",
|
|
3414
3415
|
`Cover each frozen requirement ID exactly once: ${requirementIds.join(", ") || "(none)"}. Bind every verification target to a frozen commandId from the directory above plus a Scout-confirmed file. Behavior commands prove observable behavior: one target may cover multiple related requirementIds when one test behavior proves them together; do not mechanically create one target per requirement. A behavior target id is the stable machine trace token and its file must be a test file. Static commands are project-wide checks traced by file and command only.`,
|
|
3415
|
-
"UX vocabulary protocol: record_state_registry FIRST with the full global vocabulary — one stable kebab-case behavior-domain name per UI state/interaction (e.g. planner-task-edit, focus-queue-move), never one name per AC number and never a rename of an already-recorded concept.
|
|
3416
|
+
"UX vocabulary protocol: record_state_registry FIRST with the full global vocabulary — one stable kebab-case behavior-domain name per UI state/interaction (e.g. planner-task-edit, focus-queue-move), never one name per AC number and never a rename of an already-recorded concept. Details consume complete execution-group scopes and reuse the same global names across scopes. Constraints/exclusions must not manufacture UI. Then record_state_flow entries whose names all come from that registry; uiState names must use the contract's declared authoritative ids (declaredUiStates in the plan input) when present. Retry attempts see committedUx in this input — reuse those exact names. Components: one choice may cover many state/interaction ids via covers; reuse-existing requires evidencePath naming an existing repo file (greenfield must be decision=new).",
|
|
3416
3417
|
...(requiresOpenspecClassification ? ["When a component choice uses an OpenSpec selection, cite that selection; otherwise do not classify unrelated candidates."] : []),
|
|
3417
|
-
"Call finalize_plan
|
|
3418
|
-
"TOOL-ONLY PLAN: Do not read Contract/Scout stdout, task sources, or Scout-confirmed target files. Contract and Scout already own evidence discovery; use the injected upstream facts, record a genuine evidence gap when those facts are insufficient, and start committing record_* facts immediately. For decision=new, pass sourceRequirementIds to record_component_choice; runtime derives the exact PRD citation from the frozen ledger.",
|
|
3419
|
-
"
|
|
3418
|
+
"Call finalize_plan; correct rejected facts until one successful terminal after the necessary typed facts. Return no Markdown narrative.",
|
|
3419
|
+
"Group members retain full source text and independent ACs. Use record_plan_group_coverage only for actually shared references; do not create components for exclusions. TOOL-ONLY PLAN: Do not read Contract/Scout stdout, task sources, or Scout-confirmed target files. Contract and Scout already own evidence discovery; use the injected upstream facts, record a genuine evidence gap when those facts are insufficient, and start committing record_* facts immediately. For decision=new, pass sourceRequirementIds to record_component_choice; runtime derives the exact PRD citation from the frozen ledger.",
|
|
3420
|
+
"Incremental output protocol: process the current complete scope, committing small records immediately. Runtime packs full input and estimated work without assuming a model capacity from its name. Use record_plan_group_coverage for shared references, record_mock_endpoint per endpoint, and finalize only when all coverage is complete. On exhaustion, durable progress survives and remaining work is reduced; never omit source conditions or repeatedly retry the same exhausted scope.",
|
|
3420
3421
|
fixedVerificationContext,
|
|
3421
3422
|
scopedOpenspecContext,
|
|
3422
3423
|
mockContextBlock,
|
|
@@ -3500,6 +3501,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3500
3501
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
3501
3502
|
outputContract: "Authoritative typed design terminal via approve_design / request_design_changes tools. No JSON verdict; the committed typed design fact is the only authority. No file writes.",
|
|
3502
3503
|
subtask_prompt: [
|
|
3504
|
+
"Submit findings individually with record_design_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences; shared verification is valid only when it proves each independent AC.",
|
|
3503
3505
|
"Audit the frontend plan before implementation. frontend-plan-pi is emitted to you as canonical full-contract JSON after the runtime applied and validated the planner's editable patch against its protected skeleton; there is no separate plan prose.",
|
|
3504
3506
|
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_design or request_design_changes. Call exactly one of them; after calling one, do not call the other.",
|
|
3505
3507
|
"request_design_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
@@ -3665,6 +3667,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3665
3667
|
skills: FRONTEND_REVIEW_SKILLS,
|
|
3666
3668
|
outputContract: 'Authoritative typed review terminal via approve_review / request_review_changes tools. No JSON verdict is required in the response text; the typed terminal fact is the only authority. No file writes.',
|
|
3667
3669
|
subtask_prompt: [
|
|
3670
|
+
"Submit findings individually with record_review_finding and stable IDs; terminal tools aggregate saved findings. Correct rejected calls, stop after a successful terminal. Check execution groups against every member source outcome, including permission, threshold and failure-path differences; shared verification is valid only when it proves each independent AC.",
|
|
3668
3671
|
"Review the frontend implementation and verification evidence.",
|
|
3669
3672
|
"Your authoritative terminal verdict is exactly one committed typed tool call: approve_review or request_review_changes. Call it once and do not call the other afterwards.",
|
|
3670
3673
|
"approve_review means the implementation passes; it must not carry Critical or Important findings. request_review_changes must carry a typed issueCategory, at least one evidenceRef, and non-empty findings.",
|
|
@@ -3751,6 +3754,9 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
3751
3754
|
// runner can bound M6 auto-recovery without re-reading the task config.
|
|
3752
3755
|
const frontendMaxContinuations = sources.taskConfig.frontendRecovery?.maxContinuations ?? 1;
|
|
3753
3756
|
spec.frontendRecovery = { maxContinuations: frontendMaxContinuations };
|
|
3757
|
+
// Configurable execution safety quota, independent of model context/output capacity.
|
|
3758
|
+
spec.budget = { schemaVersion: 1, mode: "hard", limits: { maxProviderRequests: sources.taskConfig.frontendRecovery?.maxProviderRequests ?? 1024 } };
|
|
3759
|
+
spec.frontendExecutionPolicy = frontendExecutionPolicySchema.parse(sources.taskConfig.frontendExecutionPolicy ?? {});
|
|
3754
3760
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3755
3761
|
stampGeneratedArtifactBindings(spec);
|
|
3756
3762
|
parseDagSpec(spec);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { collectFrontendExecutionGroups } from "./frontend-execution-groups.js";
|
|
2
|
+
import { reserveDagProviderRequest } from "./budget-enforcement.js";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { existsSync } from "node:fs";
|
|
3
5
|
import { readFile } from "node:fs/promises";
|
|
@@ -380,27 +382,14 @@ async function readFrontendPlanCommittedSnapshot(runDir, nodeId) {
|
|
|
380
382
|
function planInputRecord(value) {
|
|
381
383
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
382
384
|
}
|
|
383
|
-
function planInputText(value,
|
|
384
|
-
|
|
385
|
-
return undefined;
|
|
386
|
-
const normalized = value.trim();
|
|
387
|
-
return normalized.length <= maxChars
|
|
388
|
-
? normalized
|
|
389
|
-
: `${normalized.slice(0, maxChars - 1)}…`;
|
|
385
|
+
function planInputText(value, _maxChars) {
|
|
386
|
+
return typeof value === "string" && value.trim().length ? value.trim() : undefined;
|
|
390
387
|
}
|
|
391
388
|
function planInputStrings(value) {
|
|
392
389
|
return Array.isArray(value)
|
|
393
390
|
? value.filter((item) => typeof item === "string")
|
|
394
391
|
: [];
|
|
395
392
|
}
|
|
396
|
-
/** Input bound for the planner evidence block; protects the model input budget. */
|
|
397
|
-
const FRONTEND_PLAN_INPUT_MAX_CHARS = 12_000;
|
|
398
|
-
const FRONTEND_PLAN_INPUT_CAP_LADDER = [
|
|
399
|
-
{ text: 240, array: 40 },
|
|
400
|
-
{ text: 120, array: 20 },
|
|
401
|
-
{ text: 60, array: 10 },
|
|
402
|
-
{ text: 24, array: 4 },
|
|
403
|
-
];
|
|
404
393
|
/**
|
|
405
394
|
* Frozen requirement→PRD citation map for the plan review checklist: lets the
|
|
406
395
|
* model declare sourceRequirementIds whose section/line match the component
|
|
@@ -446,78 +435,19 @@ async function resolveComponentSourceCitations(spec, cwd) {
|
|
|
446
435
|
return new Map();
|
|
447
436
|
}
|
|
448
437
|
}
|
|
449
|
-
/** Input bound for the contract node's compiled ledger block. */
|
|
450
|
-
const FRONTEND_CONTRACT_INPUT_MAX_CHARS = 12_000;
|
|
451
438
|
/**
|
|
452
|
-
* Render the contract node's complete
|
|
439
|
+
* Render the contract node's complete semantic ledger handoff. The
|
|
453
440
|
* source-fidelity ledger already extracted canonical requirements with source
|
|
454
441
|
* spans; the contract node confirms and commits them incrementally through
|
|
455
442
|
* record_requirement instead of re-reading the raw source (extreme-environment:
|
|
456
443
|
* a small output window cannot absorb a full source re-read).
|
|
457
444
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
445
|
+
* Whole obligations are retained here; the executor selects complete scopes
|
|
446
|
+
* before dispatch. References and conditions are never clipped.
|
|
460
447
|
*/
|
|
461
448
|
export function renderFrontendContractInputContext(input) {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// sourceFragmentIds entirely, the model (correctly refusing to invent ids)
|
|
465
|
-
// committed empty bindings, and the plan compile failed the ledger-binding
|
|
466
|
-
// gate for every requirement. Bindings therefore bypass the cap ladder and
|
|
467
|
-
// every degradation level; only requirement TEXTS and fragment CONTEXT
|
|
468
|
-
// (path/headingPath) may degrade. Fragment context is rendered only for
|
|
469
|
-
// fragments actually referenced by a requirement and shrinks first.
|
|
470
|
-
const referencedFragmentIds = new Set(input.canonicalRequirements.flatMap((requirement) => planInputStrings(requirement.sourceFragmentIds)));
|
|
471
|
-
const referencedFragments = input.fragments.filter((fragment) => referencedFragmentIds.has(fragment.id));
|
|
472
|
-
const serializeAtCap = (cap) => JSON.stringify({
|
|
473
|
-
requirements: input.canonicalRequirements.map((requirement) => ({
|
|
474
|
-
id: requirement.id,
|
|
475
|
-
text: planInputText(requirement.text, cap.text),
|
|
476
|
-
sourceFragmentIds: planInputStrings(requirement.sourceFragmentIds),
|
|
477
|
-
})),
|
|
478
|
-
fragments: referencedFragments.map((fragment) => ({
|
|
479
|
-
id: fragment.id,
|
|
480
|
-
path: planInputText(fragment.path, 200),
|
|
481
|
-
headingPath: planInputText(fragment.headingPath, 120),
|
|
482
|
-
lineRange: fragment.lineRange,
|
|
483
|
-
})),
|
|
484
|
-
});
|
|
485
|
-
let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
|
|
486
|
-
for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
|
|
487
|
-
if (serialized.length <= FRONTEND_CONTRACT_INPUT_MAX_CHARS)
|
|
488
|
-
break;
|
|
489
|
-
serialized = serializeAtCap(cap);
|
|
490
|
-
}
|
|
491
|
-
if (serialized.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS) {
|
|
492
|
-
// Last resort: keep every requirement id AND its fragment bindings,
|
|
493
|
-
// degrade texts, and shrink referenced fragment context first (halve,
|
|
494
|
-
// then drop context fields, then drop the fragment list entirely).
|
|
495
|
-
// Requirement ids and sourceFragmentIds are never dropped.
|
|
496
|
-
let fragments = referencedFragments.map((fragment) => ({
|
|
497
|
-
id: fragment.id,
|
|
498
|
-
path: planInputText(fragment.path, 120),
|
|
499
|
-
}));
|
|
500
|
-
let requirements = input.canonicalRequirements.map((requirement) => {
|
|
501
|
-
const sourceFragmentIds = planInputStrings(requirement.sourceFragmentIds);
|
|
502
|
-
return {
|
|
503
|
-
id: requirement.id,
|
|
504
|
-
text: "(truncated)",
|
|
505
|
-
// Empty bindings carry no information; omit them so the payload
|
|
506
|
-
// stays inside the char bound when no requirement is bound.
|
|
507
|
-
...(sourceFragmentIds.length > 0 ? { sourceFragmentIds } : {}),
|
|
508
|
-
};
|
|
509
|
-
});
|
|
510
|
-
let bounded = JSON.stringify({ degraded: "requirement-texts-truncated", requirements, fragments });
|
|
511
|
-
while (bounded.length > FRONTEND_CONTRACT_INPUT_MAX_CHARS && fragments.length > 0) {
|
|
512
|
-
fragments = fragments.slice(0, Math.floor(fragments.length / 2));
|
|
513
|
-
bounded = JSON.stringify({
|
|
514
|
-
degraded: "requirement-texts-truncated",
|
|
515
|
-
requirements,
|
|
516
|
-
fragments,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
serialized = bounded;
|
|
520
|
-
}
|
|
449
|
+
const referenced = new Set(input.canonicalRequirements.flatMap(r => r.sourceFragmentIds));
|
|
450
|
+
const serialized = JSON.stringify({ requirements: input.canonicalRequirements, fragments: input.fragments.filter(f => referenced.has(f.id)) });
|
|
521
451
|
return [
|
|
522
452
|
"<frontend_contract_input>",
|
|
523
453
|
"Canonical requirements extracted by the source-fidelity ledger, compiled by the runner. Treat them as the authoritative requirement inventory: confirm and commit each requirement through record_requirement (one per tool call); the ledger already binds source fragments, so do NOT re-read the raw source files.",
|
|
@@ -542,16 +472,13 @@ export async function buildFrontendContractInputContext(input) {
|
|
|
542
472
|
});
|
|
543
473
|
}
|
|
544
474
|
/**
|
|
545
|
-
* Render the planner's complete
|
|
475
|
+
* Render the planner's complete semantic evidence handoff from committed
|
|
546
476
|
* typed facts. It deliberately excludes upstream response prose and artifact
|
|
547
477
|
* paths: Contract and Scout have already established these facts, so Plan
|
|
548
478
|
* should decide and commit rather than spend another model turn reading them.
|
|
549
479
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
* fallback keeps every requirement id (with `text: "(truncated)"`) while
|
|
553
|
-
* declaring the degradation, so the planner records targeted evidence gaps
|
|
554
|
-
* instead of receiving a silently corrupted tail.
|
|
480
|
+
* Whole obligations are retained. Session packing and scoped projection happen
|
|
481
|
+
* in the executor before dispatch, without dropping text or source identities.
|
|
555
482
|
*/
|
|
556
483
|
export function renderFrontendPlanInputContext(input) {
|
|
557
484
|
const committedFacts = (records) => records.flatMap((record) => record.phase === "committed" && planInputRecord(record.fact)
|
|
@@ -610,77 +537,12 @@ export function renderFrontendPlanInputContext(input) {
|
|
|
610
537
|
const behaviorRequiredIds = requirementFacts
|
|
611
538
|
.filter((requirement) => requirement.evidence.behavior === "required")
|
|
612
539
|
.map((requirement) => requirement.id);
|
|
613
|
-
const
|
|
614
|
-
requirements
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
})),
|
|
619
|
-
requiredDeliverables,
|
|
620
|
-
targetSurface: targetSurface.map((surface) => ({
|
|
621
|
-
completeness: planInputText(surface.completeness, 32),
|
|
622
|
-
entrypoint: planInputText(surface.entrypoint, cap.text),
|
|
623
|
-
routeOrMount: planInputText(surface.routeOrMount, cap.text),
|
|
624
|
-
implementationPaths: planInputStrings(surface.implementationPaths).slice(0, cap.array),
|
|
625
|
-
testPaths: planInputStrings(surface.testPaths).slice(0, cap.array),
|
|
626
|
-
dataSource: planInputText(surface.dataSource, cap.text),
|
|
627
|
-
allowedPathConflicts: planInputStrings(surface.allowedPathConflicts).slice(0, cap.array),
|
|
628
|
-
unresolvedPaths: planInputStrings(surface.unresolvedPaths).slice(0, cap.array),
|
|
629
|
-
})),
|
|
630
|
-
designEvidence: designEvidence.map((evidence) => ({
|
|
631
|
-
source: planInputText(evidence.source, cap.text),
|
|
632
|
-
paths: planInputStrings(evidence.paths).slice(0, cap.array),
|
|
633
|
-
conflicts: planInputStrings(evidence.conflicts).slice(0, cap.array),
|
|
634
|
-
})),
|
|
635
|
-
declaredUiStates: declaredUiStates.map((state) => ({
|
|
636
|
-
id: state.id,
|
|
637
|
-
trigger: planInputText(state.trigger, cap.text),
|
|
638
|
-
observableOutcome: planInputText(state.observableOutcome, cap.text),
|
|
639
|
-
})),
|
|
640
|
-
committedUx: committedUiStateNames.length > 0 ||
|
|
641
|
-
committedInteractionNames.length > 0
|
|
642
|
-
? {
|
|
643
|
-
uiStateNames: committedUiStateNames,
|
|
644
|
-
interactionNames: committedInteractionNames,
|
|
645
|
-
}
|
|
646
|
-
: undefined,
|
|
540
|
+
const serialized = JSON.stringify({
|
|
541
|
+
requirements, requiredDeliverables, targetSurface, designEvidence, declaredUiStates,
|
|
542
|
+
executionGroups: collectFrontendExecutionGroups(contractFacts.filter(f => f.kind === "requirement").map(f => ({ id: String(f.id), execution: f.execution }))),
|
|
543
|
+
constraints: contractFacts.filter(f => ["constraint", "open-question", "split-proposal", "handoff-intent"].includes(String(f.kind))),
|
|
544
|
+
committedUx: committedUiStateNames.length || committedInteractionNames.length ? { uiStateNames: committedUiStateNames, interactionNames: committedInteractionNames } : undefined,
|
|
647
545
|
});
|
|
648
|
-
let serialized = serializeAtCap(FRONTEND_PLAN_INPUT_CAP_LADDER[0]);
|
|
649
|
-
for (const cap of FRONTEND_PLAN_INPUT_CAP_LADDER.slice(1)) {
|
|
650
|
-
if (serialized.length <= FRONTEND_PLAN_INPUT_MAX_CHARS)
|
|
651
|
-
break;
|
|
652
|
-
serialized = serializeAtCap(cap);
|
|
653
|
-
}
|
|
654
|
-
if (serialized.length > FRONTEND_PLAN_INPUT_MAX_CHARS) {
|
|
655
|
-
// Last resort: keep every requirement id (ids are short and the plan
|
|
656
|
-
// prompt separately lists them) but drop their texts, shrink scout facts
|
|
657
|
-
// to the minimum, and declare the degradation instead of corrupting JSON.
|
|
658
|
-
let fallback = {
|
|
659
|
-
degraded: "requirement-texts-truncated",
|
|
660
|
-
requiredDeliverables,
|
|
661
|
-
requirements: requirements.map((requirement) => ({
|
|
662
|
-
id: requirement.id,
|
|
663
|
-
text: "(truncated)",
|
|
664
|
-
})),
|
|
665
|
-
targetSurface: targetSurface.map((surface) => ({
|
|
666
|
-
completeness: planInputText(surface.completeness, 32),
|
|
667
|
-
implementationPaths: planInputStrings(surface.implementationPaths).slice(0, FRONTEND_PLAN_INPUT_CAP_LADDER[3].array),
|
|
668
|
-
})),
|
|
669
|
-
designEvidence: [],
|
|
670
|
-
};
|
|
671
|
-
let bounded = JSON.stringify(fallback);
|
|
672
|
-
let keep = fallback.requirements.length;
|
|
673
|
-
while (bounded.length > FRONTEND_PLAN_INPUT_MAX_CHARS &&
|
|
674
|
-
keep > 0) {
|
|
675
|
-
keep = Math.max(0, Math.floor(keep / 2));
|
|
676
|
-
fallback = { ...fallback, requirements: fallback.requirements.slice(0, keep) };
|
|
677
|
-
bounded = JSON.stringify({
|
|
678
|
-
...fallback,
|
|
679
|
-
requirementIdsTruncated: keep < requirements.length,
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
serialized = bounded;
|
|
683
|
-
}
|
|
684
546
|
const checklistLines = [
|
|
685
547
|
"1. Record the GLOBAL UX vocabulary with record_state_registry BEFORE any record_state_flow: one stable behavior-domain name per state/interaction (e.g. planner-task-edit), never one name per AC number, and never a rename of an already-recorded concept. Coverage slices by AC; UX does not.",
|
|
686
548
|
"2. Every recorded interaction/uiState name must be in that registry, and uiState names must use the contract's declared authoritative ids (declaredUiStates below) when present.",
|
|
@@ -1583,6 +1445,20 @@ export async function executeDagNode(input) {
|
|
|
1583
1445
|
return;
|
|
1584
1446
|
}
|
|
1585
1447
|
}
|
|
1448
|
+
if (task.id === "frontend-scout-pi") {
|
|
1449
|
+
try {
|
|
1450
|
+
const { readTypedEventStoreFromJsonl } = await import("./frontend-typed-event-store.js");
|
|
1451
|
+
const facts = (await readTypedEventStoreFromJsonl(path.join(runDir, "frontend-contract-pi", "contract-typed-facts.jsonl"))).filter(r => r.phase === "committed").map(r => r.fact);
|
|
1452
|
+
const requirements = [...new Map(facts.filter(f => f.kind === "requirement").map(f => [String(f.id), f])).values()];
|
|
1453
|
+
if (!requirements.length)
|
|
1454
|
+
throw Error("FRONTEND_INPUT_MISSING: Scout needs committed Contract obligations");
|
|
1455
|
+
prompt += `\n<frontend_scout_input>\n${JSON.stringify({ requirements, sharedFacts: facts.filter(f => !["requirement", "contract-scope-completed", "contract-finalized"].includes(String(f.kind))) })}\n</frontend_scout_input>`;
|
|
1456
|
+
}
|
|
1457
|
+
catch (error) {
|
|
1458
|
+
await failBeforePrompt(error, "frontend-scout-input-unavailable");
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1586
1462
|
if (FRONTEND_WRITER_NODE_IDS.includes(nodeId)) {
|
|
1587
1463
|
// Design-review findings are not reliable in the provider's prose output
|
|
1588
1464
|
// (typed terminal nodes commonly return an empty assistant message). Inject
|
|
@@ -1770,6 +1646,7 @@ export async function executeDagNode(input) {
|
|
|
1770
1646
|
attemptPrompt,
|
|
1771
1647
|
});
|
|
1772
1648
|
result = await executeNode({
|
|
1649
|
+
...(state.budgetLedger?.mode === "hard" && state.budgetLedger.limits.maxProviderRequests !== undefined ? { reserveProviderRequest: () => reserveDagProviderRequest({ state, nodeId, attempt: attemptNumber, persist: input.persistState }) } : {}),
|
|
1773
1650
|
task,
|
|
1774
1651
|
cwd,
|
|
1775
1652
|
model,
|
|
@@ -136,6 +136,10 @@ export function buildUpstreamContext(task, upstream, maxChars = MAX_UPSTREAM_CHA
|
|
|
136
136
|
const consumedArtifactIds = new Set((task.consumesArtifacts ?? []).map((binding) => binding.artifactId));
|
|
137
137
|
for (const depId of task.depends_on) {
|
|
138
138
|
const record = upstream[depId];
|
|
139
|
+
if (task.id === "frontend-scout-pi" && depId === "frontend-contract-pi" && record?.status === "FINISHED") {
|
|
140
|
+
sections.push(`## Upstream output: ${depId}\nCommitted Contract obligations are supplied in frontend_scout_input. Do not reconstruct them from prose or reread the source.`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
139
143
|
const stdout = record?.stdout?.trim() ? record.stdout : "";
|
|
140
144
|
const assistantText = record?.assistantText?.trim()
|
|
141
145
|
? record.assistantText
|
|
@@ -46,6 +46,7 @@ const highRiskContinuationAuthorityV1Schema = z
|
|
|
46
46
|
tokens: z.number().int().nonnegative().nullable(),
|
|
47
47
|
wallTimeMs: z.number().nonnegative(),
|
|
48
48
|
executorCalls: z.number().int().nonnegative(),
|
|
49
|
+
providerRequests: z.number().int().nonnegative().optional(),
|
|
49
50
|
repairPasses: z.number().int().nonnegative(),
|
|
50
51
|
peakContextChars: z.number().int().nonnegative(),
|
|
51
52
|
missingTokenNodeIds: z.array(z.string()),
|
|
@@ -1300,7 +1301,12 @@ export async function evaluateDagRerunPlan(input) {
|
|
|
1300
1301
|
const task = tasksByIdForPlan.get(nodeId);
|
|
1301
1302
|
return task?.readSet ?? [];
|
|
1302
1303
|
});
|
|
1303
|
-
|
|
1304
|
+
// An omitted or empty readSet is unrestricted, not proof that an
|
|
1305
|
+
// imported node read nothing. Localize drift only when every reused
|
|
1306
|
+
// node has an explicit reading boundary.
|
|
1307
|
+
const importedReadSetsKnown = importedNodeIds.every((nodeId) => (tasksByIdForPlan.get(nodeId)?.readSet?.length ?? 0) > 0);
|
|
1308
|
+
const scopedDrift = importedReadSetsKnown &&
|
|
1309
|
+
driftedPaths !== undefined &&
|
|
1304
1310
|
driftedPaths.length > 0 &&
|
|
1305
1311
|
driftedPaths.every((driftPath) => !importedReadPaths.some((pattern) => pathMatchesWorkspacePattern(driftPath, pattern)) &&
|
|
1306
1312
|
resetNodeIds.some((nodeId) => {
|
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { isHardBudgetBreached, resolveEffectiveMaxConcurrent, } from "../../application/evaluation/budget.js";
|
|
7
7
|
import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
|
|
8
8
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
9
|
-
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
9
|
+
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, validateCumulativeBudgetSuccessor, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
10
10
|
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, readDagRunState, requireActiveDagRun, } from "./lifecycle.js";
|
|
11
11
|
import { markInterruptNeedsReconcile, mergeAbortSignals, readInterruptRequest, runnerIdentityFromState, settleDagInterrupt, startInterruptWatcher, } from "./interrupt-request.js";
|
|
12
12
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
@@ -887,6 +887,31 @@ async function executeDagCheckpoint(input) {
|
|
|
887
887
|
observeOnly: created.kind === "observed",
|
|
888
888
|
deps: {
|
|
889
889
|
...input.recovery,
|
|
890
|
+
budget: input.recovery.budget ?? state.budget,
|
|
891
|
+
readLedgers: input.recovery.readLedgers ?? (async () => {
|
|
892
|
+
if (!state.budget)
|
|
893
|
+
return [];
|
|
894
|
+
const location = await locateDagRun(cwd, state.runId);
|
|
895
|
+
let current = await readDagRunState(location?.runDir ?? runDir);
|
|
896
|
+
const visited = new Set([current.runId]);
|
|
897
|
+
while (current.frontendRecoveryState?.parentRunId === current.runId && current.frontendRecoveryState.childRunId && current.frontendRecoveryState.childRunId !== current.runId) {
|
|
898
|
+
if (["child-staging", "child-activating"].includes(current.frontendRecoveryState.phase))
|
|
899
|
+
break;
|
|
900
|
+
const childId = current.frontendRecoveryState.childRunId;
|
|
901
|
+
if (visited.has(childId))
|
|
902
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: cyclic recovery");
|
|
903
|
+
visited.add(childId);
|
|
904
|
+
const childLocation = await locateDagRun(cwd, childId);
|
|
905
|
+
if (!childLocation)
|
|
906
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_LINEAGE_INVALID: missing recovery child");
|
|
907
|
+
const child = await readDagRunState(childLocation.runDir);
|
|
908
|
+
validateCumulativeBudgetSuccessor(current, child);
|
|
909
|
+
current = child;
|
|
910
|
+
}
|
|
911
|
+
if (!current.budgetLedger)
|
|
912
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: recovery ledger missing");
|
|
913
|
+
return [current.budgetLedger];
|
|
914
|
+
}),
|
|
890
915
|
failureOwner: input.recovery.failureOwner ??
|
|
891
916
|
recoveryTrigger.failureOwner,
|
|
892
917
|
protocolFailureReason: input.recovery.protocolFailureReason ??
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { frontendExecutionPolicySchema } from "../../shared/frontend-execution-policy.js";
|
|
2
3
|
import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
|
|
3
4
|
import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
|
|
4
5
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
@@ -1385,6 +1386,7 @@ export const dagSpecSchema = z
|
|
|
1385
1386
|
evaluation: dagEvaluationBindingSchema.optional(),
|
|
1386
1387
|
/** Optional hard/record-only budget; requires version 3 or 4. */
|
|
1387
1388
|
budget: dagBudgetSchema.optional(),
|
|
1389
|
+
frontendExecutionPolicy: frontendExecutionPolicySchema.optional(),
|
|
1388
1390
|
sourceBinding: dagSourceBindingSchema.optional(),
|
|
1389
1391
|
/** v4 managed Task Contract binding; only valid on version 4. */
|
|
1390
1392
|
taskContractBinding: dagTaskContractBindingSchema.optional(),
|
|
@@ -1567,6 +1569,10 @@ export const frontendRecoveryStateSchema = z
|
|
|
1567
1569
|
recoveryRootRunId: z.string().min(1),
|
|
1568
1570
|
parentRunId: z.string().min(1),
|
|
1569
1571
|
childRunId: z.string().min(1).optional(),
|
|
1572
|
+
budgetLineage: z.object({
|
|
1573
|
+
parentLedgerSha256: z.string().regex(/^[a-f0-9]{64}$/), inheritedAt: z.string().datetime(),
|
|
1574
|
+
inheritedConsumed: z.object({ tokens: z.number().nonnegative().nullable(), wallTimeMs: z.number().nonnegative(), executorCalls: z.number().int().nonnegative(), providerRequests: z.number().int().nonnegative().optional(), repairPasses: z.number().int().nonnegative(), peakContextChars: z.number().int().nonnegative(), missingTokenNodeIds: z.array(z.string()) }).strict(),
|
|
1575
|
+
}).strict().optional(),
|
|
1570
1576
|
attemptId: z.string().min(1),
|
|
1571
1577
|
attemptIndex: z.number().int().min(0).max(5),
|
|
1572
1578
|
continuationCount: z.number().int().min(0).max(5),
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
|
|
10
10
|
- [`version-and-release-management.md`](version-and-release-management.md):版本发布管理——next / release-x.y / latest 渠道、移动式 `npm-next` ref、`inspect next` 查询入口、workflow 与 promote 手册。
|
|
11
11
|
- [`production-readiness.md`](production-readiness.md):交付前的生产就绪判断。
|
|
12
|
+
- [`frontend-reliability-canary-runbook.md`](frontend-reliability-canary-runbook.md):frontend 可靠性修复的 live canary 内网执行手册(协议冻结自 exec-plan §17;配对矩阵、证据注册与人工晋升门)。
|
|
12
13
|
- [`agent-worker-production-readiness.md`](agent-worker-production-readiness.md):agent-worker Feature/Task Pool 外层编排的 Production Readiness v1 操作合同。
|
|
13
14
|
- [`backend-test-jacoco-coverage.md`](backend-test-jacoco-coverage.md):Java 被测服务挂载 JaCoCo tcpserver agent 的 Maven、`java -jar` 与 Docker 操作手册。
|
|
14
15
|
- [`console-chat-dogfood-playbook.md`](console-chat-dogfood-playbook.md):Operator Chat(含 Explore 子 Agent)真实模型 dogfood 的 CLI 驱动步骤、mutation 双 token / SSE 超时 / question 应答等已知陷阱与验证方式。
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
- **DAG 如何消费**:进入 `evidenceGaps[]` 与 Non-goals,`frontend-review-pi`/closeout 据它保留风险与后续项。
|
|
62
62
|
- **缺失/冲突时 fail-closed**:范围与需求非目标冲突、或风险被隐藏时阻塞。
|
|
63
63
|
|
|
64
|
-
## OpenSpec
|
|
64
|
+
## OpenSpec 来源与引用
|
|
65
65
|
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
66
|
+
- Contract 用 `record_openspec_selection` 声明实际使用的 required/relevant 来源,未提及候选不自动升级为 required。
|
|
67
|
+
- Plan 通过 typed tools 引用冻结来源;runtime 从绑定来源与真实读取证据物化引用和 canonical JSON。模型不再输出 JSON 契约块或 `openspec-citations` fence。
|
|
68
|
+
- 来源未绑定、引用不可解析或缺少真实读取背书时,由确定性 gate 阻断;不得伪造读取或引用。
|
|
@@ -115,6 +115,32 @@
|
|
|
115
115
|
"items": {
|
|
116
116
|
"$ref": "#/$defs/path"
|
|
117
117
|
}
|
|
118
|
+
},
|
|
119
|
+
"execution": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"additionalProperties": false,
|
|
122
|
+
"required": [
|
|
123
|
+
"groupId",
|
|
124
|
+
"kind",
|
|
125
|
+
"summary"
|
|
126
|
+
],
|
|
127
|
+
"properties": {
|
|
128
|
+
"groupId": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"minLength": 1
|
|
131
|
+
},
|
|
132
|
+
"kind": {
|
|
133
|
+
"enum": [
|
|
134
|
+
"behavior",
|
|
135
|
+
"constraint",
|
|
136
|
+
"exclusion"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"summary": {
|
|
140
|
+
"type": "string",
|
|
141
|
+
"minLength": 1
|
|
142
|
+
}
|
|
143
|
+
}
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
}
|
|
@@ -644,10 +670,16 @@
|
|
|
644
670
|
"allOf": [
|
|
645
671
|
{
|
|
646
672
|
"if": {
|
|
647
|
-
"required": [
|
|
673
|
+
"required": [
|
|
674
|
+
"ledgerPath"
|
|
675
|
+
]
|
|
648
676
|
},
|
|
649
677
|
"then": {
|
|
650
|
-
"required": [
|
|
678
|
+
"required": [
|
|
679
|
+
"ledgerSha256",
|
|
680
|
+
"inputDigest",
|
|
681
|
+
"requirementToFragments"
|
|
682
|
+
]
|
|
651
683
|
}
|
|
652
684
|
}
|
|
653
685
|
],
|
|
@@ -27,17 +27,17 @@
|
|
|
27
27
|
"tasks": [
|
|
28
28
|
{
|
|
29
29
|
"id":"frontend-contract-pi", "depends_on":[], "complexity":"MED", "executor":"pi", "role":"planner", "writePolicy":"read-only", "allowedPaths":["**"], "forbiddenPaths":[".harness/**","artifacts/**"],
|
|
30
|
-
"outputContract":"
|
|
30
|
+
"outputContract":"Incremental Contract typed facts from complete input scopes; complete_contract_scope after all decisions, then one successful finalize_contract. Runtime owns canonical text and source bindings. No file writes.",
|
|
31
31
|
"subtask_prompt":"Read the task inputs. Produce a concise frontend contract that maps each required behavior, UI state, and interaction to observable outcomes. Identify missing or conflicting requirements as blockers; do not infer them and do not edit files."
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"id":"frontend-scout-pi", "depends_on":["frontend-contract-pi"], "complexity":"LOW", "executor":"pi", "role":"scout", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
|
|
35
|
-
"outputContract":"
|
|
35
|
+
"outputContract":"Scout typed target-surface and design-evidence facts backed by fresh read evidence; durable receipts only. No file writes.",
|
|
36
36
|
"subtask_prompt":"Inspect only the declared frontend source and test paths. Report reusable component, styling, accessibility, state-management, and verification patterns that satisfy the contract. Do not edit files or broaden the allowed paths."
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"id":"frontend-plan-pi", "depends_on":["frontend-scout-pi"], "complexity":"MED", "executor":"pi", "role":"planner", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
|
|
40
|
-
"outputContract":"
|
|
40
|
+
"outputContract":"Plan record_* tools for scoped coverage, shared execution groups and UX, one mock endpoint per record, then one successful finalize_plan. Runtime derives canonical JSON and Markdown. No file writes.",
|
|
41
41
|
"subtask_prompt":"Turn the contract and reconnaissance into a minimal implementation plan. Every target file and verification step must be concrete. Preserve existing design and accessibility conventions. If any behavior lacks a verifiable target, report it as blocked instead of guessing."
|
|
42
42
|
},
|
|
43
43
|
{
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"id":"frontend-design-review-pi", "depends_on":["frontend-design-policy-shell"], "complexity":"MED", "executor":"pi", "role":"reviewer", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/SOURCE/PATH/**","REPLACE/WITH/FRONTEND/TEST/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
|
|
51
|
-
"outputContract":"
|
|
51
|
+
"outputContract":"record_design_finding for individual findings; approve_design or request_design_changes provides the sole committed terminal. Independently review execution-group member differences. No file writes.",
|
|
52
52
|
"subtask_prompt":"Review the approved plan against the frontend contract and design-policy result. Reject missing visible states, interaction behavior, accessibility obligations, or unsupported design claims. Do not edit files."
|
|
53
53
|
},
|
|
54
54
|
{
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"id":"frontend-review-pi", "depends_on":["frontend-review-context-shell"], "complexity":"MED", "executor":"pi", "role":"reviewer", "writePolicy":"read-only", "allowedPaths":["REPLACE/WITH/FRONTEND/TARGET/PATH/**"], "forbiddenPaths":[".harness/**","artifacts/**"],
|
|
79
|
-
"outputContract":"
|
|
79
|
+
"outputContract":"record_review_finding for individual findings; approve_review or request_review_changes provides the sole committed terminal. No file writes.",
|
|
80
80
|
"subtask_prompt":"Review the actual diff and archived verification evidence against the frontend contract. Do not treat missing, weak, mock-only, or not-run evidence as passed. Report explicit residual risks; do not edit files."
|
|
81
81
|
},
|
|
82
82
|
{
|
package/package.json
CHANGED
|
@@ -15,7 +15,8 @@ language. Do not inspect implementation files or choose components, routes, Mock
|
|
|
15
15
|
mechanisms, dependencies, or target code as requirement facts.
|
|
16
16
|
|
|
17
17
|
Submit confirmed facts incrementally with the contract `record_*` tools and finish
|
|
18
|
-
with
|
|
18
|
+
with one successful `finalize_contract`. Complete each input scope with
|
|
19
|
+
`complete_contract_scope` after all its decisions; correct rejected calls and retry. Start recording as soon as a requirement or
|
|
19
20
|
constraint is confirmed; do not accumulate the whole contract in one response.
|
|
20
21
|
|
|
21
22
|
Missing product semantics may be explicit assumptions only when they do not change
|