@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.
Files changed (166) hide show
  1. package/AGENTS.md +10 -2
  2. package/CHANGELOG.md +67 -25
  3. package/README.md +82 -11
  4. package/dist/application/dag/args.js +1 -12
  5. package/dist/application/dag/generate-task-dag.js +23 -2
  6. package/dist/application/dag/run-dag.js +1 -27
  7. package/dist/application/dag/validate-dag.js +2 -2
  8. package/dist/application/loop/run-action.js +0 -4
  9. package/dist/cli/command-definitions.js +44 -16
  10. package/dist/cli/program.js +40 -23
  11. package/dist/cli/update/notifier.js +117 -0
  12. package/dist/cli/update/npm-client.js +151 -0
  13. package/dist/cli/update/policy.js +58 -0
  14. package/dist/cli/update/state.js +68 -0
  15. package/dist/cli.js +33 -0
  16. package/dist/commands/cursor-prompt.js +42 -82
  17. package/dist/commands/dag-approve.js +36 -0
  18. package/dist/commands/delegate.js +75 -77
  19. package/dist/commands/doctor.js +0 -18
  20. package/dist/commands/init.js +476 -91
  21. package/dist/commands/instructions.js +7 -10
  22. package/dist/commands/loop.js +4 -20
  23. package/dist/commands/plan.js +50 -0
  24. package/dist/executors/config-core.js +0 -51
  25. package/dist/executors/dag-pi-executor.js +1 -1
  26. package/dist/executors/dag.js +0 -1
  27. package/dist/executors/index.js +0 -2
  28. package/dist/executors/model-routing.js +9 -9
  29. package/dist/executors/shell-executor.js +1 -1
  30. package/dist/governance/checks.js +6 -3
  31. package/dist/governance/exec-plans.js +545 -0
  32. package/dist/governance/manifest-types.js +24 -2
  33. package/dist/infrastructure/harness/loop-action-store.js +0 -3
  34. package/dist/records/harvest.js +2 -23
  35. package/dist/records/one-shot-runs.js +1 -1
  36. package/dist/shared/artifacts-core.js +24 -5
  37. package/dist/shared/output-truncation.js +37 -0
  38. package/dist/shared/package-metadata.js +353 -0
  39. package/dist/{executors/cursor-executor.js → sidecars/cursor-prompt/executor.js} +2 -42
  40. package/dist/sidecars/cursor-prompt/index.js +3 -0
  41. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  42. package/dist/task/config-types.js +28 -12
  43. package/dist/task/delegate.js +9 -21
  44. package/dist/task/runtime.js +1 -2
  45. package/dist/worker/cli.js +29 -2
  46. package/dist/worker/delivery/final-verification.js +47 -11
  47. package/dist/worker/delivery/package.js +63 -10
  48. package/dist/worker/feature/run.js +60 -8
  49. package/dist/worker/loop-agent/loop-agent-client.js +329 -126
  50. package/dist/worker/observability/read-model.js +27 -1
  51. package/dist/worker/observe/static/app.js +326 -45
  52. package/dist/worker/observe/static/index.html +1 -1
  53. package/dist/worker/observe/static/styles.css +5 -4
  54. package/dist/worker/preflight.js +49 -1
  55. package/dist/worker/run-task/run-task.js +22 -12
  56. package/dist/worker/runner/run-ready.js +76 -12
  57. package/dist/worker/task-spec/schema.js +0 -1
  58. package/dist/workflows/dag/convergence/controller.js +1 -1
  59. package/dist/workflows/dag/executor-registry.js +0 -2
  60. package/dist/workflows/dag/init-hybrid.js +402 -25
  61. package/dist/workflows/dag/node-execution.js +61 -7
  62. package/dist/workflows/dag/runner.js +45 -17
  63. package/dist/workflows/dag/scheduler.js +7 -2
  64. package/dist/workflows/dag/sdd-embedded.js +128 -0
  65. package/dist/workflows/dag/skill-instructions.js +5 -4
  66. package/dist/workflows/dag/skill-snapshot.js +527 -0
  67. package/dist/workflows/dag/types.js +42 -9
  68. package/dist/workflows/dag/validate.js +5 -8
  69. package/dist/workflows/loop/actions/dag-action.js +0 -2
  70. package/dist/workflows/loop/actions/shared.js +1 -1
  71. package/dist/workflows/loop/actions.js +14 -31
  72. package/dist/workflows/loop/benchmark.js +1 -1
  73. package/dist/workflows/loop/index.js +1 -1
  74. package/dist/workflows/loop/policy/auto-policy.js +22 -14
  75. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  76. package/docs/README.md +36 -33
  77. package/docs/agent-dag-recovery-playbook.md +1 -1
  78. package/docs/agent-dag-runner.md +2 -2
  79. package/docs/architecture/README.md +26 -0
  80. package/docs/architecture/dag-execution.md +134 -0
  81. package/docs/architecture/evolution.md +52 -0
  82. package/docs/architecture/facts-and-state.md +58 -0
  83. package/docs/architecture/runtime-boundaries.md +45 -17
  84. package/docs/architecture/system-overview.md +93 -0
  85. package/docs/architecture/worker-and-feature.md +81 -0
  86. package/docs/cursor-prompt-sidecar.md +36 -0
  87. package/docs/decisions/README.md +13 -1
  88. package/docs/design/README.md +42 -21
  89. package/docs/development-principles.md +2 -2
  90. package/docs/exec-plans/active/README.md +2 -2
  91. package/docs/exec-plans/completed/README.md +12 -0
  92. package/docs/feature-workflow.md +50 -4
  93. package/docs/harness-methodology-debugging.md +1 -1
  94. package/docs/harness-methodology-tdd.md +3 -3
  95. package/docs/init-surface.manifest.json +60 -25
  96. package/docs/loop-agent-harness.md +28 -4
  97. package/docs/progress/README.md +32 -1
  98. package/docs/reports/README.md +84 -18
  99. package/docs/skills/README.md +2 -1
  100. package/docs/skills/vetted-skill-registry.md +2 -1
  101. package/docs/templates/agent-dag-report.schema.json +6 -6
  102. package/docs/templates/agent-dag.base.json +0 -5
  103. package/docs/templates/agent-dag.final-verification.json +0 -5
  104. package/docs/templates/agent-dag.schema.json +1 -2
  105. package/docs/templates/agent-dag.supervised-implementation.json +1 -6
  106. package/docs/templates/frontend-design-contract.md +33 -0
  107. package/docs/templates/frontend-task-constraints.md +25 -0
  108. package/docs/templates/frontend-task-requirement.md +61 -0
  109. package/docs/templates/harness.schema.json +10 -12
  110. package/docs/templates/hybrid-dag.json +1 -6
  111. package/docs/templates/interactive-ui-round2-experiment.md +1 -1
  112. package/docs/templates/product-line/task.yaml +0 -1
  113. package/docs/templates/project-start-checklist.md +2 -2
  114. package/docs/templates/worker-dogfood-evidence.md +28 -0
  115. package/docs/templates/worker-dogfood-setup.md +20 -0
  116. package/docs/verification-matrix.md +10 -0
  117. package/examples/decision-gate-agent-dag.json +87 -33
  118. package/examples/example-dag.json +0 -5
  119. package/examples/hybrid-loop-agent-dag.json +0 -5
  120. package/harness.json +7 -15
  121. package/package.json +22 -46
  122. package/scripts/check-product-line-docs.sh +10 -7
  123. package/skills/agent-worker/SKILL.md +37 -0
  124. package/skills/agent-worker/references/agent-worker-operator.md +43 -0
  125. package/skills/frontend-design-review/SKILL.md +59 -0
  126. package/skills/frontend-design-review/references/review-checklist.md +37 -0
  127. package/skills/frontend-implementation/SKILL.md +51 -0
  128. package/skills/frontend-implementation/references/code-standards.md +34 -0
  129. package/skills/frontend-implementation/references/design-spec.md +46 -0
  130. package/skills/frontend-implementation/references/node-contracts.md +32 -0
  131. package/skills/frontend-review/SKILL.md +53 -0
  132. package/skills/frontend-review/references/review-findings.md +42 -0
  133. package/skills/frontend-verification/SKILL.md +40 -0
  134. package/skills/frontend-verification/references/verification-checklist.md +56 -0
  135. package/skills/grill-me/SKILL.md +10 -0
  136. package/skills/grill-with-docs/SKILL.md +88 -0
  137. package/skills/grill-with-docs/adr-format.md +47 -0
  138. package/skills/grill-with-docs/context-format.md +60 -0
  139. package/skills/loop-agent/SKILL.md +11 -9
  140. package/skills/loop-agent/references/command-reference.md +13 -15
  141. package/skills/loop-agent/references/docs-converge.md +126 -0
  142. package/skills/loop-agent/references/harness-policy.md +7 -7
  143. package/skills/loop-agent/references/hybrid-dag.md +15 -18
  144. package/skills/loop-agent/references/long-running-loop.md +4 -6
  145. package/skills/loop-agent/references/multi-worktree.md +6 -6
  146. package/skills/loop-agent/references/orchestrator-and-interventions.md +3 -3
  147. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +14 -11
  148. package/skills/loop-agent/references/task-workflow.md +1 -1
  149. package/skills/using-git-worktrees/SKILL.md +215 -0
  150. package/dist/commands/cursor-worker.js +0 -43
  151. package/dist/cursor-worker-entry.js +0 -8
  152. package/dist/executors/cursor-artifacts.js +0 -33
  153. package/dist/executors/cursor-execution-log.js +0 -81
  154. package/dist/executors/cursor-executor-artifacts.js +0 -134
  155. package/dist/executors/cursor-run.js +0 -115
  156. package/dist/executors/cursor-tool.js +0 -94
  157. package/dist/executors/cursor-worker-client.js +0 -223
  158. package/dist/executors/cursor-worker-protocol.js +0 -18
  159. package/dist/executors/cursor-worker-server.js +0 -54
  160. package/dist/executors/cursor-worker.js +0 -3
  161. package/dist/executors/cursor.js +0 -6
  162. package/dist/executors/dag-cursor-executor.js +0 -87
  163. package/dist/workflows/loop/actions/cursor-fix.js +0 -191
  164. package/dist/workflows/loop/policy/cursor-fix-policy.js +0 -31
  165. package/docs/cursor-executor-usage.md +0 -25
  166. 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 { executeDagCursorNode } from "../../executors/dag-cursor-executor.js";
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: (rankCursorNodeIds) => buildRankAwareExecuteNode({
348
+ createExecuteNodeForRank: (rankWriterNodeIds) => buildRankAwareExecuteNode({
318
349
  baseExecuteNode,
319
350
  customExecuteNode: input.executeNode,
320
- rankCursorNodeIds,
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
- async function stopCursorWorkerIfRunning() {
417
- const { isCursorWorkerRunning, stopCursorWorker } = await import("../../executors/cursor-worker-client.js");
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 rankCursorNodeIds
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
- if (nodeInput.task.executor !== "cursor" || !nodeInput.model) {
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.rankCursorNodeIds, nodeInput.task.id, input.tasksById)
467
+ ? concurrentSiblingWriteSetsForNode(input.rankWriterNodeIds, nodeInput.task.id, input.tasksById)
440
468
  : [];
441
- return executeDagCursorNode({ ...nodeInput, model: nodeInput.model }, {
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 rankCursorNodeIds = runnable.filter((id) => input.tasksById.get(id)?.executor === "cursor");
59
- const executeNode = input.createExecuteNodeForRank(rankCursorNodeIds);
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
- candidates.push(normalizeCandidate(path.join(input.cwd, "skills", input.name, "SKILL.md")));
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
  }