@tea-agent/loop-agent 0.14.0 → 0.16.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 (117) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +98 -11
  3. package/README.md +24 -5
  4. package/dist/application/evaluation/alias.js +184 -0
  5. package/dist/application/evaluation/budget.js +192 -0
  6. package/dist/application/evaluation/campaign-hash.js +47 -0
  7. package/dist/application/evaluation/campaign-matrix.js +372 -0
  8. package/dist/application/evaluation/campaign-scorecard.js +135 -0
  9. package/dist/application/evaluation/campaign.js +370 -0
  10. package/dist/application/evaluation/candidate.js +23 -6
  11. package/dist/application/evaluation/corpus-hash.js +38 -0
  12. package/dist/application/evaluation/corpus.js +56 -0
  13. package/dist/application/evaluation/experiment.js +294 -0
  14. package/dist/application/evaluation/ignition.js +198 -0
  15. package/dist/application/evaluation/integrity-audit.js +162 -0
  16. package/dist/application/evaluation/outer-loop.js +132 -0
  17. package/dist/application/evaluation/pi-cell-executor.js +39 -0
  18. package/dist/application/evaluation/private-verifier.js +46 -0
  19. package/dist/application/evaluation/promotion-policy.js +151 -0
  20. package/dist/application/evaluation/proposer.js +98 -0
  21. package/dist/application/evaluation/types.js +522 -0
  22. package/dist/cli/command-definitions.js +19 -3
  23. package/dist/commands/eval.js +1176 -13
  24. package/dist/commands/init.js +4 -1
  25. package/dist/infrastructure/evaluation/alias-store.js +199 -0
  26. package/dist/infrastructure/evaluation/campaign-store.js +154 -0
  27. package/dist/infrastructure/evaluation/corpus-store.js +181 -0
  28. package/dist/infrastructure/evaluation/experiment-store.js +124 -0
  29. package/dist/infrastructure/evaluation/ignition-store.js +82 -0
  30. package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
  31. package/dist/infrastructure/evaluation/proposer-store.js +78 -0
  32. package/dist/worker/cli.js +6 -3
  33. package/dist/worker/delivery/final-verification.js +96 -8
  34. package/dist/worker/delivery/package.js +23 -4
  35. package/dist/worker/delivery/verification-bundle.js +521 -0
  36. package/dist/worker/feature/fullstack-validate.js +337 -0
  37. package/dist/worker/feature/profile-schema.js +44 -0
  38. package/dist/worker/feature/ready-plan-projection.js +1 -0
  39. package/dist/worker/feature/reducer.js +2 -0
  40. package/dist/worker/feature/review.js +106 -11
  41. package/dist/worker/materialize/harness-task-materializer.js +5 -0
  42. package/dist/worker/observability/read-model.js +7 -0
  43. package/dist/worker/observe/static/views/task.js +1 -0
  44. package/dist/worker/outcomes/adapters.js +144 -0
  45. package/dist/worker/outcomes/evidence-tokens.js +29 -0
  46. package/dist/worker/outcomes/gate.js +40 -0
  47. package/dist/worker/outcomes/projector.js +185 -0
  48. package/dist/worker/outcomes/registry.js +1 -0
  49. package/dist/worker/outcomes/store.js +131 -0
  50. package/dist/worker/outcomes/types.js +79 -0
  51. package/dist/worker/report/morning-report.js +4 -3
  52. package/dist/worker/run-task/run-task.js +85 -2
  53. package/dist/worker/runner/run-ready.js +32 -1
  54. package/dist/worker/task-graph/acceptance-schema.js +12 -0
  55. package/dist/worker/task-graph/ready-planner.js +131 -0
  56. package/dist/worker/task-graph/task-graph-schema.js +31 -0
  57. package/dist/worker/task-graph/validate.js +44 -4
  58. package/dist/worker/task-spec/schema.js +9 -0
  59. package/dist/worker/task-spec/validate.js +39 -0
  60. package/dist/worker/task-spec/workflow-routing.js +149 -0
  61. package/dist/workflows/dag/budget-enforcement.js +67 -0
  62. package/dist/workflows/dag/context-policy.js +137 -0
  63. package/dist/workflows/dag/init-hybrid.js +27 -11
  64. package/dist/workflows/dag/knowledge-curator.js +3 -0
  65. package/dist/workflows/dag/node-execution.js +11 -4
  66. package/dist/workflows/dag/prompt.js +1 -1
  67. package/dist/workflows/dag/runner.js +43 -16
  68. package/dist/workflows/dag/skill-snapshot.js +11 -7
  69. package/dist/workflows/dag/types.js +18 -0
  70. package/docs/README.md +1 -0
  71. package/docs/init-surface.manifest.json +7 -7
  72. package/docs/templates/branch-merge-report.md +0 -1
  73. package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
  74. package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
  75. package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
  76. package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
  77. package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
  78. package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
  79. package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
  80. package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
  81. package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
  82. package/docs/templates/product-line/AGENTS.md +1 -0
  83. package/docs/templates/product-line/README.md +17 -0
  84. package/docs/templates/product-line/acceptance.yaml +9 -0
  85. package/docs/templates/product-line/feature.yaml +11 -0
  86. package/docs/templates/product-line/task-graph.yaml +8 -0
  87. package/docs/templates/product-line/task.yaml +4 -0
  88. package/package.json +6 -16
  89. package/skills/browser-tools/SKILL.md +2 -2
  90. package/skills/frontend-design-review/references/review-checklist.md +27 -45
  91. package/skills/frontend-implementation/references/node-contracts.md +4 -4
  92. package/skills/frontend-review/SKILL.md +3 -1
  93. package/skills/frontend-review/references/review-findings.md +2 -1
  94. package/skills/frontend-verification/SKILL.md +3 -1
  95. package/skills/frontend-verification/references/verification-checklist.md +13 -22
  96. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  97. package/docs/agent-dag-recovery-playbook.md +0 -195
  98. package/docs/agent-dag-runner.md +0 -67
  99. package/docs/cursor-prompt-sidecar.md +0 -36
  100. package/docs/decisions/README.md +0 -18
  101. package/docs/design/README.md +0 -167
  102. package/docs/development-principles.md +0 -73
  103. package/docs/exec-plans/README.md +0 -6
  104. package/docs/exec-plans/active/README.md +0 -13
  105. package/docs/exec-plans/completed/README.md +0 -108
  106. package/docs/feature-workflow.md +0 -414
  107. package/docs/loop-agent-harness.md +0 -142
  108. package/docs/production-readiness.md +0 -96
  109. package/docs/progress/README.md +0 -81
  110. package/docs/reports/README.md +0 -163
  111. package/docs/verification-matrix.md +0 -70
  112. package/scripts/check-product-line-docs.sh +0 -29
  113. package/scripts/check-task-pool-root.sh +0 -32
  114. package/scripts/kb-graph-incremental-prepare.sh +0 -5
  115. package/scripts/kb-graph-materialize.sh +0 -4
  116. package/scripts/kb-graph-promote.sh +0 -4
  117. package/scripts/kb-query.sh +0 -5
@@ -0,0 +1,149 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Explicit runtime workflow that routes a TaskSpec to a controller DAG.
4
+ *
5
+ * The workflow is orthogonal to the business {@link TaskSpec.type} and the
6
+ * governance `loop_agent` profile. It is the only field the materializer uses
7
+ * to derive {@link TaskKind} (written to `task.json.taskKind`) which the
8
+ * published controller turns into a concrete DAG.
9
+ */
10
+ export const workflowSchema = z.enum([
11
+ "agent-dag",
12
+ "frontend-implementation",
13
+ "backend-test",
14
+ "frontend-test",
15
+ ]);
16
+ /**
17
+ * Deterministic workflow → taskKind mapping.
18
+ *
19
+ * - `agent-dag` → `standard` (the default agent DAG).
20
+ * - The three remaining workflows map 1:1 to their same-named taskKind.
21
+ */
22
+ export const WORKFLOW_TASK_KIND = {
23
+ "agent-dag": "standard",
24
+ "frontend-implementation": "frontend-implementation",
25
+ "backend-test": "backend-test",
26
+ "frontend-test": "frontend-test",
27
+ };
28
+ /**
29
+ * Legacy QA business types that have no deterministic backend/frontend split.
30
+ * Without an explicit {@link Workflow} they MUST surface migration guidance
31
+ * rather than silently guessing `backend-test` or `frontend-test`.
32
+ */
33
+ export const LEGACY_QA_TYPES = new Set([
34
+ "qa-casegen",
35
+ "qa-testcode",
36
+ "qa-execute",
37
+ ]);
38
+ /**
39
+ * Legacy business types that deterministically route to `frontend-implementation`.
40
+ */
41
+ const FRONTEND_FEATURE_TYPES = new Set(["frontend-feature"]);
42
+ /**
43
+ * Legacy business types that deterministically route to the `agent-dag`/`standard`
44
+ * DAG. Anything that is not a QA type and not a frontend-feature lands here.
45
+ */
46
+ export function legacyDefaultWorkflow(type) {
47
+ return FRONTEND_FEATURE_TYPES.has(type) ? "frontend-implementation" : "agent-dag";
48
+ }
49
+ /** Migration guidance surfaced whenever a legacy QA type lacks an explicit workflow. */
50
+ export const LEGACY_QA_MIGRATION_GUIDANCE = "Legacy qa-casegen/qa-testcode/qa-execute tasks no longer auto-select a backend or frontend workflow. " +
51
+ "Add `execution.workflow` with one of: agent-dag, frontend-implementation, backend-test, frontend-test. " +
52
+ "Use backend-test for backend-only test generation/execution, frontend-test for browser/UI tests, " +
53
+ "or agent-dag when the QA task is a generic agent study.";
54
+ export class WorkflowRoutingError extends Error {
55
+ code;
56
+ migrationGuidance;
57
+ constructor(code, message, migrationGuidance) {
58
+ super(message);
59
+ this.name = "WorkflowRoutingError";
60
+ this.code = code;
61
+ this.migrationGuidance = migrationGuidance;
62
+ }
63
+ }
64
+ /**
65
+ * Resolve the runtime {@link Workflow} and {@link TaskKind} for a TaskSpec.
66
+ *
67
+ * Resolution order:
68
+ * 1. Explicit `taskSpec.execution?.workflow` (source: `explicit`).
69
+ * 2. Legacy `frontend-feature` → `frontend-implementation`.
70
+ * 3. Legacy `backend-feature` (and any non-QA legacy type) → `agent-dag`.
71
+ * 4. Legacy QA types ({@link LEGACY_QA_TYPES}) without an explicit workflow →
72
+ * preserve their historic default `standard` execution as `agent-dag` and
73
+ * emit migration guidance during validation. We never guess backend vs frontend.
74
+ *
75
+ * Compatibility between an explicit workflow and the business `type` must be
76
+ * validated first via {@link validateWorkflowCompatibility}; this function
77
+ * assumes the combination is already known-valid.
78
+ */
79
+ export function resolveWorkflow(taskSpec) {
80
+ const explicit = taskSpec.execution?.workflow;
81
+ if (explicit) {
82
+ return {
83
+ workflow: explicit,
84
+ taskKind: WORKFLOW_TASK_KIND[explicit],
85
+ source: "explicit",
86
+ };
87
+ }
88
+ if (LEGACY_QA_TYPES.has(taskSpec.type)) {
89
+ return {
90
+ workflow: "agent-dag",
91
+ taskKind: WORKFLOW_TASK_KIND["agent-dag"],
92
+ source: "legacy-warning",
93
+ };
94
+ }
95
+ const workflow = legacyDefaultWorkflow(taskSpec.type);
96
+ return {
97
+ workflow,
98
+ taskKind: WORKFLOW_TASK_KIND[workflow],
99
+ source: "legacy-deterministic",
100
+ };
101
+ }
102
+ /**
103
+ * Deterministic compatibility validation between an explicit workflow and the
104
+ * business {@link TaskSpec.type}. Legacy QA types without an explicit workflow
105
+ * surface a single migration-guidance issue instead of a routing decision.
106
+ *
107
+ * Incompatible combinations (e.g. `frontend-feature` + `backend-test`,
108
+ * `backend-feature` + `frontend-implementation`, or a `qa-*` type paired with
109
+ * an implementation/test workflow from the wrong surface) are reported so the
110
+ * validator can fail fast rather than silently materializing a wrong DAG.
111
+ */
112
+ export function validateWorkflowCompatibility(taskSpec) {
113
+ const explicit = taskSpec.execution?.workflow;
114
+ if (!explicit) {
115
+ if (LEGACY_QA_TYPES.has(taskSpec.type)) {
116
+ return { issues: [], migrationGuidance: LEGACY_QA_MIGRATION_GUIDANCE };
117
+ }
118
+ // Legacy backend/frontend feature and other non-QA types route deterministically.
119
+ return { issues: [] };
120
+ }
121
+ const issues = [];
122
+ const type = taskSpec.type;
123
+ if (type === "backend-feature" && explicit !== "agent-dag" && explicit !== "backend-test") {
124
+ issues.push({
125
+ code: "workflow-type-incompatible",
126
+ message: `backend-feature is incompatible with workflow "${explicit}" (expected agent-dag or backend-test)`,
127
+ path: "execution.workflow",
128
+ });
129
+ }
130
+ if (type === "frontend-feature" && explicit !== "frontend-implementation" && explicit !== "frontend-test") {
131
+ issues.push({
132
+ code: "workflow-type-incompatible",
133
+ message: `frontend-feature is incompatible with workflow "${explicit}" (expected frontend-implementation or frontend-test)`,
134
+ path: "execution.workflow",
135
+ });
136
+ }
137
+ if (LEGACY_QA_TYPES.has(type)) {
138
+ // qa-* types are allowed to opt into a concrete test/implementation workflow
139
+ // but never into a feature implementation surface that does not match QA intent.
140
+ if (explicit === "frontend-implementation") {
141
+ issues.push({
142
+ code: "workflow-type-incompatible",
143
+ message: `${type} is a QA type and cannot route to frontend-implementation; use frontend-test, backend-test, or agent-dag`,
144
+ path: "execution.workflow",
145
+ });
146
+ }
147
+ }
148
+ return { issues };
149
+ }
@@ -0,0 +1,67 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { checkBudgetPreNode, createBudgetLedger, formatBudgetReportMarkdown, isHardBudgetBreached, recordNodeBudgetSample, skipPendingNodesForBudgetBreach, } from "../../application/evaluation/budget.js";
4
+ export function initRunBudgetLedger(state, budget) {
5
+ if (!budget)
6
+ return;
7
+ state.budget = structuredClone(budget);
8
+ state.budgetLedger = createBudgetLedger(budget);
9
+ }
10
+ function wallTimeMs(state, now = Date.now()) {
11
+ return Math.max(0, now - new Date(state.startedAt).getTime());
12
+ }
13
+ function repairPasses(state) {
14
+ return state.convergence?.currentPass ?? 0;
15
+ }
16
+ export function applyHardBudgetBreach(state, breach) {
17
+ if (!state.budgetLedger || state.budgetLedger.mode !== "hard")
18
+ return;
19
+ skipPendingNodesForBudgetBreach(state.nodes, breach);
20
+ state.failureCategory = "budget_breach";
21
+ }
22
+ export function preflightBudgetOrBreach(state) {
23
+ const ledger = state.budgetLedger;
24
+ if (!ledger)
25
+ return undefined;
26
+ const breach = checkBudgetPreNode(ledger, {
27
+ wallTimeMs: wallTimeMs(state),
28
+ repairPasses: repairPasses(state),
29
+ });
30
+ if (breach && isHardBudgetBreached(ledger)) {
31
+ applyHardBudgetBreach(state, breach);
32
+ }
33
+ return breach;
34
+ }
35
+ export function recordFinishedNodeBudget(state, node, contextChars) {
36
+ const ledger = state.budgetLedger;
37
+ if (!ledger)
38
+ return undefined;
39
+ const breach = recordNodeBudgetSample(ledger, {
40
+ nodeId: node.id,
41
+ tokensUsed: node.tokensUsed,
42
+ contextChars,
43
+ wallTimeMs: wallTimeMs(state),
44
+ repairPasses: repairPasses(state),
45
+ });
46
+ if (breach && isHardBudgetBreached(ledger)) {
47
+ applyHardBudgetBreach(state, breach);
48
+ }
49
+ return breach;
50
+ }
51
+ export async function writeBudgetLedgerArtifacts(runDir, ledger) {
52
+ if (!ledger)
53
+ return;
54
+ const jsonPath = path.join(runDir, "budget-ledger.json");
55
+ const mdPath = path.join(runDir, "budget-report.md");
56
+ await writeFile(jsonPath, `${JSON.stringify(ledger, null, 2)}\n`, "utf8");
57
+ await writeFile(mdPath, formatBudgetReportMarkdown(ledger), "utf8");
58
+ }
59
+ export function assertFrozenBudget(specBudget, stateBudget, runId) {
60
+ if (!specBudget && !stateBudget)
61
+ return;
62
+ if (!specBudget ||
63
+ !stateBudget ||
64
+ JSON.stringify(specBudget) !== JSON.stringify(stateBudget)) {
65
+ throw new Error(`dag run ${runId} budget drifted between run.json and state.json; refuse to continue`);
66
+ }
67
+ }
@@ -0,0 +1,137 @@
1
+ import { DEFAULT_SKILL_INSTRUCTION_MAX_CHARS, DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS, } from "./skill-instructions.js";
2
+ import { MAX_UPSTREAM_CHARS } from "./prompt.js";
3
+ import { resolveDagNodeSkills } from "./skills.js";
4
+ export const CONTEXT_POLICY_IDS = [
5
+ "baseline-v1",
6
+ "role-specialized-v1",
7
+ ];
8
+ export const DEFAULT_CONTEXT_POLICY_ID = "baseline-v1";
9
+ function roleOrUndefined(task) {
10
+ return task.role;
11
+ }
12
+ function pickByRole(table, role) {
13
+ if (role && table[role] !== undefined)
14
+ return table[role];
15
+ return table.default;
16
+ }
17
+ class BaselineContextPolicy {
18
+ id = "baseline-v1";
19
+ description = "Current DAG context assembly: shared upstream char budget, role skill defaults, learned patterns only for implementer.";
20
+ resolveSkills(spec, task) {
21
+ return resolveDagNodeSkills(spec, task);
22
+ }
23
+ resolveSkillInstructionBudget(task) {
24
+ return {
25
+ includeLearnedPatterns: task.role === "implementer",
26
+ perSkillMaxChars: DEFAULT_SKILL_INSTRUCTION_MAX_CHARS,
27
+ totalMaxChars: DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS,
28
+ };
29
+ }
30
+ resolveMaxUpstreamChars(_task) {
31
+ return MAX_UPSTREAM_CHARS;
32
+ }
33
+ toManifest() {
34
+ return {
35
+ schemaVersion: 1,
36
+ policyId: this.id,
37
+ description: this.description,
38
+ knobs: {
39
+ maxUpstreamCharsByRole: { default: MAX_UPSTREAM_CHARS },
40
+ includeLearnedPatternsRoles: ["implementer"],
41
+ perSkillMaxCharsByRole: {
42
+ default: DEFAULT_SKILL_INSTRUCTION_MAX_CHARS,
43
+ },
44
+ totalMaxCharsByRole: {
45
+ default: DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS,
46
+ },
47
+ },
48
+ };
49
+ }
50
+ }
51
+ /**
52
+ * First A/B challenger: keep skill name resolution identical to baseline, but
53
+ * specialize upstream / skill-instruction budgets by role so scouts see less
54
+ * noise and implementers retain more upstream + learned patterns surface.
55
+ */
56
+ class RoleSpecializedContextPolicy {
57
+ id = "role-specialized-v1";
58
+ description = "Role-specialized upstream and skill-instruction budgets; skill names still resolve via baseline merge order.";
59
+ maxUpstreamCharsByRole = {
60
+ default: MAX_UPSTREAM_CHARS,
61
+ scout: 1_200,
62
+ reviewer: 1_200,
63
+ implementer: 3_000,
64
+ verifier: 1_600,
65
+ closeout: 1_600,
66
+ planner: MAX_UPSTREAM_CHARS,
67
+ supervisor: MAX_UPSTREAM_CHARS,
68
+ };
69
+ perSkillMaxCharsByRole = {
70
+ default: DEFAULT_SKILL_INSTRUCTION_MAX_CHARS,
71
+ scout: 2_500,
72
+ implementer: 3_500,
73
+ };
74
+ totalMaxCharsByRole = {
75
+ default: DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS,
76
+ scout: 10_000,
77
+ implementer: 14_000,
78
+ };
79
+ learnedPatternRoles = new Set([
80
+ "implementer",
81
+ "closeout",
82
+ ]);
83
+ resolveSkills(spec, task) {
84
+ return resolveDagNodeSkills(spec, task);
85
+ }
86
+ resolveSkillInstructionBudget(task) {
87
+ const role = roleOrUndefined(task);
88
+ return {
89
+ includeLearnedPatterns: role
90
+ ? this.learnedPatternRoles.has(role)
91
+ : false,
92
+ perSkillMaxChars: pickByRole(this.perSkillMaxCharsByRole, role),
93
+ totalMaxChars: pickByRole(this.totalMaxCharsByRole, role),
94
+ };
95
+ }
96
+ resolveMaxUpstreamChars(task) {
97
+ return pickByRole(this.maxUpstreamCharsByRole, roleOrUndefined(task));
98
+ }
99
+ toManifest() {
100
+ return {
101
+ schemaVersion: 1,
102
+ policyId: this.id,
103
+ description: this.description,
104
+ knobs: {
105
+ maxUpstreamCharsByRole: this.maxUpstreamCharsByRole,
106
+ includeLearnedPatternsRoles: [...this.learnedPatternRoles],
107
+ perSkillMaxCharsByRole: this.perSkillMaxCharsByRole,
108
+ totalMaxCharsByRole: this.totalMaxCharsByRole,
109
+ },
110
+ };
111
+ }
112
+ }
113
+ const POLICIES = {
114
+ "baseline-v1": new BaselineContextPolicy(),
115
+ "role-specialized-v1": new RoleSpecializedContextPolicy(),
116
+ };
117
+ export function isContextPolicyId(value) {
118
+ return CONTEXT_POLICY_IDS.includes(value);
119
+ }
120
+ export function getContextPolicy(policyId) {
121
+ return POLICIES[policyId];
122
+ }
123
+ export function listContextPolicies() {
124
+ return CONTEXT_POLICY_IDS.map((id) => POLICIES[id]);
125
+ }
126
+ export function resolveContextPolicyId(spec) {
127
+ const raw = spec.defaults?.contextPolicyId;
128
+ if (!raw)
129
+ return DEFAULT_CONTEXT_POLICY_ID;
130
+ if (!isContextPolicyId(raw)) {
131
+ throw new Error(`unknown contextPolicyId "${raw}"; expected one of ${CONTEXT_POLICY_IDS.join(", ")}`);
132
+ }
133
+ return raw;
134
+ }
135
+ export function resolveContextPolicy(spec) {
136
+ return getContextPolicy(resolveContextPolicyId(spec));
137
+ }
@@ -555,7 +555,7 @@ export function hasApiDependency(sources) {
555
555
  * |---|---|---|---|
556
556
  * | 无 | 任意 | auto | not-required |
557
557
  * | 有 | present | auto | required |
558
- * | 有 | present/absent/ambiguous | auto | required (strategy chooses a safe mechanism) |
558
+ * | 有 | absent/ambiguous | auto | not-required (skip Mock, preserve integration gap) |
559
559
  * | 任意 | present | required | required |
560
560
  * | 任意 | absent/ambiguous | required | blocked |
561
561
  * | 任意 | 任意 | disabled | not-required (if spec allows) else blocked |
@@ -587,12 +587,12 @@ export function resolveFrontendMockMode(capability, taskConfig, hasApiDep) {
587
587
  if (!hasApiDep) {
588
588
  return "not-required";
589
589
  }
590
- // In auto mode, capability discovery is evidence for the strategy node, not
591
- // a final mechanism decision. Projects without a native Mock service may use
592
- // an existing browser interception harness or a reversible request adapter.
593
- // The deterministic strategy gate blocks before the writer when none can be
594
- // verified by the DAG's frozen static/behavior entrypoints.
595
- return "required";
590
+ // In auto mode, frontend Mock is optional. Only require Mock-backed strategy
591
+ // assessment when a native project Mock capability is actually present.
592
+ // Projects without confirmed Mock support continue through the normal
593
+ // frontend writer and must preserve the real integration gap instead of
594
+ // inventing a temporary Mock framework or blocking solely for missing Mock.
595
+ return capability.status === "present" ? "required" : "not-required";
596
596
  }
597
597
  function mapTaskComplexity(complexity) {
598
598
  if (complexity === "small")
@@ -1322,6 +1322,12 @@ export function buildStandardHybridDagFromTask(sources) {
1322
1322
  return spec;
1323
1323
  }
1324
1324
  function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, fixedVerificationContext, readOnlyPaths, forbiddenPaths) {
1325
+ const configuredPolicy = sources.taskConfig.frontendMock?.policy ?? "auto";
1326
+ const mockMode = sources.frontendMockMode ?? "not-required";
1327
+ const capabilityStatus = sources.frontendMockCapability?.status;
1328
+ const autoMaySkipMissingMock = configuredPolicy === "auto" &&
1329
+ mockMode === "not-required" &&
1330
+ (capabilityStatus === "absent" || capabilityStatus === "ambiguous");
1325
1331
  return {
1326
1332
  id: "frontend-mock-assess-pi",
1327
1333
  depends_on: ["frontend-contract-pi", "frontend-scout-pi"],
@@ -1337,7 +1343,9 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1337
1343
  "Perform read-only Mock assessment and select one safe frontend data strategy.",
1338
1344
  "The first non-empty line must be exactly one of: MOCK_STRATEGY: native, MOCK_STRATEGY: browser-intercept, MOCK_STRATEGY: request-adapter, MOCK_STRATEGY: not-needed, or MOCK_STRATEGY: blocked.",
1339
1345
  "Prefer an existing native Mock facility. Use browser-intercept only with an existing browser/e2e harness. When no Mock exists but the API layer is writable, use request-adapter by adding a minimal reversible adapter/DI seam within the approved writeSet; the real adapter must remain the production default.",
1340
- "Select not-needed only with positive evidence that no remote API is involved, a stable real backend will be exercised, or existing fixtures already cover the contract without changes. not-needed still requires the fixed behavior entrypoint to exercise applicable real or no-remote behavior verification. When configured policy is required, not-needed is forbidden.",
1346
+ autoMaySkipMissingMock
1347
+ ? "Auto mode may skip Mock when no project Mock capability is confirmed. Select not-needed with positive evidence from contract/scout that no project Mock capability is confirmed, continue without adding Mock files or dependencies, run the fixed verification entrypoints, and record any unproved real API behavior in Real Integration Gap. Do not block solely because no project Mock capability, browser interception harness, or request adapter exists."
1348
+ : "Select not-needed only with positive evidence that no remote API is involved, a stable real backend will be exercised, or existing fixtures already cover the contract without changes. not-needed still requires the fixed behavior entrypoint to exercise applicable real or no-remote behavior verification. When configured policy is required, not-needed is forbidden.",
1341
1349
  "Configured policy disabled requests no Mock but cannot override project specifications; if an actually-read project rule requires Mock, select blocked.",
1342
1350
  "",
1343
1351
  "## Required Output Sections:",
@@ -1356,6 +1364,9 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1356
1364
  "- Read project Mock/API/schema specifications before making any judgment.",
1357
1365
  "- Do not infer Mock service from lockfile-only or transitive dependency evidence.",
1358
1366
  "- Output MOCK_STRATEGY: blocked if capability evidence conflicts, contract fields are missing/conflicting, paths or dependencies are unauthorized, specs were not actually read, sources conflict, production-default-off cannot be proven, the API layer is not writable for a new adapter, or the frozen entrypoints cannot verify the selected strategy.",
1367
+ ...(autoMaySkipMissingMock
1368
+ ? ["- In auto mode with no confirmed project Mock capability, do not block solely because Mock is unavailable or Mock-specific verification is absent; choose not-needed and make the Real Integration Gap explicit unless project specs require Mock or another safety rule is violated."]
1369
+ : []),
1359
1370
  "- Never comment out or replace the real request with inline data, hard-code Mock enablement, import test mocks from a production entrypoint, invent API fields, or place secrets/real user data in fixtures.",
1360
1371
  "- Mock-backed behavior evidence proves the documented frontend contract only; it never proves real API integration.",
1361
1372
  "",
@@ -1564,7 +1575,11 @@ function resolveFrontendMockContextBlock(sources) {
1564
1575
  parts.push("Mock-backed frontend verification is required. Prefer the detected native service; otherwise the assessment may select an existing browser interception harness or reversible request adapter. Any handler, fixture, adapter, and UI changes stay in the single frontend-implement-pi writeSet.");
1565
1576
  }
1566
1577
  if (mode === "not-required") {
1567
- parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed only positively, or select a safe Mock strategy if an API dependency is confirmed.");
1578
+ parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
1579
+ if ((sources.taskConfig.frontendMock?.policy ?? "auto") === "auto" &&
1580
+ (capability.status === "absent" || capability.status === "ambiguous")) {
1581
+ parts.push("Auto mode may skip Mock when no project Mock capability is confirmed. Do not block solely for missing Mock; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.");
1582
+ }
1568
1583
  }
1569
1584
  if (mode === "blocked") {
1570
1585
  parts.push("Mock contract is blocked. The DAG must stop before any write node executes.");
@@ -1900,7 +1915,7 @@ function buildFrontendHybridDagFromTask(sources) {
1900
1915
  subtask_prompt: [
1901
1916
  "Audit the frontend plan before implementation.",
1902
1917
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
1903
- "Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for the selected strategy. Mock strategies require Mock-backed evidence; not-needed requires applicable real or no-remote behavior evidence.",
1918
+ "Request revision when the Mock strategy is MOCK_STRATEGY: blocked, missing, unsupported by repository evidence, inconsistent with the API contract, outside authorized paths/dependencies, unable to prove production-default-off behavior with the fixed production/default-real-path static check, or missing deterministic behavior verification for the selected strategy. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case the plan must preserve the real request path and record the Real Integration Gap.",
1904
1919
  "Also request revision for missing applicable UI states, unsupported dependency additions, design-system drift without reason, weak interaction coverage, broad scope, inline fake data, schema drift, or missing deterministic verification commands.",
1905
1920
  "Read-only: do not modify repository files.",
1906
1921
  fixedVerificationContext,
@@ -2330,7 +2345,7 @@ function buildFrontendHybridDagFromTask(sources) {
2330
2345
  "Read the validated frontend-implementation-contract, frontend-verification-trace evidence, static/behavior shell facts, and actual diff. Trace proves command/file/symbol binding only—not semantic correctness.",
2331
2346
  "Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
2332
2347
  "Use the direct contract, original plan, revision/no-op result, and final design review to reconstruct the approved plan and design verdict; do not infer them from the implementation summary.",
2333
- "Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence; not-needed requires applicable real or no-remote behavior evidence. Verify that the real request remains the default when Mock activation is absent.",
2348
+ "Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
2334
2349
  "Inspect the production/default-real-path static evidence directly and require Mock activation to be off for that check.",
2335
2350
  "Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
2336
2351
  "Review implementation quality, behavior/state coverage, verification evidence, and maintainability. Read-only: do not modify files.",
@@ -2391,6 +2406,7 @@ function buildFrontendHybridDagFromTask(sources) {
2391
2406
  "Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
2392
2407
  "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use frontend-verification-trace facts; do not invent Browser evidence from component tests.",
2393
2408
  `When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
2409
+ `When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
2394
2410
  "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
2395
2411
  sourceContext,
2396
2412
  mockContextBlock,
@@ -103,6 +103,7 @@ export async function curateKnowledgePatterns(input) {
103
103
  ok: true,
104
104
  patternsPath,
105
105
  patternCount: 0,
106
+ patterns: [],
106
107
  safetyFindings: [],
107
108
  message: "no patterns.jsonl found; no proposal generated",
108
109
  };
@@ -139,6 +140,7 @@ export async function curateKnowledgePatterns(input) {
139
140
  patternsPath,
140
141
  outputPath,
141
142
  patternCount: patterns.length,
143
+ patterns,
142
144
  proposalMarkdown,
143
145
  safetyFindings,
144
146
  message: "proposal failed skill safety audit",
@@ -153,6 +155,7 @@ export async function curateKnowledgePatterns(input) {
153
155
  patternsPath,
154
156
  outputPath,
155
157
  patternCount: patterns.length,
158
+ patterns,
156
159
  proposalMarkdown,
157
160
  safetyFindings,
158
161
  message: patterns.length === 0
@@ -3,29 +3,35 @@ import { readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { recordDecisionEnvelopeForNode, shouldPauseOnHumanEscalation, writeHumanEscalationArtifacts, } from "./decision-envelope.js";
5
5
  import { writeNodeRecord, writeNodeSkillArtifacts } from "./run-store.js";
6
+ import { resolveContextPolicy } from "./context-policy.js";
6
7
  import { buildDagNodePromptEnvelope } from "./prompt.js";
7
8
  import { persistLongNodeOutputArtifacts } from "./upstream-artifacts.js";
8
9
  import { computeBackoffDelayMs, isRetryablePiFailureCategory, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
9
10
  import { writeDagNodeJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
10
11
  import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillSnapshotIntegrityError, readSkillSnapshot, } from "./skill-snapshot.js";
11
12
  import { resolveDagSkillInstructions, skillInstructionMetadata, } from "./skill-instructions.js";
12
- import { resolveDagNodeSkills } from "./skills.js";
13
13
  import { parseRepairArtifactFromText, resolveRepairTaskForGate, validateRepairArtifactScope, } from "./repair-artifact.js";
14
14
  import { resolveModelForTask, } from "./types.js";
15
15
  export function buildNodePrompt(spec, task, upstream) {
16
+ const policy = resolveContextPolicy(spec);
16
17
  return buildDagNodePromptEnvelope({
17
18
  spec,
18
19
  task,
19
20
  upstream,
20
- resolvedSkills: resolveDagNodeSkills(spec, task),
21
+ resolvedSkills: policy.resolveSkills(spec, task),
22
+ maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
21
23
  });
22
24
  }
23
25
  export async function buildNodePromptWithResolvedSkillInstructions(spec, task, upstream, cwd) {
24
- const skillNames = resolveDagNodeSkills(spec, task);
26
+ const policy = resolveContextPolicy(spec);
27
+ const skillNames = policy.resolveSkills(spec, task);
28
+ const budget = policy.resolveSkillInstructionBudget(task);
25
29
  const resolvedSkillInstructions = task.executor === "pi"
26
30
  ? await resolveDagSkillInstructions(skillNames, {
27
31
  cwd,
28
- includeLearnedPatterns: task.role === "implementer",
32
+ includeLearnedPatterns: budget.includeLearnedPatterns,
33
+ perSkillMaxChars: budget.perSkillMaxChars,
34
+ totalMaxChars: budget.totalMaxChars,
29
35
  })
30
36
  : [];
31
37
  return {
@@ -35,6 +41,7 @@ export async function buildNodePromptWithResolvedSkillInstructions(spec, task, u
35
41
  upstream,
36
42
  resolvedSkills: skillNames,
37
43
  resolvedSkillInstructions,
44
+ maxUpstreamChars: policy.resolveMaxUpstreamChars(task),
38
45
  }),
39
46
  resolvedSkills: skillInstructionMetadata(resolvedSkillInstructions),
40
47
  };
@@ -6,7 +6,7 @@ export const DAG_AUTHORING_GUIDANCE = [
6
6
  "Prefer same-rank parallel read-only scouts over unnecessary serial depends_on chains.",
7
7
  "Add depends_on only when a child truly needs upstream output; default to independent ranks.",
8
8
  "Every task must explicitly declare executor; defaults.executor is schema-only, not a runtime fallback.",
9
- "Cursor remains a first-class executor for scouts and exclusive implementers when it fits the task.",
9
+ "Pi is the only governed Agent DAG writer; cursor-prompt is an explicit manual one-shot sidecar and must not enter Loop auto-execute or Delegate writers.",
10
10
  "exclusive nodes require narrow, concrete, disjoint writeSet paths; never use ** or repo root.",
11
11
  "Read-only nodes must not write repository files, including root artifacts/**; return findings in node output only.",
12
12
  "If the DAG is a single linear chain, challenge whether read-only work can run in parallel ranks.",