@tea-agent/loop-agent 0.10.0 → 0.11.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.
- package/AGENTS.md +10 -2
- package/CHANGELOG.md +67 -25
- package/README.md +82 -11
- package/dist/application/dag/args.js +1 -12
- package/dist/application/dag/generate-task-dag.js +23 -2
- package/dist/application/dag/run-dag.js +1 -27
- package/dist/application/dag/validate-dag.js +2 -2
- package/dist/application/loop/run-action.js +0 -4
- package/dist/cli/command-definitions.js +44 -16
- package/dist/cli/program.js +40 -23
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +58 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli.js +33 -0
- package/dist/commands/cursor-prompt.js +42 -82
- package/dist/commands/dag-approve.js +36 -0
- package/dist/commands/delegate.js +75 -77
- package/dist/commands/doctor.js +0 -18
- package/dist/commands/init.js +476 -91
- package/dist/commands/instructions.js +7 -10
- package/dist/commands/loop.js +4 -20
- package/dist/commands/plan.js +50 -0
- package/dist/executors/config-core.js +0 -51
- package/dist/executors/dag-pi-executor.js +1 -1
- package/dist/executors/dag.js +0 -1
- package/dist/executors/index.js +0 -2
- package/dist/executors/model-routing.js +9 -9
- package/dist/executors/shell-executor.js +1 -1
- package/dist/governance/checks.js +6 -3
- package/dist/governance/exec-plans.js +545 -0
- package/dist/governance/manifest-types.js +24 -2
- package/dist/infrastructure/harness/loop-action-store.js +0 -3
- package/dist/records/harvest.js +2 -23
- package/dist/records/one-shot-runs.js +1 -1
- package/dist/shared/artifacts-core.js +24 -5
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +353 -0
- package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +28 -12
- package/dist/task/delegate.js +9 -21
- package/dist/task/runtime.js +1 -2
- package/dist/worker/cli.js +29 -2
- package/dist/worker/delivery/final-verification.js +47 -11
- package/dist/worker/delivery/package.js +63 -10
- package/dist/worker/feature/run.js +60 -8
- package/dist/worker/loop-agent/loop-agent-client.js +329 -126
- package/dist/worker/observability/read-model.js +27 -1
- package/dist/worker/observe/static/app.js +326 -45
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/styles.css +5 -4
- package/dist/worker/preflight.js +49 -1
- package/dist/worker/run-task/run-task.js +22 -12
- package/dist/worker/runner/run-ready.js +76 -12
- package/dist/worker/task-spec/schema.js +0 -1
- package/dist/workflows/dag/convergence/controller.js +1 -1
- package/dist/workflows/dag/executor-registry.js +0 -2
- package/dist/workflows/dag/init-hybrid.js +402 -25
- package/dist/workflows/dag/node-execution.js +61 -7
- package/dist/workflows/dag/runner.js +45 -17
- package/dist/workflows/dag/scheduler.js +7 -2
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +5 -4
- package/dist/workflows/dag/skill-snapshot.js +527 -0
- package/dist/workflows/dag/types.js +42 -9
- package/dist/workflows/dag/validate.js +5 -8
- package/dist/workflows/loop/actions/dag-action.js +0 -2
- package/dist/workflows/loop/actions/shared.js +1 -1
- package/dist/workflows/loop/actions.js +14 -31
- package/dist/workflows/loop/benchmark.js +1 -1
- package/dist/workflows/loop/index.js +1 -1
- package/dist/workflows/loop/policy/auto-policy.js +22 -14
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/docs/README.md +36 -33
- package/docs/agent-dag-recovery-playbook.md +1 -1
- package/docs/agent-dag-runner.md +2 -2
- package/docs/architecture/README.md +26 -0
- package/docs/architecture/dag-execution.md +134 -0
- package/docs/architecture/evolution.md +52 -0
- package/docs/architecture/facts-and-state.md +58 -0
- package/docs/architecture/runtime-boundaries.md +45 -17
- package/docs/architecture/system-overview.md +93 -0
- package/docs/architecture/worker-and-feature.md +81 -0
- package/docs/cursor-prompt-sidecar.md +36 -0
- package/docs/decisions/README.md +13 -1
- package/docs/design/README.md +42 -21
- package/docs/development-principles.md +2 -2
- package/docs/exec-plans/active/README.md +2 -2
- package/docs/exec-plans/completed/README.md +12 -0
- package/docs/feature-workflow.md +50 -4
- package/docs/harness-methodology-debugging.md +1 -1
- package/docs/harness-methodology-tdd.md +3 -3
- package/docs/init-surface.manifest.json +60 -25
- package/docs/loop-agent-harness.md +28 -4
- package/docs/progress/README.md +32 -1
- package/docs/reports/README.md +84 -18
- package/docs/skills/README.md +2 -1
- package/docs/skills/vetted-skill-registry.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +6 -6
- package/docs/templates/agent-dag.base.json +0 -5
- package/docs/templates/agent-dag.final-verification.json +0 -5
- package/docs/templates/agent-dag.schema.json +1 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -6
- package/docs/templates/frontend-design-contract.md +33 -0
- package/docs/templates/frontend-task-constraints.md +25 -0
- package/docs/templates/frontend-task-requirement.md +61 -0
- package/docs/templates/harness.schema.json +10 -12
- package/docs/templates/hybrid-dag.json +1 -6
- package/docs/templates/interactive-ui-round2-experiment.md +1 -1
- package/docs/templates/product-line/task.yaml +0 -1
- package/docs/templates/project-start-checklist.md +2 -2
- package/docs/templates/worker-dogfood-evidence.md +28 -0
- package/docs/templates/worker-dogfood-setup.md +20 -0
- package/docs/verification-matrix.md +10 -0
- package/examples/decision-gate-agent-dag.json +87 -33
- package/examples/example-dag.json +0 -5
- package/examples/hybrid-loop-agent-dag.json +0 -5
- package/harness.json +7 -15
- package/package.json +22 -46
- package/scripts/check-product-line-docs.sh +10 -7
- package/skills/agent-worker/SKILL.md +37 -0
- package/skills/agent-worker/references/agent-worker-operator.md +43 -0
- package/skills/frontend-design-review/SKILL.md +59 -0
- package/skills/frontend-design-review/references/review-checklist.md +37 -0
- package/skills/frontend-implementation/SKILL.md +51 -0
- package/skills/frontend-implementation/references/code-standards.md +34 -0
- package/skills/frontend-implementation/references/design-spec.md +46 -0
- package/skills/frontend-implementation/references/node-contracts.md +32 -0
- package/skills/frontend-review/SKILL.md +53 -0
- package/skills/frontend-review/references/review-findings.md +42 -0
- package/skills/frontend-verification/SKILL.md +40 -0
- package/skills/frontend-verification/references/verification-checklist.md +56 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +88 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +60 -0
- package/skills/loop-agent/SKILL.md +11 -9
- package/skills/loop-agent/references/command-reference.md +13 -15
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +7 -7
- package/skills/loop-agent/references/hybrid-dag.md +15 -18
- package/skills/loop-agent/references/long-running-loop.md +4 -6
- package/skills/loop-agent/references/multi-worktree.md +6 -6
- package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/dist/commands/cursor-worker.js +0 -43
- package/dist/cursor-worker-entry.js +0 -8
- package/dist/executors/cursor-artifacts.js +0 -33
- package/dist/executors/cursor-execution-log.js +0 -81
- package/dist/executors/cursor-executor-artifacts.js +0 -134
- package/dist/executors/cursor-run.js +0 -115
- package/dist/executors/cursor-tool.js +0 -94
- package/dist/executors/cursor-worker-client.js +0 -223
- package/dist/executors/cursor-worker-protocol.js +0 -18
- package/dist/executors/cursor-worker-server.js +0 -54
- package/dist/executors/cursor-worker.js +0 -3
- package/dist/executors/cursor.js +0 -6
- package/dist/executors/dag-cursor-executor.js +0 -87
- package/dist/workflows/loop/actions/cursor-fix.js +0 -191
- package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
- package/docs/cursor-executor-usage.md +0 -25
- package/docs/dynamic-workflow-dag-engine-roadmap.md +0 -1749
|
@@ -5,10 +5,11 @@ import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/r
|
|
|
5
5
|
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
6
6
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareActiveRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
7
7
|
import { createDagNodeExecutor } from "./executor-registry.js";
|
|
8
|
-
import {
|
|
8
|
+
import { executeDagPiNode } from "../../executors/dag-pi-executor.js";
|
|
9
9
|
import { assertValidDagSpec } from "./validate.js";
|
|
10
10
|
import { normalizeDagPromptSources } from "./prompt-source.js";
|
|
11
11
|
import { relocateConvergenceArtifactPaths, relocateRunArtifactPaths, } from "./upstream-artifacts.js";
|
|
12
|
+
import { createSkillSnapshot, prepareSkillSnapshotForContinuation, writeSkillSnapshot, } from "./skill-snapshot.js";
|
|
12
13
|
import { buildNodePrompt, buildNodePromptWithResolvedSkillInstructions, executeDagNode, } from "./node-execution.js";
|
|
13
14
|
import { runConvergencePassController, shouldEnableDagConvergence, } from "./convergence/controller.js";
|
|
14
15
|
import { executeDagRanksOnce, isConditionSkippedReason, } from "./scheduler.js";
|
|
@@ -162,6 +163,19 @@ export async function runDag(spec, opts) {
|
|
|
162
163
|
const runDir = activeRunDir;
|
|
163
164
|
await prepareActiveRunDir(runDir);
|
|
164
165
|
await writeRunSpec(runDir, spec);
|
|
166
|
+
// Resolve and snapshot all skill profiles before any node executes.
|
|
167
|
+
try {
|
|
168
|
+
const snapshot = await createSkillSnapshot({
|
|
169
|
+
runId: state.runId,
|
|
170
|
+
spec,
|
|
171
|
+
cwd: opts.cwd,
|
|
172
|
+
mode: "run-start",
|
|
173
|
+
});
|
|
174
|
+
state.skillSnapshotRef = await writeSkillSnapshot(runDir, snapshot);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new Error(`failed to create run-start skill snapshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
178
|
+
}
|
|
165
179
|
await writeRunState(runDir, state);
|
|
166
180
|
await notifyRunObserver(opts.observer, "onRunStart", state);
|
|
167
181
|
if (opts.dryRun || opts.initOnly) {
|
|
@@ -190,7 +204,6 @@ export async function runDag(spec, opts) {
|
|
|
190
204
|
});
|
|
191
205
|
}
|
|
192
206
|
finally {
|
|
193
|
-
await stopCursorWorkerIfRunning();
|
|
194
207
|
}
|
|
195
208
|
}
|
|
196
209
|
export async function resumeDagRun(opts) {
|
|
@@ -221,6 +234,25 @@ export async function resumeDagRun(opts) {
|
|
|
221
234
|
catch {
|
|
222
235
|
throw new Error(`dag run ${opts.runId} is missing human-approval.json for node ${decisionNodeId}`);
|
|
223
236
|
}
|
|
237
|
+
// Validate or backfill before mutating runner/status for this resume attempt.
|
|
238
|
+
try {
|
|
239
|
+
const prepared = await prepareSkillSnapshotForContinuation({
|
|
240
|
+
runDir,
|
|
241
|
+
runId: state.runId,
|
|
242
|
+
spec,
|
|
243
|
+
state,
|
|
244
|
+
cwd: state.cwd,
|
|
245
|
+
ref: state.skillSnapshotRef,
|
|
246
|
+
});
|
|
247
|
+
if (prepared.backfilled) {
|
|
248
|
+
console.warn(`[dag resume] warning: run ${state.runId} has no skill snapshot ref; creating a legacy-resume-backfill that freezes remaining nodes only`);
|
|
249
|
+
state.skillSnapshotRef = prepared.ref;
|
|
250
|
+
await writeRunState(runDir, state);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
throw new Error(`skill snapshot validation failed on resume: ${error instanceof Error ? error.message : String(error)}`);
|
|
255
|
+
}
|
|
224
256
|
const maxConcurrent = Math.max(1, opts.maxConcurrent ?? 4);
|
|
225
257
|
state.status = "running";
|
|
226
258
|
const resumedAt = new Date().toISOString();
|
|
@@ -249,7 +281,6 @@ export async function resumeDagRun(opts) {
|
|
|
249
281
|
});
|
|
250
282
|
}
|
|
251
283
|
finally {
|
|
252
|
-
await stopCursorWorkerIfRunning();
|
|
253
284
|
}
|
|
254
285
|
}
|
|
255
286
|
async function executeDagCheckpoint(input) {
|
|
@@ -314,10 +345,10 @@ async function executeDagCheckpoint(input) {
|
|
|
314
345
|
tasksById,
|
|
315
346
|
maxConcurrent,
|
|
316
347
|
persistState,
|
|
317
|
-
createExecuteNodeForRank: (
|
|
348
|
+
createExecuteNodeForRank: (rankWriterNodeIds) => buildRankAwareExecuteNode({
|
|
318
349
|
baseExecuteNode,
|
|
319
350
|
customExecuteNode: input.executeNode,
|
|
320
|
-
|
|
351
|
+
rankWriterNodeIds,
|
|
321
352
|
tasksById,
|
|
322
353
|
meta: { runDir, runId: state.runId, spec },
|
|
323
354
|
}),
|
|
@@ -413,16 +444,10 @@ function finalizeTerminalRunStatus(state, taskCount) {
|
|
|
413
444
|
state.status = "failed";
|
|
414
445
|
}
|
|
415
446
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
if (isCursorWorkerRunning()) {
|
|
419
|
-
await stopCursorWorker();
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
function concurrentSiblingWriteSetsForNode(rankCursorNodeIds, nodeId, tasksById) {
|
|
423
|
-
if (rankCursorNodeIds.length <= 1)
|
|
447
|
+
function concurrentSiblingWriteSetsForNode(rankWriterNodeIds, nodeId, tasksById) {
|
|
448
|
+
if (rankWriterNodeIds.length <= 1)
|
|
424
449
|
return [];
|
|
425
|
-
return
|
|
450
|
+
return rankWriterNodeIds
|
|
426
451
|
.filter((id) => id !== nodeId)
|
|
427
452
|
.map((id) => tasksById.get(id)?.writeSet ?? [])
|
|
428
453
|
.filter((writeSet) => writeSet.length > 0);
|
|
@@ -432,13 +457,16 @@ function buildRankAwareExecuteNode(input) {
|
|
|
432
457
|
return input.baseExecuteNode;
|
|
433
458
|
}
|
|
434
459
|
return async (nodeInput) => {
|
|
435
|
-
|
|
460
|
+
const isPiWriter = nodeInput.task.executor === "pi" &&
|
|
461
|
+
nodeInput.task.toolProfile === "write" &&
|
|
462
|
+
Boolean(nodeInput.model);
|
|
463
|
+
if (!isPiWriter) {
|
|
436
464
|
return input.baseExecuteNode(nodeInput);
|
|
437
465
|
}
|
|
438
466
|
const siblingWriteSets = nodeInput.task.writePolicy === "exclusive"
|
|
439
|
-
? concurrentSiblingWriteSetsForNode(input.
|
|
467
|
+
? concurrentSiblingWriteSetsForNode(input.rankWriterNodeIds, nodeInput.task.id, input.tasksById)
|
|
440
468
|
: [];
|
|
441
|
-
return
|
|
469
|
+
return executeDagPiNode({ ...nodeInput, model: nodeInput.model }, {
|
|
442
470
|
...input.meta,
|
|
443
471
|
concurrentSiblingWriteSets: siblingWriteSets.length > 0 ? siblingWriteSets : undefined,
|
|
444
472
|
writeGuardAttribution: siblingWriteSets.length > 0 ? "best-effort" : "per-node",
|
|
@@ -55,8 +55,13 @@ export async function executeDagRanksOnce(input) {
|
|
|
55
55
|
return isPauseOnHumanDecisionGate(task);
|
|
56
56
|
});
|
|
57
57
|
const regularRunnable = runnable.filter((id) => !pauseGateRunnable.includes(id));
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const rankWriterNodeIds = runnable.filter((id) => {
|
|
59
|
+
const task = input.tasksById.get(id);
|
|
60
|
+
return (task?.executor === "pi" &&
|
|
61
|
+
task.toolProfile === "write" &&
|
|
62
|
+
task.writePolicy === "exclusive");
|
|
63
|
+
});
|
|
64
|
+
const executeNode = input.createExecuteNodeForRank(rankWriterNodeIds);
|
|
60
65
|
for (const nodeId of pauseGateRunnable) {
|
|
61
66
|
await input.executeScheduledNode(nodeId, executeNode, (id, pausedAt, pauseReason) => {
|
|
62
67
|
pausedByNodeId = id;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* SDD embedded-mode optional enhancement.
|
|
5
|
+
*
|
|
6
|
+
* When the target project ships repo-local `SDD-*` skills (under
|
|
7
|
+
* `.agents/skills/<name>/SKILL.md`), loop-agent can
|
|
8
|
+
* reuse them as knowledge/method references inside individual DAG nodes. This
|
|
9
|
+
* module probes those repo-local paths and, on a hit, appends the matched skill
|
|
10
|
+
* name to the mapped node's `task.skills` (so it flows through the existing
|
|
11
|
+
* run-owned skill snapshot via `resolveDagNodeSkills`) plus a loop-agent-managed
|
|
12
|
+
* embedded-mode constraint block to the node prompt.
|
|
13
|
+
*
|
|
14
|
+
* loop-agent always owns process/state/verification/closeout. SDD state
|
|
15
|
+
* transitions, confirmation gates, and archival are explicitly forbidden inside
|
|
16
|
+
* the embedded-mode block.
|
|
17
|
+
*/
|
|
18
|
+
/** The three SDD skills that may be reused as repo-local knowledge. */
|
|
19
|
+
export const SDD_SKILL_NAMES = [
|
|
20
|
+
"SDD-requirement-analysis",
|
|
21
|
+
"SDD-design-analysis",
|
|
22
|
+
"SDD-implementation-test-review",
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* DAG node id -> mapped SDD skill name. `verify-pi` is present in standard and
|
|
26
|
+
* review-gated templates; supervised verification stays shell-based. The init skills
|
|
27
|
+
* (`SDD-code-spec-init`, `SDD-project-how-to-spec-init`) are intentionally
|
|
28
|
+
* absent: they are never auto-executed inside a normal feature DAG.
|
|
29
|
+
*/
|
|
30
|
+
export const SDD_NODE_SKILL_MAP = {
|
|
31
|
+
"contract-pi": "SDD-requirement-analysis",
|
|
32
|
+
"plan-pi": "SDD-design-analysis",
|
|
33
|
+
"implement-pi": "SDD-implementation-test-review",
|
|
34
|
+
"repair-pi": "SDD-implementation-test-review",
|
|
35
|
+
"verify-pi": "SDD-implementation-test-review",
|
|
36
|
+
"review-pi": "SDD-implementation-test-review",
|
|
37
|
+
};
|
|
38
|
+
/** DAG node id -> SDD phase label injected into the constraint block. */
|
|
39
|
+
export const SDD_NODE_PHASE_LABEL = {
|
|
40
|
+
"contract-pi": "需求",
|
|
41
|
+
"plan-pi": "设计",
|
|
42
|
+
"implement-pi": "实现",
|
|
43
|
+
"repair-pi": "修复",
|
|
44
|
+
"verify-pi": "验证",
|
|
45
|
+
"review-pi": "审查",
|
|
46
|
+
};
|
|
47
|
+
/** Fence tokens used to delimit the embedded-mode constraint block. */
|
|
48
|
+
export const SDD_EMBEDDED_MODE_OPEN = "[SDD-EMBEDDED-MODE]";
|
|
49
|
+
export const SDD_EMBEDDED_MODE_CLOSE = "[/SDD-EMBEDDED-MODE]";
|
|
50
|
+
const SDD_SKILL_CANDIDATE_BASES = [".agents/skills"];
|
|
51
|
+
/**
|
|
52
|
+
* Probe the target project repo for repo-local SDD skills. Only the canonical
|
|
53
|
+
* repo-relative `.agents/skills` base is checked; this deliberately does NOT reuse
|
|
54
|
+
* the skill loader candidate list (which also falls back to user-level and
|
|
55
|
+
* bundled package skills) so user-level/package skills never trigger
|
|
56
|
+
* auto-enhancement.
|
|
57
|
+
*
|
|
58
|
+
* Returns the set of SDD skill names whose `SKILL.md` exists under at least one
|
|
59
|
+
* repo-local base. Returns an empty set when `repoRoot` is absent so callers
|
|
60
|
+
* that do not pass a repo root preserve byte-level default output.
|
|
61
|
+
*/
|
|
62
|
+
export async function probeRepoLocalSddSkills(repoRoot) {
|
|
63
|
+
if (!repoRoot)
|
|
64
|
+
return new Set();
|
|
65
|
+
const hits = new Set();
|
|
66
|
+
for (const name of SDD_SKILL_NAMES) {
|
|
67
|
+
for (const base of SDD_SKILL_CANDIDATE_BASES) {
|
|
68
|
+
const candidate = path.join(repoRoot, base, name, "SKILL.md");
|
|
69
|
+
try {
|
|
70
|
+
await access(candidate);
|
|
71
|
+
hits.add(name);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// not present at this candidate base; try the next
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return hits;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build the loop-agent-managed embedded-mode constraint block for a node. The
|
|
83
|
+
* block declares DAG-node-boundary priority and forbids SDD state transitions,
|
|
84
|
+
* confirmation gates, working_requirements_status updates, and archival.
|
|
85
|
+
*
|
|
86
|
+
* Returns `null` for unmapped node ids (e.g. scouts, shell gates, closeout).
|
|
87
|
+
*/
|
|
88
|
+
export function buildSddEmbeddedConstraintBlock(nodeId) {
|
|
89
|
+
const phase = SDD_NODE_PHASE_LABEL[nodeId];
|
|
90
|
+
if (!phase)
|
|
91
|
+
return null;
|
|
92
|
+
return [
|
|
93
|
+
SDD_EMBEDDED_MODE_OPEN,
|
|
94
|
+
`当前 DAG 节点: ${nodeId},阶段: ${phase}`,
|
|
95
|
+
"loop-agent 拥有流程、状态、验证与收口控制权;SDD skill 仅作 repo-local 知识与方法参考。",
|
|
96
|
+
"禁止:更新 working_requirements_status.md;推进 SDD 阶段;触发 SDD 用户确认;执行归档。",
|
|
97
|
+
"优先级:本节点的读写边界、当前阶段与输出契约优先于任何 SDD 指令冲突。",
|
|
98
|
+
"implement/repair writer 仍必须服从当前 writeSet/allowedPaths/forbiddenPaths;不得把 ai_workspace 加入隐式写范围,也不得解析或写入 SDD 工作区状态机。",
|
|
99
|
+
SDD_EMBEDDED_MODE_CLOSE,
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Apply SDD embedded-mode enhancements in place on a fully-constructed DAG spec.
|
|
104
|
+
*
|
|
105
|
+
* For each mapped task whose mapped skill is present in the hit set:
|
|
106
|
+
* - append the skill name to `task.skills` (de-duplicated, order-preserving);
|
|
107
|
+
* - append the constraint block to the end of `task.subtask_prompt` (once).
|
|
108
|
+
*
|
|
109
|
+
* When the hit set is empty this is a no-op, preserving the default DAG byte
|
|
110
|
+
* for byte. The function is idempotent.
|
|
111
|
+
*/
|
|
112
|
+
export function applySddEmbeddedEnhancements(spec, hitSkills) {
|
|
113
|
+
if (hitSkills.size === 0)
|
|
114
|
+
return;
|
|
115
|
+
for (const task of spec.tasks) {
|
|
116
|
+
const mapped = SDD_NODE_SKILL_MAP[task.id];
|
|
117
|
+
if (!mapped || !hitSkills.has(mapped))
|
|
118
|
+
continue;
|
|
119
|
+
const existing = task.skills ?? [];
|
|
120
|
+
if (!existing.includes(mapped)) {
|
|
121
|
+
task.skills = [...existing, mapped];
|
|
122
|
+
}
|
|
123
|
+
const block = buildSddEmbeddedConstraintBlock(task.id);
|
|
124
|
+
if (block && !task.subtask_prompt.includes(SDD_EMBEDDED_MODE_OPEN)) {
|
|
125
|
+
task.subtask_prompt = `${task.subtask_prompt}\n\n${block}`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -41,11 +41,12 @@ function buildSkillCandidatePaths(input) {
|
|
|
41
41
|
}
|
|
42
42
|
const piSkillsDir = path.join(input.homeDir, ".pi", "agent", "skills");
|
|
43
43
|
candidates.push(normalizeCandidate(path.join(piSkillsDir, input.name, "SKILL.md")), normalizeCandidate(path.join(piSkillsDir, `${input.name}.md`)));
|
|
44
|
-
|
|
45
|
-
// Agent-compatible project path (e.g. OpenCode). `skills/` stays the primary
|
|
46
|
-
// loop-agent repo-local path; `.agents/skills` is searched after it and before
|
|
47
|
-
// bundled skills so a project with only the mirror can still resolve.
|
|
44
|
+
// Canonical target-project repo-local skill path.
|
|
48
45
|
candidates.push(normalizeCandidate(path.join(input.cwd, ".agents", "skills", input.name, "SKILL.md")));
|
|
46
|
+
// Source-repository compatibility: the loop-agent package itself still ships
|
|
47
|
+
// bundled skills under `skills/`, and local source development may resolve them
|
|
48
|
+
// before the package fallback is found.
|
|
49
|
+
candidates.push(normalizeCandidate(path.join(input.cwd, "skills", input.name, "SKILL.md")));
|
|
49
50
|
if (input.name === "loop-agent") {
|
|
50
51
|
candidates.push(normalizeCandidate(path.join(input.cwd, "skill", "SKILL.md")));
|
|
51
52
|
}
|