@tea-agent/loop-agent 0.25.6 → 0.26.1
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/AGENTS.md +2 -1
- package/CHANGELOG.md +27 -1
- package/README.md +8 -3
- package/dist/cli/command-definitions.js +25 -10
- package/dist/cli/help.js +4 -3
- package/dist/cli/program.js +43 -17
- package/dist/commands/import-prd.js +7 -2
- package/dist/commands/init.js +7 -5
- package/dist/commands/task-source-prepare.js +468 -0
- package/dist/executors/dag-pi-executor.js +66 -25
- package/dist/executors/model-routing.js +34 -18
- package/dist/executors/shell-write-guard.js +161 -25
- package/dist/governance/manifest-types.js +33 -5
- package/dist/task/source-prepare/build-draft.js +215 -0
- package/dist/task/source-prepare/completeness.js +195 -0
- package/dist/task/source-prepare/index.js +7 -0
- package/dist/task/source-prepare/parse-intent.js +373 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/prepare.js +506 -0
- package/dist/task/source-prepare/reference-integrity.js +274 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/task-demand-routing.js +3 -1
- package/dist/worker/console/chat/model-resolver.js +15 -3
- package/dist/worker/observe/static/constants.js +3 -2
- package/dist/worker/observe/static/dag-model.js +1 -0
- package/dist/worker/observe/static/styles.css +182 -42
- package/dist/workflows/dag/lifecycle.js +40 -30
- package/dist/workflows/dag/node-execution.js +13 -0
- package/dist/workflows/dag/types.js +59 -19
- package/docs/templates/harness.schema.json +29 -7
- package/docs/templates/init-managed-agents.md +10 -5
- package/harness.json +1 -2
- package/package.json +1 -1
- package/skills/loop-agent/SKILL.md +5 -2
- package/skills/loop-agent/references/command-reference.md +17 -15
- package/skills/loop-agent/references/harness-policy.md +3 -4
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/loop-agent/references/model-routing.md +2 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
- package/skills/loop-agent/references/task-workflow.md +7 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, mkdir, readFile, readdir, rename
|
|
1
|
+
import { access, mkdir, readFile, readdir, rename } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { hostname as localHostname } from "node:os";
|
|
4
4
|
import { writeJsonAtomic, } from "../../infrastructure/harness/atomic-write.js";
|
|
@@ -170,20 +170,22 @@ export function assessDagRunLiveness(input) {
|
|
|
170
170
|
return { status: "unknown" };
|
|
171
171
|
if (runner.hostname !== (input.hostname ?? localHostname()))
|
|
172
172
|
return { status: "unknown-host" };
|
|
173
|
-
const isAlive = input.isProcessAlive ??
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
const isAlive = input.isProcessAlive ??
|
|
174
|
+
((pid) => {
|
|
175
|
+
try {
|
|
176
|
+
process.kill(pid, 0);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
182
183
|
if (!isAlive(runner.pid))
|
|
183
184
|
return { status: "orphaned", runnerAlive: false };
|
|
184
185
|
const heartbeatMs = Date.parse(runner.heartbeatAt);
|
|
185
186
|
const nowMs = (input.now ?? new Date()).getTime();
|
|
186
|
-
if (!Number.isNaN(heartbeatMs) &&
|
|
187
|
+
if (!Number.isNaN(heartbeatMs) &&
|
|
188
|
+
nowMs - heartbeatMs > (input.staleThresholdMs ?? 90_000)) {
|
|
187
189
|
return { status: "stale", runnerAlive: true };
|
|
188
190
|
}
|
|
189
191
|
const activeNode = Object.values(input.state.nodes).find((node) => node.status === "RUNNING");
|
|
@@ -193,20 +195,20 @@ export function assessDagRunLiveness(input) {
|
|
|
193
195
|
if (activeNode.livenessStatus === "needs-attention") {
|
|
194
196
|
return { status: "needs-attention", runnerAlive: true };
|
|
195
197
|
}
|
|
196
|
-
if (activeNode.livenessStatus === "suspected-stall"
|
|
197
|
-
|
|
198
|
+
if (activeNode.livenessStatus === "suspected-stall" ||
|
|
199
|
+
activeNode.livenessStatus === "probing") {
|
|
198
200
|
return { status: "suspected-stall", runnerAlive: true };
|
|
199
201
|
}
|
|
200
202
|
if (activeNode.livenessStatus === "quiet") {
|
|
201
203
|
return { status: "node-quiet", runnerAlive: true };
|
|
202
204
|
}
|
|
203
205
|
// Fall back to meaningful progress clocks (never use runner lease as progress).
|
|
204
|
-
const meaningfulAt = activeNode.lastMeaningfulProgressAt
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
206
|
+
const meaningfulAt = activeNode.lastMeaningfulProgressAt ??
|
|
207
|
+
activeNode.lastProviderActivityAt ??
|
|
208
|
+
activeNode.lastToolActivityAt ??
|
|
209
|
+
activeNode.lastOutputActivityAt ??
|
|
210
|
+
activeNode.lastActivityAt ??
|
|
211
|
+
activeNode.startedAt;
|
|
210
212
|
const nodeActivityMs = Date.parse(meaningfulAt ?? "");
|
|
211
213
|
if (!Number.isNaN(nodeActivityMs)) {
|
|
212
214
|
const idleMs = nowMs - nodeActivityMs;
|
|
@@ -229,12 +231,12 @@ export function deriveDagRunEffectiveStatus(input) {
|
|
|
229
231
|
if (input.lifecycle === "paused")
|
|
230
232
|
return "paused";
|
|
231
233
|
if (input.lifecycle === "completed") {
|
|
232
|
-
return input.state.status
|
|
234
|
+
return mapTerminalDagRunEffectiveStatus(input.state.status);
|
|
233
235
|
}
|
|
234
236
|
if (input.state.status === "pending")
|
|
235
237
|
return "pending";
|
|
236
238
|
if (isTerminalDagRunStatus(input.state.status)) {
|
|
237
|
-
return input.state.status
|
|
239
|
+
return mapTerminalDagRunEffectiveStatus(input.state.status);
|
|
238
240
|
}
|
|
239
241
|
if (input.liveness === "orphaned" || input.liveness === "stale")
|
|
240
242
|
return "interrupted";
|
|
@@ -252,19 +254,20 @@ export function deriveDagRunEffectiveStatus(input) {
|
|
|
252
254
|
}
|
|
253
255
|
export function assessDagRunRecoveryEligibility(input) {
|
|
254
256
|
const reasons = [];
|
|
255
|
-
const canResume = input.lifecycle === "active"
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
const canResume = input.lifecycle === "active" &&
|
|
258
|
+
input.state.status === "running" &&
|
|
259
|
+
Boolean(input.state.humanDecisionNodeId) &&
|
|
260
|
+
Boolean(input.hasHumanApproval);
|
|
259
261
|
if (!canResume)
|
|
260
262
|
reasons.push("standard-resume-preconditions-not-met");
|
|
261
263
|
let canReconcile = true;
|
|
262
|
-
if (input.lifecycle === "completed" ||
|
|
264
|
+
if (input.lifecycle === "completed" ||
|
|
265
|
+
isTerminalDagRunStatus(input.state.status)) {
|
|
263
266
|
canReconcile = false;
|
|
264
267
|
reasons.push("run-already-terminal");
|
|
265
268
|
}
|
|
266
|
-
if (input.lifecycle === "active"
|
|
267
|
-
|
|
269
|
+
if (input.lifecycle === "active" &&
|
|
270
|
+
[
|
|
268
271
|
"active",
|
|
269
272
|
"node-quiet",
|
|
270
273
|
"suspected-stall",
|
|
@@ -298,6 +301,13 @@ export const TERMINAL_RUN_STATUSES = new Set([
|
|
|
298
301
|
export function isTerminalDagRunStatus(status) {
|
|
299
302
|
return TERMINAL_RUN_STATUSES.has(status);
|
|
300
303
|
}
|
|
304
|
+
function mapTerminalDagRunEffectiveStatus(status) {
|
|
305
|
+
if (status === "finished")
|
|
306
|
+
return "finished";
|
|
307
|
+
if (status === "partial_failed")
|
|
308
|
+
return "partial_failed";
|
|
309
|
+
return "failed";
|
|
310
|
+
}
|
|
301
311
|
function listPendingNodeIds(state) {
|
|
302
312
|
return Object.values(state.nodes)
|
|
303
313
|
.filter((node) => node.status === "PENDING")
|
|
@@ -477,8 +487,8 @@ export async function listAllDagRunEntries(repoRoot) {
|
|
|
477
487
|
located.push(...(await listRunsInLifecycleDir(repoRoot, lifecycle)));
|
|
478
488
|
}
|
|
479
489
|
return located.sort((left, right) => {
|
|
480
|
-
const lifecycleCompare = DAG_LIFECYCLE_SCAN_ORDER.indexOf(left.lifecycle)
|
|
481
|
-
|
|
490
|
+
const lifecycleCompare = DAG_LIFECYCLE_SCAN_ORDER.indexOf(left.lifecycle) -
|
|
491
|
+
DAG_LIFECYCLE_SCAN_ORDER.indexOf(right.lifecycle);
|
|
482
492
|
if (lifecycleCompare !== 0)
|
|
483
493
|
return lifecycleCompare;
|
|
484
494
|
return left.runId.localeCompare(right.runId);
|
|
@@ -16,6 +16,8 @@ import { assertSkillSnapshotCoversSpec, buildNodePromptFromSnapshot, isDagSkillS
|
|
|
16
16
|
import { resolveDagSkillInstructions, skillInstructionMetadata, } from "./skill-instructions.js";
|
|
17
17
|
import { parseRepairArtifactFromText, resolveRepairTaskForGate, validateRepairArtifactScope, } from "./repair-artifact.js";
|
|
18
18
|
import { resolveModelForTask, } from "./types.js";
|
|
19
|
+
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
20
|
+
import { resolveExecutorThinkingMatrix } from "../../executors/model-routing.js";
|
|
19
21
|
export function buildNodePrompt(spec, task, upstream, options) {
|
|
20
22
|
const policy = resolveContextPolicy(spec);
|
|
21
23
|
return buildDagNodePromptEnvelope({
|
|
@@ -359,6 +361,16 @@ export async function executeDagNode(input) {
|
|
|
359
361
|
node.resolvedSkills = resolvedSkills;
|
|
360
362
|
await writeNodeSkillArtifacts(runDir, nodeId, resolvedSkills);
|
|
361
363
|
const model = resolveModelForTask(task, spec.executorModels);
|
|
364
|
+
let thinking;
|
|
365
|
+
if (task.executor === "pi") {
|
|
366
|
+
try {
|
|
367
|
+
const manifest = await loadHarnessManifest(cwd);
|
|
368
|
+
thinking = resolveExecutorThinkingMatrix(manifest.executors?.pi)[task.complexity];
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// Invalid or missing harness preserves the previous no-override behavior.
|
|
372
|
+
}
|
|
373
|
+
}
|
|
362
374
|
const retryPolicy = task.retryPolicy && isSafeReadOnlyPiRetryCandidate(task)
|
|
363
375
|
? task.retryPolicy
|
|
364
376
|
: undefined;
|
|
@@ -418,6 +430,7 @@ export async function executeDagNode(input) {
|
|
|
418
430
|
task,
|
|
419
431
|
cwd,
|
|
420
432
|
model,
|
|
433
|
+
...(thinking ? { thinking } : {}),
|
|
421
434
|
prompt: buildAttemptPrompt(task, prompt, attemptNumber, previousFailureCategory, previousProtocolReason),
|
|
422
435
|
attempt: attemptNumber,
|
|
423
436
|
reportActivity,
|
|
@@ -74,7 +74,9 @@ export const dagVerdictGateSchema = z.object({
|
|
|
74
74
|
* Prefer listing the post-revision / final reviewer first when both may exist.
|
|
75
75
|
*/
|
|
76
76
|
fallbackFromNodeIds: z
|
|
77
|
-
.array(z
|
|
77
|
+
.array(z
|
|
78
|
+
.string()
|
|
79
|
+
.regex(/^[a-z][a-z0-9-]*$/, "fallbackFromNodeIds must be kebab-case"))
|
|
78
80
|
.optional(),
|
|
79
81
|
accept: z.array(z.string().min(1)).min(1),
|
|
80
82
|
lineMode: z.enum(["first-non-empty", "first-verdict-line"]).optional(),
|
|
@@ -130,15 +132,20 @@ export const dagFrontendPrewriteGateSchema = z.object({
|
|
|
130
132
|
planFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
|
|
131
133
|
reviewFromNodeId: dagFrontendNodeIdSchema,
|
|
132
134
|
reviewFallbackFromNodeIds: z.array(dagFrontendNodeIdSchema).default([]),
|
|
133
|
-
requiredRequirementIds: z
|
|
134
|
-
|
|
135
|
+
requiredRequirementIds: z
|
|
136
|
+
.array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
|
|
137
|
+
.default([]),
|
|
138
|
+
allowedMockStrategies: z
|
|
139
|
+
.array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
|
|
140
|
+
.min(1),
|
|
135
141
|
artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
|
|
136
142
|
outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
|
|
137
143
|
requireSourceFreshness: z.literal(true),
|
|
138
144
|
implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
|
|
139
145
|
openspecCandidatePaths: z
|
|
140
|
-
.array(z
|
|
141
|
-
|
|
146
|
+
.array(z
|
|
147
|
+
.string()
|
|
148
|
+
.refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/ or openspec/project-specs/"))
|
|
142
149
|
.default([]),
|
|
143
150
|
});
|
|
144
151
|
export const dagFrontendLintBaselineSchema = z
|
|
@@ -148,7 +155,8 @@ export const dagFrontendLintBaselineSchema = z
|
|
|
148
155
|
lintEvidence: dagShellVerifyEvidenceSchema,
|
|
149
156
|
})
|
|
150
157
|
.strict();
|
|
151
|
-
export const dagFrontendVerificationBundleSchema = z
|
|
158
|
+
export const dagFrontendVerificationBundleSchema = z
|
|
159
|
+
.object({
|
|
152
160
|
schemaVersion: z.literal(1),
|
|
153
161
|
mockCommands: z.array(z.string()).default([]),
|
|
154
162
|
lintCommands: z.array(z.string().min(1)).optional(),
|
|
@@ -161,7 +169,8 @@ export const dagFrontendVerificationBundleSchema = z.object({
|
|
|
161
169
|
lintBaselineNodeId: dagFrontendNodeIdSchema.optional(),
|
|
162
170
|
writerNodeIds: z.array(dagFrontendNodeIdSchema).optional(),
|
|
163
171
|
mode: z.enum(["initial", "repair"]),
|
|
164
|
-
})
|
|
172
|
+
})
|
|
173
|
+
.superRefine((bundle, context) => {
|
|
165
174
|
const groups = [
|
|
166
175
|
["mock", bundle.mockCommands, bundle.mockEvidence],
|
|
167
176
|
["lint", bundle.lintCommands ?? [], bundle.lintEvidence],
|
|
@@ -169,23 +178,48 @@ export const dagFrontendVerificationBundleSchema = z.object({
|
|
|
169
178
|
["behavior", bundle.behaviorCommands, bundle.behaviorEvidence],
|
|
170
179
|
];
|
|
171
180
|
for (const [name, commands, evidence] of groups) {
|
|
172
|
-
if ((name === "mock" || name === "lint") &&
|
|
181
|
+
if ((name === "mock" || name === "lint") &&
|
|
182
|
+
commands.length === 0 &&
|
|
183
|
+
!evidence)
|
|
173
184
|
continue;
|
|
174
185
|
if (!evidence) {
|
|
175
|
-
context.addIssue({
|
|
186
|
+
context.addIssue({
|
|
187
|
+
code: z.ZodIssueCode.custom,
|
|
188
|
+
path: [`${name}Evidence`],
|
|
189
|
+
message: `${name} evidence is required when commands are configured`,
|
|
190
|
+
});
|
|
176
191
|
continue;
|
|
177
192
|
}
|
|
178
193
|
if (evidence.commandCount !== commands.length) {
|
|
179
|
-
context.addIssue({
|
|
194
|
+
context.addIssue({
|
|
195
|
+
code: z.ZodIssueCode.custom,
|
|
196
|
+
path: [`${name}Evidence`, "commandCount"],
|
|
197
|
+
message: `${name} commandCount must match commands`,
|
|
198
|
+
});
|
|
180
199
|
}
|
|
181
200
|
if (evidence.commandLabels.length !== commands.length) {
|
|
182
|
-
context.addIssue({
|
|
201
|
+
context.addIssue({
|
|
202
|
+
code: z.ZodIssueCode.custom,
|
|
203
|
+
path: [`${name}Evidence`, "commandLabels"],
|
|
204
|
+
message: `${name} commandLabels must match commands`,
|
|
205
|
+
});
|
|
183
206
|
}
|
|
184
|
-
if (evidence.commandTexts.length !== commands.length ||
|
|
185
|
-
|
|
207
|
+
if (evidence.commandTexts.length !== commands.length ||
|
|
208
|
+
evidence.commandTexts.some((command, index) => command !== commands[index])) {
|
|
209
|
+
context.addIssue({
|
|
210
|
+
code: z.ZodIssueCode.custom,
|
|
211
|
+
path: [`${name}Evidence`, "commandTexts"],
|
|
212
|
+
message: `${name} commandTexts must exactly match commands`,
|
|
213
|
+
});
|
|
186
214
|
}
|
|
187
|
-
if (new Set(evidence.commandLabels).size !==
|
|
188
|
-
|
|
215
|
+
if (new Set(evidence.commandLabels).size !==
|
|
216
|
+
evidence.commandLabels.length ||
|
|
217
|
+
evidence.commandLabels.some((label) => !label.trim())) {
|
|
218
|
+
context.addIssue({
|
|
219
|
+
code: z.ZodIssueCode.custom,
|
|
220
|
+
path: [`${name}Evidence`, "commandLabels"],
|
|
221
|
+
message: `${name} commandLabels must be non-empty and unique`,
|
|
222
|
+
});
|
|
189
223
|
}
|
|
190
224
|
}
|
|
191
225
|
if ((bundle.lintCommands?.length ?? 0) > 0) {
|
|
@@ -318,9 +352,12 @@ export const dagShellConfigSchema = z.object({
|
|
|
318
352
|
commands: z.array(z.string()).default([]),
|
|
319
353
|
preset: dagShellPresetSchema.optional(),
|
|
320
354
|
verdictGate: dagVerdictGateSchema.optional(),
|
|
321
|
-
projectGovernanceGate: z
|
|
355
|
+
projectGovernanceGate: z
|
|
356
|
+
.object({
|
|
322
357
|
contextPath: z.literal(".runtime/project-governance-context.json"),
|
|
323
|
-
})
|
|
358
|
+
})
|
|
359
|
+
.strict()
|
|
360
|
+
.optional(),
|
|
324
361
|
requirementCoverageGate: dagRequirementCoverageGateSchema.optional(),
|
|
325
362
|
jsonArtifactGate: dagJsonArtifactGateSchema.optional(),
|
|
326
363
|
frontendPrewriteGate: dagFrontendPrewriteGateSchema.optional(),
|
|
@@ -333,7 +370,8 @@ export const dagShellConfigSchema = z.object({
|
|
|
333
370
|
frontendTestHtmlReport: dagFrontendTestHtmlReportSchema.optional(),
|
|
334
371
|
backendTestPipeline: dagBackendTestPipelineSchema.optional(),
|
|
335
372
|
/** JaCoCo coverage collection for backend-test (Java services). When set, node 7 dumps coverage over TCP from a JaCoCo tcpserver agent and feeds it to the L-5 dashboard. */
|
|
336
|
-
jacocoCoverage: z
|
|
373
|
+
jacocoCoverage: z
|
|
374
|
+
.object({
|
|
337
375
|
/** JaCoCo tcpserver endpoint, e.g. "host:6300". */
|
|
338
376
|
endpoint: z.string().min(1),
|
|
339
377
|
/** Absolute path to jacococli.jar on this machine, used to convert .exec → jacoco.xml. */
|
|
@@ -342,7 +380,9 @@ export const dagShellConfigSchema = z.object({
|
|
|
342
380
|
includes: z.string().min(1).optional().default("*"),
|
|
343
381
|
/** TCP connect timeout in ms. Defaults to 5000. */
|
|
344
382
|
connectTimeoutMs: z.number().int().positive().optional().default(5000),
|
|
345
|
-
})
|
|
383
|
+
})
|
|
384
|
+
.strict()
|
|
385
|
+
.optional(),
|
|
346
386
|
verifyEvidence: dagShellVerifyEvidenceSchema.optional(),
|
|
347
387
|
repairArtifactGate: dagRepairArtifactGateSchema.optional(),
|
|
348
388
|
/** fail (default): any nonzero command fails the node. record: finish node FINISHED with failure facts for downstream assess/repair. */
|
|
@@ -187,10 +187,32 @@
|
|
|
187
187
|
"enum": ["standard-dag", "review-gated-dag", "supervised-implementation"],
|
|
188
188
|
"description": "某个治理 profile 选择的 DAG 生成模板。"
|
|
189
189
|
},
|
|
190
|
+
"executorTierModel": {
|
|
191
|
+
"oneOf": [
|
|
192
|
+
{ "type": "string" },
|
|
193
|
+
{
|
|
194
|
+
"type": "object",
|
|
195
|
+
"additionalProperties": false,
|
|
196
|
+
"required": ["model"],
|
|
197
|
+
"properties": {
|
|
198
|
+
"model": {
|
|
199
|
+
"type": "string",
|
|
200
|
+
"minLength": 1,
|
|
201
|
+
"description": "模型 id 或 provider/model 限定引用。"
|
|
202
|
+
},
|
|
203
|
+
"thinking": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"description": "可选 Pi thinking level(如 max/low)。省略时不强制覆盖 Pi 默认。"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
"description": "executor 复杂度档模型:字符串 model id,或 { model, thinking? }。字面值 default 表示不覆盖。"
|
|
211
|
+
},
|
|
190
212
|
"executor": {
|
|
191
213
|
"type": "object",
|
|
192
214
|
"additionalProperties": false,
|
|
193
|
-
"description": "executor 设置。DAG 模型选择优先读取 LOW/MED/HIGH,其次读取 defaultModel,最后回退到 loop-agent runtime 默认矩阵。字面值 default 表示不覆盖。",
|
|
215
|
+
"description": "executor 设置。DAG 模型选择优先读取 LOW/MED/HIGH,其次读取 defaultModel,最后回退到 loop-agent runtime 默认矩阵。字面值 default 表示不覆盖。LOW/MED/HIGH 可为字符串或 { model, thinking? } 对象。",
|
|
194
216
|
"properties": {
|
|
195
217
|
"description": { "type": "string" },
|
|
196
218
|
"enabled": { "type": "boolean" },
|
|
@@ -200,16 +222,16 @@
|
|
|
200
222
|
"default": "default"
|
|
201
223
|
},
|
|
202
224
|
"LOW": {
|
|
203
|
-
"
|
|
204
|
-
"description": "LOW 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel
|
|
225
|
+
"$ref": "#/$defs/executorTierModel",
|
|
226
|
+
"description": "LOW 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel。可为模型字符串,或 { model, thinking? } 对象。"
|
|
205
227
|
},
|
|
206
228
|
"MED": {
|
|
207
|
-
"
|
|
208
|
-
"description": "MED 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel
|
|
229
|
+
"$ref": "#/$defs/executorTierModel",
|
|
230
|
+
"description": "MED 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel。可为模型字符串,或 { model, thinking? } 对象。"
|
|
209
231
|
},
|
|
210
232
|
"HIGH": {
|
|
211
|
-
"
|
|
212
|
-
"description": "HIGH 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel
|
|
233
|
+
"$ref": "#/$defs/executorTierModel",
|
|
234
|
+
"description": "HIGH 复杂度 DAG task 的模型覆盖值,优先级高于 defaultModel。可为模型字符串,或 { model, thinking? } 对象。"
|
|
213
235
|
},
|
|
214
236
|
"requiresApiKey": {
|
|
215
237
|
"type": "string",
|
|
@@ -86,19 +86,24 @@ pwd → `README.md` → `harness.json` → `__LOOP_AGENT_GOVERNANCE_ROOT__/READM
|
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
loop-agent new-task <task-id> "任务标题"
|
|
89
|
-
#
|
|
89
|
+
# 推荐默认:详细 PRD → import → prepare(无需手写两个 source;默认无 LLM 写 source)
|
|
90
|
+
loop-agent import-prd <task-id> --file <path-to-prd.md>
|
|
90
91
|
# 非微小或跨会话任务(推荐):loop-agent plan create <plan-id> "<title>"
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
loop-agent task source prepare <task-id> \
|
|
93
|
+
--use-imported-prd \
|
|
94
|
+
--allowed-path "<glob>" \
|
|
95
|
+
--forbidden-path ".harness/**" \
|
|
96
|
+
--verify "typecheck:npm run typecheck" \
|
|
97
|
+
--apply --json
|
|
93
98
|
loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
94
99
|
loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance
|
|
95
100
|
loop-agent run-dag --dag .harness/tasks/<task-id>/dag.json --cwd .
|
|
96
101
|
# 有 plan 时收尾:loop-agent plan complete <plan-id> --summary "..."
|
|
97
102
|
```
|
|
98
103
|
|
|
99
|
-
`source/需求.md` 与 `source/执行约束.md`
|
|
104
|
+
`source/需求.md` 与 `source/执行约束.md` 仍必需(M8/M9),但默认由 `task source prepare --apply` 投影生成,而不是主会话手写。有原始 PRD 时优先 `import-prd` 原样归档。`import-prd` / `plan create` 不是 `dag run-task` 的硬依赖。写入前同步 `task.json.allowedPaths` / `task.json.forbiddenPaths` 并审查 writer `writeSet`。
|
|
100
105
|
|
|
101
|
-
|
|
106
|
+
凡是影响项目公共契约、执行入口、交付流水线、自动化/治理、数据模型、安全或权限模型、跨模块行为、用户可见工作流的改动,都必须在编辑实现文件前先创建任务、完成 source prepare(或等价 managed contract)、生成 DAG,并审查 DAG/writeSet。
|
|
102
107
|
|
|
103
108
|
### 任务类型路由(taskKind)
|
|
104
109
|
|
package/harness.json
CHANGED
|
@@ -58,9 +58,8 @@
|
|
|
58
58
|
"executors": {
|
|
59
59
|
"pi": {
|
|
60
60
|
"description": "Pi planning, review, diagnosis, and bounded writing when DAG toolProfile=write",
|
|
61
|
-
"defaultModel": "minimax-m3",
|
|
62
61
|
"LOW": "minimax-m3",
|
|
63
|
-
"MED": "
|
|
62
|
+
"MED": {"model": "deepseek/deepseek-v4-flash","thinking": "max"},
|
|
64
63
|
"HIGH": "gpt-5.6-sol"
|
|
65
64
|
}
|
|
66
65
|
}
|
package/package.json
CHANGED
|
@@ -27,8 +27,11 @@ Entry: routing and hard rules. Required details come from frontmatter references
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
loop-agent new-task <task-id> "Title"
|
|
30
|
-
#
|
|
31
|
-
#
|
|
30
|
+
# PRD-first default: detailed PRD → import-prd → task source prepare --apply
|
|
31
|
+
# no hand-written/LLM source by default
|
|
32
|
+
loop-agent import-prd <task-id> --file <prd.md>
|
|
33
|
+
loop-agent task source prepare <task-id> --use-imported-prd --allowed-path "<glob>" --apply --json
|
|
34
|
+
# plan create for non-trivial work (see references/source-and-plan-practice.md)
|
|
32
35
|
loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
33
36
|
# default draft: .harness/tasks/<task-id>/dag.json
|
|
34
37
|
loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance
|
|
@@ -37,9 +37,14 @@ loop-agent doctor
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
loop-agent new-task <task-id> "Task Title"
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
# 推荐默认:详细 PRD → import → prepare(无需手写两 source;默认无 LLM 写 source)
|
|
41
|
+
loop-agent import-prd <task-id> --file <path-to-prd.md>
|
|
42
|
+
loop-agent task source prepare <task-id> \
|
|
43
|
+
--use-imported-prd \
|
|
44
|
+
--allowed-path "<glob>" \
|
|
45
|
+
--forbidden-path ".harness/**" \
|
|
46
|
+
--verify "typecheck:npm run typecheck" \
|
|
47
|
+
--apply --json
|
|
43
48
|
# 非微小 / 跨会话(推荐默认):
|
|
44
49
|
# loop-agent plan create <plan-id> "<title>"
|
|
45
50
|
loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
@@ -168,7 +173,7 @@ loop-agent new-task <task-id> "Task Title"
|
|
|
168
173
|
loop-agent import-prd <task-id> --file ai_workspace/loop-agent/path/to-prd.md [--name requirement] [--json]
|
|
169
174
|
```
|
|
170
175
|
|
|
171
|
-
把用户原始 PRD **原样复制** 到 `.harness/tasks/<task-id>/source/references/`,并写入 `source/source-manifest.json`(含 SHA-256)与 `task.json.referenceDocs
|
|
176
|
+
把用户原始 PRD **原样复制** 到 `.harness/tasks/<task-id>/source/references/`,并写入 `source/source-manifest.json`(含 SHA-256)与 `task.json.referenceDocs`。此步骤不调用模型、不改写内容。随后用 `task source prepare --use-imported-prd --apply` 派生 managed `source/需求.md` / `执行约束.md`;冲突时以 `source/references/*` 为准。
|
|
172
177
|
|
|
173
178
|
`referenceDocs` 是 `{ path, name? }[]` 对象数组,不是路径字符串数组;`import-prd` 会确定性写入正确结构。
|
|
174
179
|
|
|
@@ -213,22 +218,19 @@ one-shot run evidence 位于 `.harness/runs/{active,completed,failed}/<run-id>/`
|
|
|
213
218
|
|
|
214
219
|
### 运行任何 step 前:准备 source materials
|
|
215
220
|
|
|
216
|
-
`new-task` 之后,先归档原始 PRD
|
|
221
|
+
`new-task` 之后,先归档原始 PRD,再 `task source prepare` 派生执行契约:
|
|
217
222
|
|
|
218
223
|
```bash
|
|
219
224
|
loop-agent import-prd <task-id> --file <path-to-original-prd.md>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
225
|
+
loop-agent task source prepare <task-id> \
|
|
226
|
+
--use-imported-prd \
|
|
227
|
+
--allowed-path "<glob>" \
|
|
228
|
+
--forbidden-path ".harness/**" \
|
|
229
|
+
--verify "typecheck:npm run typecheck" \
|
|
230
|
+
--apply --json
|
|
223
231
|
```
|
|
224
232
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
若 task 有硬约束(仅允许特定文件、禁止改动),另加:
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
cat > <repo-root>/.harness/tasks/<task-id>/source/执行约束.md
|
|
231
|
-
```
|
|
233
|
+
默认无 LLM 写 source;工程边界用 flags 显式给出。不要让 AI 直接改写 `source/references/*`。高级用户仍可手工编辑后 `task contract adopt`。
|
|
232
234
|
|
|
233
235
|
### Feature-study workflow(参考代码 → 轻量实现)
|
|
234
236
|
|
|
@@ -54,10 +54,9 @@ Minimum governed path:
|
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
loop-agent new-task <task-id> "Task Title" [--repo-root <target-repo>]
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# write <target-repo>/.harness/tasks/<task-id>/source/执行约束.md
|
|
57
|
+
# PRD-first: import-prd → task source prepare --apply
|
|
58
|
+
loop-agent import-prd <task-id> --file <path-to-original-prd.md> [--repo-root <target-repo>]
|
|
59
|
+
loop-agent task source prepare <task-id> --use-imported-prd --allowed-path "<glob>" --apply --json [--repo-root <target-repo>]
|
|
61
60
|
|
|
62
61
|
loop-agent dag run-task <task-id> \
|
|
63
62
|
--profile auto \
|
|
@@ -67,7 +67,7 @@ loop-agent run-dag --dag <temp-dir>/hybrid-dag.json --init-only --canvas-path <t
|
|
|
67
67
|
|
|
68
68
|
- **常规 validation**:`dag validate --dag <path>` 做 schema/topology/ranks。JSON 输出含 `governanceProfile`(确定性 `minimal|standard|reviewed|supervised` 推断,含 `process` / `delivery` / `codeChange` signal 与 `reasons`),及 model-matrix drift、governance lint(如 read-only artifact-boundary drift 或 DAG 内 `check-repo.sh` shell env drift)的 warnings。手写临时 DAG spec 执行前用 `dag validate --dag <path> --strict-models`;governance warning 应 fail fast 时加 `--strict-governance`。含 `executor: "cursor"` 的旧 DAG 会在 schema 校验失败;默认生成 DAG 使用 `pi` read-only / Pi write profile / shell。仅当有意在 `.harness/dag-runs/active/` 要 active run snapshot 时用 `run-dag --dry-run`。
|
|
69
69
|
- **Governance profile 推断与 routing(code vs skill 分工)**:`./src/workflows/dag/governance-profile.ts` 从 DAG 结构与 write scope 做 **硬确定性推断**。JSON 输出 **报告** `process` / `delivery` / `codeChange` signal 与人类可读 `reasons`;`profile` tier(`minimal|standard|reviewed|supervised`)仅由该模块 code rule 选择(如多个 exclusive writer、repair node、review-gate topology、`loop-agent-runtime-paths`、`scripts-ci-harness-paths`、weak post-implementation shell verification、supervised topology)。baseline `forbiddenPaths`(`.harness/**`、`.harness/dag-runs/**`、`artifacts/**`)是默认 governance,**本身不是** process-risk signal。skill prompt 与本 reference **解释** tier 并摘要 profile 选择原因;不替代 code 推断。`dag run-task` 转发 embedded validate step 的同一 candidate `governanceProfile`。`dag run-task --profile auto` 先将 candidate profile 经 `harness.json.workflowPolicy.dag.profileRouting` 映射,再在 candidate delivery signal 含 `loop-agent-runtime-paths`、`scripts-ci-harness-paths` 或 `public-contract-paths` 时应用 M4 `supervised-quality-gate` promotion;`profileRouting.routingReasons` 记录确定性 reason。无 profile `dag run-task <task-id>` 仍为 standard-compatible;显式 `--profile minimal|standard|reviewed|supervised` 与自动 promotion 记录治理强度,已识别的前端业务 workflow 仍使用前端专用模板。高风险 task 应用 `--profile auto` 或显式 `--profile supervised`,而非显式 `--profile reviewed`。
|
|
70
|
-
- **Executor model routing**:DAG spec 选 `executor` 与 `complexity`,可通过 `executorModels.pi`
|
|
70
|
+
- **Executor model routing**:DAG spec 选 `executor` 与 `complexity`,可通过 `executorModels.pi` 覆盖模型。值写成 `provider/model` 时显式选择 Pi provider(只分割第一个 `/`);裸模型名继续走内置映射或默认 `wizard-local`。默认 routing:Pi LOW=`gpt-5.3-codex-spark`、MED=`gpt-5.5`、HIGH=`gpt-5.5`。`shell` 不用 model,忽略 `executorModels`。
|
|
71
71
|
- **Active visibility**:真实 `run-dag` execution 在 run/node 转换时写 active `state.json`,归档前 core runner 暴露 isolated `DagRunObserver` hook 供 derived view。`.harness/dag-runs/completed/<run-id>/` / `paused/<run-id>/` 仍是 source of truth;observer 输出非 canonical。
|
|
72
72
|
- **可选 Canvas**:传 `--canvas-path <abs-path>` 或 `--canvas <name>` 输出 derived `.canvas.tsx` live view。省略 flag 行为不变。`--init-only` + Canvas 无需 `CURSOR_API_KEY`。
|
|
73
73
|
|
|
@@ -232,7 +232,7 @@ review-heavy DAG 中长 shell stdout 可能掩盖 proof 时,用 **evidence-sum
|
|
|
232
232
|
| --- | ------- | -------- |
|
|
233
233
|
| 1 | Topology | 优先 same-rank parallel read-only scout/review;仅 output 真正需要时加 `depends_on` |
|
|
234
234
|
| 2 | Executor | 每个 task 显式声明 `executor`;`defaults.executor` 是 schema metadata,非 runtime fallback |
|
|
235
|
-
| 3 | Model routing | 用 node `complexity` + `executorModels
|
|
235
|
+
| 3 | Model routing | 用 node `complexity` + `executorModels`;需要固定 provider 时写 `provider/model`,裸模型名保持兼容;Pi MED 与 HIGH 默认都使用 `gpt-5.5`,切换 complexity 不会切换默认模型;provider 临时不可用时仅使用有记录、有限范围的 worker `--pi-model` smoke override,勿 mutate canonical model matrix |
|
|
236
236
|
| 4 | Read-only output | read-only / Pi node 在 **node output** 返回发现;runner 归档于 `.harness/dag-runs/<run-id>/<node-id>/` |
|
|
237
237
|
| 5 | Root `artifacts/**` | 非 read-only handoff target;持久记录去 `ai_workspace/loop-agent/reports/`、`ai_workspace/loop-agent/progress/`,或 narrow exclusive `writeSet` 写 legacy 摘要并记录迁移计划 |
|
|
238
238
|
| 6 | DAG Cursor artifacts | 写入 `.harness/dag-runs/<state>/<run-id>/artifacts/<node-id>/`;不得写入 `./artifacts/**` |
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
Agent DAG node 的模型来自 DAG JSON 中的 `executorModels`,并由 `dag validate --strict-models` 检查 canonical matrix 漂移。若变更模型配置,须同步更新 `harness.json`、相关测试、repo docs 与本 skill。
|
|
19
19
|
|
|
20
|
+
`executorModels.pi` 的每档值支持两种形式:裸模型名继续走内置映射或默认 `wizard-local`;`provider/model` 限定引用则显式选择 Pi provider,只在第一个 `/` 处分割并保留模型 ID 的剩余部分。provider 定义与凭证仍归 Pi 的 `~/.pi/agent/models.json` 和环境配置所有。例如不同档可分别写 `wizard-local/glm-5.2` 与 `deepseek/deepseek-v4-flash`。
|
|
21
|
+
|
|
20
22
|
`pi-prompt` 是独立 one-shot helper,不使用 `harness.json.models` 或 DAG `executorModels`。当前默认是 `wizard-local/glm-5.2`;高复杂度 one-shot 显式传 `--model gpt-5.5`。Agent DAG `pi` executor 的 canonical matrix 保持;读写 profile 共用同一矩阵:
|
|
21
23
|
|
|
22
24
|
```json
|
|
@@ -27,7 +27,7 @@ DAG run、promotion、closeout 和最终验证完成后:
|
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
1. new-task <id>
|
|
30
|
-
2. 有 PRD 文件:import-prd
|
|
30
|
+
2. 有 PRD 文件:import-prd → task source prepare --use-imported-prd --apply(默认不手写两 source)
|
|
31
31
|
3. 非微小:plan create(或挂到已有 active plan)
|
|
32
32
|
4. dag run-task <id> --profile auto --strict-models
|
|
33
33
|
5. dag validate --dag .harness/tasks/<id>/dag.json --strict-models --strict-governance
|
|
@@ -11,7 +11,7 @@ Task 目录布局:`task-workflow.md`。
|
|
|
11
11
|
| 层 | 命令 / 路径 | 作用 | 主路径 DAG 是否强制 |
|
|
12
12
|
| --- | --- | --- | --- |
|
|
13
13
|
| Source 事实 | `import-prd` → `source/references/*` + `source-manifest.json` | 原始 PRD **不可变**归档 | 否(有 PRD 文件时**强烈推荐**) |
|
|
14
|
-
| Source 契约 |
|
|
14
|
+
| Source 契约 | `task source prepare --apply` 投影 `source/需求.md`、`执行约束.md` + managed paths | DAG 生成与验收真源 | **是**(至少 `需求.md`;默认不手写) |
|
|
15
15
|
| Exec-plan | `plan create` / `plan complete` / `plan check` | 仓库级计划索引与交接 | 否(**非微小**推荐) |
|
|
16
16
|
| DAG 运行时 | `dag run-task` → `dag validate` → `run-dag` | 可执行编排 | **是**(常规实现) |
|
|
17
17
|
|
|
@@ -47,7 +47,8 @@ Task 目录布局:`task-workflow.md`。
|
|
|
47
47
|
```bash
|
|
48
48
|
loop-agent new-task <task-id> "简短标题"
|
|
49
49
|
loop-agent import-prd <task-id> --file <path-to-original-prd.md> [--json]
|
|
50
|
-
|
|
50
|
+
loop-agent task source prepare <task-id> --use-imported-prd --allowed-path "<glob>" --apply --json
|
|
51
|
+
# 默认无 LLM 写 source;工程边界用 flags 显式给出
|
|
51
52
|
# .harness/tasks/<task-id>/source/需求.md
|
|
52
53
|
# .harness/tasks/<task-id>/source/执行约束.md
|
|
53
54
|
# 同步 task.json.allowedPaths / forbiddenPaths
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
loop-agent new-task <task-id> "Task Title"
|
|
11
|
-
#
|
|
11
|
+
# 推荐:详细 PRD → import → prepare(无需手写两 source)
|
|
12
|
+
loop-agent import-prd <task-id> --file <prd>
|
|
13
|
+
loop-agent task source prepare <task-id> --use-imported-prd --allowed-path "<glob>" --apply --json
|
|
12
14
|
# 非微小:loop-agent plan create <plan-id> "<title>"(可与 task 解耦,见 source-and-plan-practice.md)
|
|
13
15
|
loop-agent dag run-task <task-id> --profile auto --strict-models
|
|
14
16
|
loop-agent dag validate --dag .harness/tasks/<task-id>/dag.json --strict-models --strict-governance
|
|
@@ -35,10 +37,10 @@ loop-agent run-dag --dag .harness/tasks/<task-id>/dag.json --cwd <repo-root>
|
|
|
35
37
|
task.json
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
- 用户原始 PRD 用 `loop-agent import-prd <task-id> --file <prd>` 归档到 `source/references/`,禁止 AI 改写。**有文件就 import
|
|
39
|
-
- `需求.md`
|
|
40
|
-
-
|
|
41
|
-
- 若 `ai_workspace/loop-agent/` 已有权威 plan/spec/PRD,优先 `import-prd`
|
|
40
|
+
- 用户原始 PRD 用 `loop-agent import-prd <task-id> --file <prd>` 归档到 `source/references/`,禁止 AI 改写。**有文件就 import**。
|
|
41
|
+
- 默认用 `task source prepare --use-imported-prd --apply` 投影 `需求.md` / `执行约束.md` 与 managed paths;**不要**默认让 LLM/主会话手写两 source。
|
|
42
|
+
- 无独立 PRD 的微小任务可用 `task source prepare --from-text ... --apply` escape hatch(见 `source-and-plan-practice.md`)。
|
|
43
|
+
- 若 `ai_workspace/loop-agent/` 已有权威 plan/spec/PRD,优先 `import-prd` 复制,再 prepare 派生薄契约;避免把长 PRD 直接改写成唯一 source。
|
|
42
44
|
- 仓库级 exec-plan(`plan create`)与 harness task **解耦**:非微小实现应有 plan 或复用 active plan;微小任务可不建 plan。
|
|
43
45
|
- Worker / TaskSpec materialize 路径会把 `source_docs` 复制到 `source/references/`,并在派生 `需求.md` 顶部声明“冲突以 references 为准”;`acceptance_refs` 应展开为短摘要而不只写 ID。
|
|
44
46
|
- review 节点必须三方对照:`source/references/*`(尤其 requirement/acceptance)、派生 `需求.md`、以及实现/验证证据。
|