@tea-agent/loop-agent 0.1.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 +121 -0
- package/CHANGELOG.md +37 -0
- package/README.md +144 -0
- package/bin/loop-agent.js +21 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/loop-agent.js +98 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli/catalog.js +259 -0
- package/dist/cli/help.js +55 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/program.js +505 -0
- package/dist/cli.js +12 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/cursor-prompt.js +222 -0
- package/dist/commands/cursor-worker.js +43 -0
- package/dist/commands/dag-approve.js +102 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +56 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +177 -0
- package/dist/commands/dag-resume.js +34 -0
- package/dist/commands/dag-run-task.js +470 -0
- package/dist/commands/dag-validate.js +186 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +132 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +5 -0
- package/dist/commands/doctor.js +50 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +195 -0
- package/dist/commands/knowledge.js +64 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +241 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/pi-prompt.js +181 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag.js +184 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +84 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/cursor-worker-entry.js +8 -0
- package/dist/executors/config-core.js +55 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/cursor-artifacts.js +33 -0
- package/dist/executors/cursor-execution-log.js +81 -0
- package/dist/executors/cursor-executor-artifacts.js +135 -0
- package/dist/executors/cursor-executor.js +468 -0
- package/dist/executors/cursor-run.js +115 -0
- package/dist/executors/cursor-tool.js +94 -0
- package/dist/executors/cursor-worker-client.js +213 -0
- package/dist/executors/cursor-worker-protocol.js +18 -0
- package/dist/executors/cursor-worker-server.js +54 -0
- package/dist/executors/cursor-worker.js +3 -0
- package/dist/executors/cursor.js +6 -0
- package/dist/executors/dag-cursor-executor.js +88 -0
- package/dist/executors/dag-pi-executor.js +322 -0
- package/dist/executors/dag-static-executor.js +45 -0
- package/dist/executors/dag.js +4 -0
- package/dist/executors/index.js +8 -0
- package/dist/executors/model-routing.js +60 -0
- package/dist/executors/pi-event-serializer.js +43 -0
- package/dist/executors/pi-executor.js +606 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +255 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/shell-executor.js +300 -0
- package/dist/executors/shell-presets.js +47 -0
- package/dist/executors/shell-verification.js +251 -0
- package/dist/executors/shell-write-guard.js +126 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +434 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +128 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +152 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +236 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +421 -0
- package/dist/records/promotion.js +199 -0
- package/dist/shared/artifacts-core.js +88 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +165 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +23 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +238 -0
- package/dist/shared/timeout-policy.js +19 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/task/config-types.js +97 -0
- package/dist/task/config.js +2 -0
- package/dist/task/delegate.js +220 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +9 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +149 -0
- package/dist/task/runtime.js +699 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +55 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +171 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/decision-envelope.js +502 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/executor-registry.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +111 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +405 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +855 -0
- package/dist/workflows/dag/knowledge-curator.js +162 -0
- package/dist/workflows/dag/lifecycle.js +484 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +130 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +226 -0
- package/dist/workflows/dag/repair-artifact.js +136 -0
- package/dist/workflows/dag/report.js +1019 -0
- package/dist/workflows/dag/runner.js +1677 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/skill-instructions.js +471 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +275 -0
- package/dist/workflows/dag/upstream-artifacts.js +95 -0
- package/dist/workflows/dag/validate.js +527 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +360 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +114 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions.js +1334 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +134 -0
- package/dist/workflows/loop/context.js +48 -0
- package/dist/workflows/loop/events.js +25 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +55 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +62 -0
- package/docs/agent-dag-recovery-playbook.md +158 -0
- package/docs/agent-dag-runner.md +40 -0
- package/docs/cursor-executor-usage.md +25 -0
- package/docs/decisions/README.md +3 -0
- package/docs/design/README.md +36 -0
- package/docs/development-principles.md +71 -0
- package/docs/dynamic-workflow-dag-engine-roadmap.md +1749 -0
- package/docs/exec-plans/README.md +6 -0
- package/docs/exec-plans/active/README.md +5 -0
- package/docs/exec-plans/completed/README.md +5 -0
- package/docs/feature-workflow.md +184 -0
- package/docs/harness-methodology-debugging.md +153 -0
- package/docs/harness-methodology-tdd.md +130 -0
- package/docs/harness-methodology-verification.md +27 -0
- package/docs/loop-agent-harness.md +42 -0
- package/docs/progress/README.md +3 -0
- package/docs/reports/README.md +3 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +423 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +195 -0
- package/docs/templates/agent-dag.final-verification.json +190 -0
- package/docs/templates/agent-dag.schema.json +316 -0
- package/docs/templates/agent-dag.supervised-implementation.json +500 -0
- package/docs/templates/exec-plan.md +64 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/hybrid-dag.json +193 -0
- package/docs/templates/progress-log.md +17 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +42 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/verification-matrix.md +30 -0
- package/examples/decision-gate-agent-dag.json +123 -0
- package/examples/example-dag.json +51 -0
- package/examples/hybrid-loop-agent-dag.json +194 -0
- package/harness.json +92 -0
- package/package.json +61 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/loop-agent/SKILL.md +260 -0
- package/skills/loop-agent/references/README.md +63 -0
- package/skills/loop-agent/references/command-reference.md +315 -0
- package/skills/loop-agent/references/harness-policy.md +258 -0
- package/skills/loop-agent/references/hybrid-dag.md +216 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/model-routing.md +36 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +85 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +83 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -0
- package/skills/loop-agent/references/task-workflow.md +84 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +74 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
export const DEFAULT_SKILL_INSTRUCTION_MAX_CHARS = 2_500;
|
|
7
|
+
export const DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS = 10_000;
|
|
8
|
+
const SKILL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
9
|
+
async function fileExists(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
await access(filePath);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function sha256(content) {
|
|
19
|
+
return createHash("sha256").update(content).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
function normalizeCandidate(filePath) {
|
|
22
|
+
return path.resolve(filePath);
|
|
23
|
+
}
|
|
24
|
+
function bundledSkillCandidatePaths(name) {
|
|
25
|
+
const candidates = [];
|
|
26
|
+
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
while (true) {
|
|
28
|
+
candidates.push(normalizeCandidate(path.join(current, "skills", name, "SKILL.md")));
|
|
29
|
+
const parent = path.dirname(current);
|
|
30
|
+
if (parent === current)
|
|
31
|
+
break;
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
return candidates;
|
|
35
|
+
}
|
|
36
|
+
function buildSkillCandidatePaths(input) {
|
|
37
|
+
const candidates = [];
|
|
38
|
+
const agentDir = input.env.PI_CODING_AGENT_DIR;
|
|
39
|
+
if (agentDir) {
|
|
40
|
+
candidates.push(normalizeCandidate(path.join(agentDir, "skills", input.name, "SKILL.md")), normalizeCandidate(path.join(agentDir, "skills", `${input.name}.md`)));
|
|
41
|
+
}
|
|
42
|
+
const piSkillsDir = path.join(input.homeDir, ".pi", "agent", "skills");
|
|
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
|
+
if (input.name === "loop-agent") {
|
|
46
|
+
candidates.push(normalizeCandidate(path.join(input.cwd, "skill", "SKILL.md")));
|
|
47
|
+
}
|
|
48
|
+
candidates.push(...bundledSkillCandidatePaths(input.name));
|
|
49
|
+
return Array.from(new Set(candidates));
|
|
50
|
+
}
|
|
51
|
+
const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
52
|
+
export function splitSkillFrontmatter(content) {
|
|
53
|
+
const match = content.match(FRONTMATTER_RE);
|
|
54
|
+
if (!match) {
|
|
55
|
+
return { frontmatter: null, body: content };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
frontmatter: match[1],
|
|
59
|
+
body: content.slice(match[0].length),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function parseSkillReferenceDeclarations(frontmatter) {
|
|
63
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
64
|
+
let inReferences = false;
|
|
65
|
+
let referencesKeyIndent = -1;
|
|
66
|
+
const declarations = [];
|
|
67
|
+
let currentObject = null;
|
|
68
|
+
const flushCurrentObject = () => {
|
|
69
|
+
if (currentObject?.path) {
|
|
70
|
+
declarations.push(currentObject);
|
|
71
|
+
}
|
|
72
|
+
currentObject = null;
|
|
73
|
+
};
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
if (!inReferences) {
|
|
76
|
+
if (/^references:\s*$/.test(line)) {
|
|
77
|
+
inReferences = true;
|
|
78
|
+
referencesKeyIndent = 0;
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (line.trim() === "")
|
|
83
|
+
continue;
|
|
84
|
+
const indent = line.search(/\S/);
|
|
85
|
+
if (indent === -1)
|
|
86
|
+
continue;
|
|
87
|
+
if (indent <= referencesKeyIndent && !/^\s*-\s+/.test(line)) {
|
|
88
|
+
flushCurrentObject();
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
const listMatch = line.match(/^(\s*)-\s+(.*)$/);
|
|
92
|
+
if (listMatch) {
|
|
93
|
+
flushCurrentObject();
|
|
94
|
+
const rest = listMatch[2].trim();
|
|
95
|
+
if (rest.startsWith("path:")) {
|
|
96
|
+
currentObject = {
|
|
97
|
+
path: rest.slice("path:".length).trim().replace(/^['"]|['"]$/g, ""),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else if (!rest.includes(":")) {
|
|
101
|
+
declarations.push({
|
|
102
|
+
path: rest.replace(/^['"]|['"]$/g, ""),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (currentObject) {
|
|
108
|
+
const propMatch = line.match(/^\s+(\w+):\s*(.*)$/);
|
|
109
|
+
if (propMatch) {
|
|
110
|
+
const key = propMatch[1];
|
|
111
|
+
const value = propMatch[2].trim().replace(/^['"]|['"]$/g, "");
|
|
112
|
+
if (key === "path")
|
|
113
|
+
currentObject.path = value;
|
|
114
|
+
if (key === "name")
|
|
115
|
+
currentObject.name = value;
|
|
116
|
+
if (key === "maxChars") {
|
|
117
|
+
const parsed = Number.parseInt(value, 10);
|
|
118
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
119
|
+
currentObject.maxChars = parsed;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (key === "required")
|
|
123
|
+
currentObject.required = value === "true";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
flushCurrentObject();
|
|
128
|
+
return declarations;
|
|
129
|
+
}
|
|
130
|
+
function isValidReferencePath(refPath) {
|
|
131
|
+
if (!refPath || refPath.includes("\\"))
|
|
132
|
+
return false;
|
|
133
|
+
if (path.isAbsolute(refPath))
|
|
134
|
+
return false;
|
|
135
|
+
if (/[*?[\]{}]/.test(refPath))
|
|
136
|
+
return false;
|
|
137
|
+
if (!refPath.endsWith(".md"))
|
|
138
|
+
return false;
|
|
139
|
+
return !refPath
|
|
140
|
+
.split("/")
|
|
141
|
+
.some((segment) => segment === "" || segment === "." || segment === "..");
|
|
142
|
+
}
|
|
143
|
+
function isPathWithinDir(resolvedPath, dir) {
|
|
144
|
+
const normalizedDir = path.resolve(dir);
|
|
145
|
+
const normalizedResolved = path.resolve(resolvedPath);
|
|
146
|
+
return (normalizedResolved === normalizedDir ||
|
|
147
|
+
normalizedResolved.startsWith(`${normalizedDir}${path.sep}`));
|
|
148
|
+
}
|
|
149
|
+
function referenceDisplayName(declaration) {
|
|
150
|
+
if (declaration.name)
|
|
151
|
+
return declaration.name;
|
|
152
|
+
const base = path.basename(declaration.path, ".md");
|
|
153
|
+
return base || declaration.path;
|
|
154
|
+
}
|
|
155
|
+
function truncateReferenceContent(content, maxChars) {
|
|
156
|
+
if (content.length <= maxChars) {
|
|
157
|
+
return { excerpt: content, truncated: false };
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
excerpt: `${content.slice(0, Math.max(0, maxChars))}\n[reference truncated at ${maxChars} chars]`,
|
|
161
|
+
truncated: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function resolveSkillReferences(input) {
|
|
165
|
+
const sections = [];
|
|
166
|
+
const references = [];
|
|
167
|
+
const unresolvedReferences = [];
|
|
168
|
+
let remainingTotal = input.remainingTotal;
|
|
169
|
+
for (const declaration of input.declarations) {
|
|
170
|
+
const name = referenceDisplayName(declaration);
|
|
171
|
+
const refPath = declaration.path;
|
|
172
|
+
if (!isValidReferencePath(refPath)) {
|
|
173
|
+
unresolvedReferences.push({
|
|
174
|
+
name,
|
|
175
|
+
path: refPath,
|
|
176
|
+
reason: "invalid-path",
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const resolvedPath = normalizeCandidate(path.join(input.skillDir, refPath));
|
|
181
|
+
if (!isPathWithinDir(resolvedPath, input.skillDir)) {
|
|
182
|
+
unresolvedReferences.push({
|
|
183
|
+
name,
|
|
184
|
+
path: refPath,
|
|
185
|
+
reason: "invalid-path",
|
|
186
|
+
});
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (input.resolvedReferencePaths.has(resolvedPath)) {
|
|
190
|
+
unresolvedReferences.push({
|
|
191
|
+
name,
|
|
192
|
+
path: refPath,
|
|
193
|
+
reason: "already-resolved",
|
|
194
|
+
});
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (remainingTotal <= 0) {
|
|
198
|
+
unresolvedReferences.push({
|
|
199
|
+
name,
|
|
200
|
+
path: refPath,
|
|
201
|
+
reason: "budget",
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!(await fileExists(resolvedPath))) {
|
|
206
|
+
unresolvedReferences.push({
|
|
207
|
+
name,
|
|
208
|
+
path: refPath,
|
|
209
|
+
reason: "missing",
|
|
210
|
+
});
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const content = await readFile(resolvedPath, "utf-8");
|
|
215
|
+
const contentSha256 = sha256(content);
|
|
216
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
217
|
+
const budget = Math.max(0, Math.min(declaration.maxChars ?? input.perReferenceMaxChars, remainingTotal));
|
|
218
|
+
const { excerpt, truncated } = truncateReferenceContent(content, budget);
|
|
219
|
+
input.resolvedReferencePaths.add(resolvedPath);
|
|
220
|
+
references.push({
|
|
221
|
+
name,
|
|
222
|
+
path: resolvedPath,
|
|
223
|
+
sha256: contentSha256,
|
|
224
|
+
bytes,
|
|
225
|
+
...(truncated ? { truncated: true } : {}),
|
|
226
|
+
});
|
|
227
|
+
remainingTotal = Math.max(0, remainingTotal - Math.min(content.length, budget));
|
|
228
|
+
sections.push([
|
|
229
|
+
`Reference: ${name}`,
|
|
230
|
+
`Path: ${resolvedPath}`,
|
|
231
|
+
`Sha256: ${contentSha256}`,
|
|
232
|
+
excerpt,
|
|
233
|
+
].join("\n"));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
unresolvedReferences.push({
|
|
237
|
+
name,
|
|
238
|
+
path: refPath,
|
|
239
|
+
reason: "read-error",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
sections,
|
|
245
|
+
references,
|
|
246
|
+
unresolvedReferences,
|
|
247
|
+
remainingTotal,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function buildResolvedPromptText(input) {
|
|
251
|
+
const truncated = input.content.length > input.maxChars;
|
|
252
|
+
const excerpt = truncated
|
|
253
|
+
? input.content.slice(0, Math.max(0, input.maxChars))
|
|
254
|
+
: input.content;
|
|
255
|
+
const instructions = truncated
|
|
256
|
+
? `${excerpt}\n[skill instructions truncated at ${input.maxChars} chars]`
|
|
257
|
+
: excerpt;
|
|
258
|
+
const lines = [
|
|
259
|
+
`Skill: ${input.name}`,
|
|
260
|
+
"Resolution: resolved",
|
|
261
|
+
`Path: ${input.filePath}`,
|
|
262
|
+
`Sha256: ${input.contentSha256}`,
|
|
263
|
+
"Injection mode: bounded-inline",
|
|
264
|
+
"Instructions:",
|
|
265
|
+
instructions,
|
|
266
|
+
];
|
|
267
|
+
if (input.referenceSections && input.referenceSections.length > 0) {
|
|
268
|
+
lines.push("Resolved references:", ...input.referenceSections);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
truncated,
|
|
272
|
+
promptText: lines.join("\n"),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function buildMissingPromptText(input) {
|
|
276
|
+
return [
|
|
277
|
+
`Skill: ${input.name}`,
|
|
278
|
+
"Resolution: missing",
|
|
279
|
+
"Searched paths:",
|
|
280
|
+
...input.searchedPaths.map((candidate) => `- ${candidate}`),
|
|
281
|
+
"Injection mode: name-only",
|
|
282
|
+
"Effect: only the skill name is available; no instructions were loaded.",
|
|
283
|
+
].join("\n");
|
|
284
|
+
}
|
|
285
|
+
function buildErrorPromptText(input) {
|
|
286
|
+
return [
|
|
287
|
+
`Skill: ${input.name}`,
|
|
288
|
+
"Resolution: error",
|
|
289
|
+
`Error: ${input.error}`,
|
|
290
|
+
"Injection mode: name-only",
|
|
291
|
+
"Effect: only the skill name is available; no instructions were loaded.",
|
|
292
|
+
].join("\n");
|
|
293
|
+
}
|
|
294
|
+
export function skillInstructionMetadata(instructions) {
|
|
295
|
+
return instructions.map(({ promptText: _promptText, ...metadata }) => metadata);
|
|
296
|
+
}
|
|
297
|
+
export async function resolveDagSkillInstructions(skills, options) {
|
|
298
|
+
if (skills.length === 0)
|
|
299
|
+
return [];
|
|
300
|
+
const env = options.env ?? process.env;
|
|
301
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
302
|
+
const perSkillMaxChars = options.perSkillMaxChars ?? DEFAULT_SKILL_INSTRUCTION_MAX_CHARS;
|
|
303
|
+
let remainingTotal = options.totalMaxChars ?? DEFAULT_SKILL_INSTRUCTION_TOTAL_MAX_CHARS;
|
|
304
|
+
const resolved = [];
|
|
305
|
+
const resolvedReferencePaths = new Set();
|
|
306
|
+
for (const name of skills) {
|
|
307
|
+
if (!SKILL_NAME_PATTERN.test(name)) {
|
|
308
|
+
const error = `invalid skill name: ${name}`;
|
|
309
|
+
resolved.push({
|
|
310
|
+
name,
|
|
311
|
+
resolution: "error",
|
|
312
|
+
error,
|
|
313
|
+
injectionMode: "name-only",
|
|
314
|
+
promptText: buildErrorPromptText({ name, error }),
|
|
315
|
+
});
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const searchedPaths = buildSkillCandidatePaths({
|
|
319
|
+
name,
|
|
320
|
+
cwd: options.cwd,
|
|
321
|
+
env,
|
|
322
|
+
homeDir,
|
|
323
|
+
});
|
|
324
|
+
const filePath = await findFirstExistingFile(searchedPaths);
|
|
325
|
+
if (!filePath) {
|
|
326
|
+
resolved.push({
|
|
327
|
+
name,
|
|
328
|
+
resolution: "missing",
|
|
329
|
+
searchedPaths,
|
|
330
|
+
injectionMode: "name-only",
|
|
331
|
+
promptText: buildMissingPromptText({ name, searchedPaths }),
|
|
332
|
+
});
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const content = await readFile(filePath, "utf-8");
|
|
337
|
+
const { frontmatter, body } = splitSkillFrontmatter(content);
|
|
338
|
+
const instructionContent = frontmatter ? body : content;
|
|
339
|
+
const referenceDeclarations = frontmatter
|
|
340
|
+
? parseSkillReferenceDeclarations(frontmatter)
|
|
341
|
+
: [];
|
|
342
|
+
const contentSha256 = sha256(content);
|
|
343
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
344
|
+
const budget = Math.max(0, Math.min(perSkillMaxChars, remainingTotal));
|
|
345
|
+
remainingTotal = Math.max(0, remainingTotal - Math.min(instructionContent.length, budget));
|
|
346
|
+
const { sections: referenceSections, references, unresolvedReferences, remainingTotal: afterReferences, } = await resolveSkillReferences({
|
|
347
|
+
declarations: referenceDeclarations,
|
|
348
|
+
skillDir: path.dirname(filePath),
|
|
349
|
+
remainingTotal,
|
|
350
|
+
perReferenceMaxChars: perSkillMaxChars,
|
|
351
|
+
resolvedReferencePaths,
|
|
352
|
+
});
|
|
353
|
+
remainingTotal = afterReferences;
|
|
354
|
+
const { promptText, truncated } = buildResolvedPromptText({
|
|
355
|
+
name,
|
|
356
|
+
filePath,
|
|
357
|
+
content: instructionContent,
|
|
358
|
+
contentSha256,
|
|
359
|
+
bytes,
|
|
360
|
+
maxChars: budget,
|
|
361
|
+
referenceSections,
|
|
362
|
+
});
|
|
363
|
+
resolved.push({
|
|
364
|
+
name,
|
|
365
|
+
resolution: "resolved",
|
|
366
|
+
path: filePath,
|
|
367
|
+
sha256: contentSha256,
|
|
368
|
+
bytes,
|
|
369
|
+
truncated,
|
|
370
|
+
injectionMode: "bounded-inline",
|
|
371
|
+
...(references.length > 0 ? { references } : {}),
|
|
372
|
+
...(unresolvedReferences.length > 0
|
|
373
|
+
? { unresolvedReferences }
|
|
374
|
+
: {}),
|
|
375
|
+
promptText,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
380
|
+
resolved.push({
|
|
381
|
+
name,
|
|
382
|
+
resolution: "error",
|
|
383
|
+
path: filePath,
|
|
384
|
+
error: message,
|
|
385
|
+
injectionMode: "name-only",
|
|
386
|
+
promptText: buildErrorPromptText({ name, error: message }),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (options.includeLearnedPatterns && skills.includes("loop-agent")) {
|
|
391
|
+
const learned = await resolveLearnedPatternInstructions({
|
|
392
|
+
cwd: options.cwd,
|
|
393
|
+
maxChars: Math.max(0, Math.min(perSkillMaxChars, remainingTotal)),
|
|
394
|
+
});
|
|
395
|
+
if (learned) {
|
|
396
|
+
resolved.push(learned);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return resolved;
|
|
400
|
+
}
|
|
401
|
+
async function findFirstExistingFile(candidates) {
|
|
402
|
+
for (const candidate of candidates) {
|
|
403
|
+
if (await fileExists(candidate))
|
|
404
|
+
return candidate;
|
|
405
|
+
}
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
async function resolveLearnedPatternInstructions(input) {
|
|
409
|
+
const repoSlug = path.basename(input.cwd).replace(/[^A-Za-z0-9_-]+/g, "-");
|
|
410
|
+
const candidates = [
|
|
411
|
+
normalizeCandidate(path.join(input.cwd, "skills", "loop-agent", "references", "learned", `${repoSlug}.md`)),
|
|
412
|
+
normalizeCandidate(path.join(input.cwd, "skills", "loop-agent", "references", "learned", "default.md")),
|
|
413
|
+
normalizeCandidate(path.join(input.cwd, "tools", "loop-agent", "skill", "references", "learned", `${repoSlug}.md`)),
|
|
414
|
+
normalizeCandidate(path.join(input.cwd, "tools", "loop-agent", "skill", "references", "learned", "default.md")),
|
|
415
|
+
];
|
|
416
|
+
const filePath = await findFirstExistingFile(candidates);
|
|
417
|
+
if (!filePath)
|
|
418
|
+
return undefined;
|
|
419
|
+
try {
|
|
420
|
+
const content = await readFile(filePath, "utf-8");
|
|
421
|
+
const selected = selectTopLearnedSections(content, input.maxChars);
|
|
422
|
+
const contentSha256 = sha256(content);
|
|
423
|
+
return {
|
|
424
|
+
name: "loop-agent-learned",
|
|
425
|
+
resolution: "resolved",
|
|
426
|
+
path: filePath,
|
|
427
|
+
sha256: contentSha256,
|
|
428
|
+
bytes: Buffer.byteLength(content, "utf-8"),
|
|
429
|
+
truncated: selected.length < content.length,
|
|
430
|
+
injectionMode: "bounded-inline",
|
|
431
|
+
promptText: [
|
|
432
|
+
"Skill: loop-agent-learned",
|
|
433
|
+
"Resolution: resolved",
|
|
434
|
+
`Path: ${filePath}`,
|
|
435
|
+
`Sha256: ${contentSha256}`,
|
|
436
|
+
"Injection mode: bounded-inline",
|
|
437
|
+
"Instructions:",
|
|
438
|
+
"Human-gated learned repair patterns for implementer nodes:",
|
|
439
|
+
selected || "(empty learned guidance)",
|
|
440
|
+
].join("\n"),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
445
|
+
return {
|
|
446
|
+
name: "loop-agent-learned",
|
|
447
|
+
resolution: "error",
|
|
448
|
+
path: filePath,
|
|
449
|
+
error: message,
|
|
450
|
+
injectionMode: "name-only",
|
|
451
|
+
promptText: buildErrorPromptText({
|
|
452
|
+
name: "loop-agent-learned",
|
|
453
|
+
error: message,
|
|
454
|
+
}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function selectTopLearnedSections(content, maxChars) {
|
|
459
|
+
if (maxChars <= 0)
|
|
460
|
+
return "";
|
|
461
|
+
const sections = content
|
|
462
|
+
.split(/\n(?=##\s+)/)
|
|
463
|
+
.filter((section) => section.trim().length > 0)
|
|
464
|
+
.slice(0, 3)
|
|
465
|
+
.join("\n")
|
|
466
|
+
.trim();
|
|
467
|
+
const selected = sections || content.trim();
|
|
468
|
+
return selected.length > maxChars
|
|
469
|
+
? `${selected.slice(0, maxChars)}\n[learned patterns truncated at ${maxChars} chars]`
|
|
470
|
+
: selected;
|
|
471
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in role skill defaults (prompt-level guidance only; no Pi ResourceLoader).
|
|
3
|
+
* Applied when a node has a role but the spec omits skillsByRole[role].
|
|
4
|
+
*/
|
|
5
|
+
export const DEFAULT_SKILLS_BY_ROLE = {
|
|
6
|
+
planner: ["loop-agent"],
|
|
7
|
+
scout: ["ai-engineering-context"],
|
|
8
|
+
implementer: ["verification-before-completion"],
|
|
9
|
+
verifier: ["verification-before-completion", "systematic-debugging"],
|
|
10
|
+
reviewer: ["requesting-code-review"],
|
|
11
|
+
supervisor: ["verification-before-completion", "loop-agent"],
|
|
12
|
+
closeout: ["loop-agent", "verification-before-completion"],
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Resolve prompt-level skills for a DAG node.
|
|
16
|
+
*
|
|
17
|
+
* Merge order (first occurrence wins on dedup):
|
|
18
|
+
* defaults.skills + skillsByRole[role] + node.skills
|
|
19
|
+
*
|
|
20
|
+
* When skillsByRole[role] is absent, built-in role defaults from
|
|
21
|
+
* DEFAULT_SKILLS_BY_ROLE are used instead.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveDagNodeSkills(spec, task) {
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const resolved = [];
|
|
26
|
+
const add = (skills) => {
|
|
27
|
+
for (const skill of skills ?? []) {
|
|
28
|
+
if (seen.has(skill))
|
|
29
|
+
continue;
|
|
30
|
+
seen.add(skill);
|
|
31
|
+
resolved.push(skill);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
add(spec.defaults?.skills);
|
|
35
|
+
if (task.role) {
|
|
36
|
+
const roleSkills = spec.skillsByRole?.[task.role] ?? DEFAULT_SKILLS_BY_ROLE[task.role];
|
|
37
|
+
add(roleSkills);
|
|
38
|
+
}
|
|
39
|
+
add(task.skills);
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kahn's algorithm: each rank is the set of nodes with in-degree zero at that step.
|
|
3
|
+
*/
|
|
4
|
+
export function topoSortToRanks(spec) {
|
|
5
|
+
const inDegree = new Map();
|
|
6
|
+
for (const task of spec.tasks) {
|
|
7
|
+
inDegree.set(task.id, task.depends_on.length);
|
|
8
|
+
}
|
|
9
|
+
const processed = new Set();
|
|
10
|
+
const ranks = [];
|
|
11
|
+
while (processed.size < spec.tasks.length) {
|
|
12
|
+
const rank = spec.tasks
|
|
13
|
+
.filter((task) => !processed.has(task.id) && (inDegree.get(task.id) ?? 0) === 0)
|
|
14
|
+
.map((task) => task.id);
|
|
15
|
+
if (rank.length === 0) {
|
|
16
|
+
const remaining = spec.tasks.filter((task) => !processed.has(task.id)).map((task) => task.id);
|
|
17
|
+
return { ranks, hasCycle: true, cycleNodes: remaining };
|
|
18
|
+
}
|
|
19
|
+
ranks.push(rank);
|
|
20
|
+
for (const id of rank) {
|
|
21
|
+
processed.add(id);
|
|
22
|
+
for (const task of spec.tasks) {
|
|
23
|
+
if (task.depends_on.includes(id)) {
|
|
24
|
+
inDegree.set(task.id, (inDegree.get(task.id) ?? 0) - 1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { ranks, hasCycle: false, cycleNodes: [] };
|
|
30
|
+
}
|