@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,405 @@
|
|
|
1
|
+
import { resolveShellCommands } from "../../executors/shell-executor.js";
|
|
2
|
+
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
3
|
+
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
4
|
+
import { topoSortToRanks } from "./topo.js";
|
|
5
|
+
/** Delivery signals that require supervised-implementation under --profile auto. */
|
|
6
|
+
export const SUPERVISED_QUALITY_GATE_DELIVERY_SIGNALS = [
|
|
7
|
+
"loop-agent-runtime-paths",
|
|
8
|
+
"scripts-ci-harness-paths",
|
|
9
|
+
"public-contract-paths",
|
|
10
|
+
];
|
|
11
|
+
const PLACEHOLDER_WRITESET_MARKER = "REPLACE/WITH";
|
|
12
|
+
const DELIVERY_PATH_RULES = [
|
|
13
|
+
{
|
|
14
|
+
id: "public-contract-paths",
|
|
15
|
+
patterns: [
|
|
16
|
+
"docs/templates/**",
|
|
17
|
+
"**/*.schema.json",
|
|
18
|
+
"docs/**/prompt*.md",
|
|
19
|
+
"**/*prompt-template*",
|
|
20
|
+
],
|
|
21
|
+
message: "writeSet touches public contract/schema/prompt-template paths",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "loop-agent-runtime-paths",
|
|
25
|
+
patterns: [
|
|
26
|
+
"./src/workflows/dag/**",
|
|
27
|
+
"./src/task/**",
|
|
28
|
+
"./src/executors/**",
|
|
29
|
+
"./src/records/**",
|
|
30
|
+
"./src/governance/**",
|
|
31
|
+
"./src/shared/**",
|
|
32
|
+
"./src/adapters/**",
|
|
33
|
+
"./src/commands/**",
|
|
34
|
+
],
|
|
35
|
+
message: "writeSet touches loop-agent runtime/workflow/executor paths",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "scripts-ci-harness-paths",
|
|
39
|
+
patterns: ["scripts/**", "**/ci*.sh", ".github/**"],
|
|
40
|
+
message: "writeSet touches scripts/CI/harness governance paths",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "high-failure-cost-paths",
|
|
44
|
+
patterns: [
|
|
45
|
+
"**/agents/memory/**",
|
|
46
|
+
"**/agents/skills/**",
|
|
47
|
+
"~/.agents/**",
|
|
48
|
+
"~/GoogleDrive/agents/**",
|
|
49
|
+
],
|
|
50
|
+
message: "writeSet touches high failure-cost memory/agent-extension paths",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
const CLI_CONTRACT_PATHS = [
|
|
54
|
+
"./src/cli.ts",
|
|
55
|
+
"./src/cli/index.ts",
|
|
56
|
+
];
|
|
57
|
+
function normalizePath(value) {
|
|
58
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
59
|
+
}
|
|
60
|
+
function resolveWritePolicy(task, spec) {
|
|
61
|
+
return task.writePolicy ?? spec.defaults?.writePolicy;
|
|
62
|
+
}
|
|
63
|
+
function isExclusiveWriter(task, spec) {
|
|
64
|
+
return resolveWritePolicy(task, spec) === "exclusive";
|
|
65
|
+
}
|
|
66
|
+
function isImplementer(task) {
|
|
67
|
+
return task.role === "implementer";
|
|
68
|
+
}
|
|
69
|
+
function isRepairNode(task) {
|
|
70
|
+
return /repair/i.test(task.id);
|
|
71
|
+
}
|
|
72
|
+
function forbiddenPathsBeyondBaseline(forbiddenPaths) {
|
|
73
|
+
return forbiddenPaths.filter((entry) => !BASELINE_FORBIDDEN_PATHS.some((baseline) => pathMatchesPattern(entry, baseline)) &&
|
|
74
|
+
!BASELINE_FORBIDDEN_PATHS.some((baseline) => pathMatchesPattern(baseline, entry)));
|
|
75
|
+
}
|
|
76
|
+
function isBroadWriteSetEntry(entry) {
|
|
77
|
+
const normalized = normalizePath(entry.trim());
|
|
78
|
+
if (!normalized || normalized === "." || normalized === "./")
|
|
79
|
+
return true;
|
|
80
|
+
if (normalized === "**")
|
|
81
|
+
return true;
|
|
82
|
+
return normalized.includes(PLACEHOLDER_WRITESET_MARKER);
|
|
83
|
+
}
|
|
84
|
+
function topLevelModules(writeSet) {
|
|
85
|
+
const modules = new Set();
|
|
86
|
+
for (const entry of writeSet) {
|
|
87
|
+
const normalized = normalizePath(entry);
|
|
88
|
+
const first = normalized.split("/")[0];
|
|
89
|
+
if (first && first !== "**") {
|
|
90
|
+
modules.add(first);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return modules;
|
|
94
|
+
}
|
|
95
|
+
function writeSetMatchesAnyPattern(writeSet, patterns) {
|
|
96
|
+
for (const entry of writeSet) {
|
|
97
|
+
const normalized = normalizePath(entry);
|
|
98
|
+
for (const pattern of patterns) {
|
|
99
|
+
if (pathMatchesPattern(normalized, pattern)) {
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
function shellCommandLooksDeterministic(command) {
|
|
107
|
+
return (/\bnpx vitest run\b/.test(command) ||
|
|
108
|
+
/\bnpm run (lint|typecheck|test)\b/.test(command) ||
|
|
109
|
+
/check-repo\.sh/.test(command) ||
|
|
110
|
+
/\bshell\.preset\b/.test(command) ||
|
|
111
|
+
/loop-agent-standard-verify/.test(command));
|
|
112
|
+
}
|
|
113
|
+
function taskHasDeterministicShellVerification(task) {
|
|
114
|
+
if (task.executor !== "shell")
|
|
115
|
+
return false;
|
|
116
|
+
const commands = resolveShellCommands(task.shell ?? { commands: [] });
|
|
117
|
+
return commands.some(shellCommandLooksDeterministic);
|
|
118
|
+
}
|
|
119
|
+
function collectExclusiveWriters(spec) {
|
|
120
|
+
return spec.tasks.filter((task) => isExclusiveWriter(task, spec) && (task.writeSet?.length ?? 0) > 0);
|
|
121
|
+
}
|
|
122
|
+
function collectImplementerWriters(spec) {
|
|
123
|
+
return collectExclusiveWriters(spec).filter(isImplementer);
|
|
124
|
+
}
|
|
125
|
+
function findLastImplementerRankIndex(spec, ranks) {
|
|
126
|
+
const implementerIds = new Set(collectImplementerWriters(spec).map((task) => task.id));
|
|
127
|
+
let lastRank = -1;
|
|
128
|
+
for (let index = 0; index < ranks.length; index += 1) {
|
|
129
|
+
if (ranks[index].some((id) => implementerIds.has(id))) {
|
|
130
|
+
lastRank = index;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return lastRank;
|
|
134
|
+
}
|
|
135
|
+
function hasPostImplementationShellVerification(spec, ranks) {
|
|
136
|
+
const lastImplementerRank = findLastImplementerRankIndex(spec, ranks);
|
|
137
|
+
if (lastImplementerRank < 0)
|
|
138
|
+
return false;
|
|
139
|
+
const taskById = new Map(spec.tasks.map((task) => [task.id, task]));
|
|
140
|
+
for (let index = lastImplementerRank + 1; index < ranks.length; index += 1) {
|
|
141
|
+
for (const taskId of ranks[index] ?? []) {
|
|
142
|
+
const task = taskById.get(taskId);
|
|
143
|
+
if (task && taskHasDeterministicShellVerification(task)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
function hasSupervisedTopology(spec) {
|
|
151
|
+
const hasSupervisor = spec.tasks.some((task) => task.role === "supervisor");
|
|
152
|
+
const hasRepairWriter = spec.tasks.some((task) => isRepairNode(task) && isExclusiveWriter(task, spec));
|
|
153
|
+
const verdictGates = spec.tasks.filter((task) => task.shell?.verdictGate);
|
|
154
|
+
const hasWriteSetGate = spec.tasks.some((task) => /write-set-gate|write-set-audit/i.test(task.id));
|
|
155
|
+
const hasProcessGate = spec.tasks.some((task) => /process-gate|process-supervisor/i.test(task.id));
|
|
156
|
+
const hasReviewGate = spec.tasks.some((task) => /review-gate/i.test(task.id));
|
|
157
|
+
return (hasSupervisor &&
|
|
158
|
+
hasRepairWriter &&
|
|
159
|
+
verdictGates.length >= 2 &&
|
|
160
|
+
hasWriteSetGate &&
|
|
161
|
+
hasProcessGate &&
|
|
162
|
+
hasReviewGate);
|
|
163
|
+
}
|
|
164
|
+
function hasReviewGateTopology(spec) {
|
|
165
|
+
return spec.tasks.some((task) => task.executor === "shell" &&
|
|
166
|
+
(/review-gate/i.test(task.id) || task.shell?.verdictGate !== undefined) &&
|
|
167
|
+
task.depends_on.some((dep) => /review/i.test(dep)));
|
|
168
|
+
}
|
|
169
|
+
function collectProcessSignals(spec, ranks) {
|
|
170
|
+
const signals = [];
|
|
171
|
+
const writers = collectExclusiveWriters(spec);
|
|
172
|
+
if (writers.length > 1) {
|
|
173
|
+
signals.push({
|
|
174
|
+
id: "multiple-exclusive-writers",
|
|
175
|
+
category: "process",
|
|
176
|
+
message: `DAG has ${writers.length} exclusive writer nodes (${writers.map((task) => task.id).join(", ")})`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const repairNodes = spec.tasks.filter((task) => isRepairNode(task) && isExclusiveWriter(task, spec));
|
|
180
|
+
if (repairNodes.length > 0) {
|
|
181
|
+
signals.push({
|
|
182
|
+
id: "repair-node-present",
|
|
183
|
+
category: "process",
|
|
184
|
+
message: `repair exclusive writer present: ${repairNodes.map((task) => task.id).join(", ")}`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
for (const writer of writers) {
|
|
188
|
+
const extraForbidden = forbiddenPathsBeyondBaseline(writer.forbiddenPaths ?? []);
|
|
189
|
+
if (extraForbidden.length > 0) {
|
|
190
|
+
signals.push({
|
|
191
|
+
id: "non-baseline-forbidden-on-writer",
|
|
192
|
+
category: "process",
|
|
193
|
+
message: `writer ${writer.id} declares non-baseline forbiddenPaths: ${extraForbidden.join(", ")}`,
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const writer of writers) {
|
|
199
|
+
for (const entry of writer.writeSet ?? []) {
|
|
200
|
+
if (isBroadWriteSetEntry(entry)) {
|
|
201
|
+
signals.push({
|
|
202
|
+
id: "broad-write-set",
|
|
203
|
+
category: "process",
|
|
204
|
+
message: `writer ${writer.id} has broad/placeholder/root writeSet entry "${entry}"`,
|
|
205
|
+
});
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (signals.some((signal) => signal.id === "broad-write-set"))
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
for (const writer of collectImplementerWriters(spec)) {
|
|
213
|
+
if (writer.complexity === "HIGH") {
|
|
214
|
+
signals.push({
|
|
215
|
+
id: "high-complexity-implementer",
|
|
216
|
+
category: "process",
|
|
217
|
+
message: `HIGH complexity implementer ${writer.id} increases attribution/repair risk`,
|
|
218
|
+
});
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const mergedWriteSet = writers.flatMap((task) => task.writeSet ?? []);
|
|
223
|
+
if (topLevelModules(mergedWriteSet).size > 1) {
|
|
224
|
+
signals.push({
|
|
225
|
+
id: "cross-module-write-set",
|
|
226
|
+
category: "process",
|
|
227
|
+
message: `exclusive writeSet spans multiple top-level modules: ${[...topLevelModules(mergedWriteSet)].join(", ")}`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (collectImplementerWriters(spec).length > 0 &&
|
|
231
|
+
!hasPostImplementationShellVerification(spec, ranks)) {
|
|
232
|
+
signals.push({
|
|
233
|
+
id: "weak-post-implementation-verification",
|
|
234
|
+
category: "process",
|
|
235
|
+
message: "no deterministic shell verification node after the last implementer (preset/vitest/check-repo)",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return signals;
|
|
239
|
+
}
|
|
240
|
+
function collectDeliverySignals(spec) {
|
|
241
|
+
const signals = [];
|
|
242
|
+
const writers = collectExclusiveWriters(spec);
|
|
243
|
+
const mergedWriteSet = writers.flatMap((task) => task.writeSet ?? []);
|
|
244
|
+
for (const rule of DELIVERY_PATH_RULES) {
|
|
245
|
+
const hit = writeSetMatchesAnyPattern(mergedWriteSet, rule.patterns);
|
|
246
|
+
if (hit) {
|
|
247
|
+
signals.push({
|
|
248
|
+
id: rule.id,
|
|
249
|
+
category: "delivery",
|
|
250
|
+
message: `${rule.message} (matched "${hit}")`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const cliHit = writeSetMatchesAnyPattern(mergedWriteSet, CLI_CONTRACT_PATHS);
|
|
255
|
+
if (cliHit) {
|
|
256
|
+
signals.push({
|
|
257
|
+
id: "cli-contract-paths",
|
|
258
|
+
category: "delivery",
|
|
259
|
+
message: `writeSet touches CLI contract paths (matched "${cliHit}")`,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
const srcEntries = mergedWriteSet.filter((entry) => {
|
|
263
|
+
const normalized = normalizePath(entry);
|
|
264
|
+
return (/\/src\//.test(normalized) &&
|
|
265
|
+
!/\/test\//.test(normalized) &&
|
|
266
|
+
!normalized.endsWith(".test.ts"));
|
|
267
|
+
});
|
|
268
|
+
const testEntries = mergedWriteSet.filter((entry) => {
|
|
269
|
+
const normalized = normalizePath(entry);
|
|
270
|
+
return /\/test\//.test(normalized) || normalized.endsWith(".test.ts");
|
|
271
|
+
});
|
|
272
|
+
if (srcEntries.length > 0 && testEntries.length === 0) {
|
|
273
|
+
signals.push({
|
|
274
|
+
id: "missing-focused-test-evidence",
|
|
275
|
+
category: "delivery",
|
|
276
|
+
message: "writeSet changes source paths without paired focused regression test paths",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return signals;
|
|
280
|
+
}
|
|
281
|
+
function collectCodeChangeSignals(spec) {
|
|
282
|
+
const signals = [];
|
|
283
|
+
for (const writer of collectExclusiveWriters(spec)) {
|
|
284
|
+
for (const entry of writer.writeSet ?? []) {
|
|
285
|
+
const normalized = normalizePath(entry);
|
|
286
|
+
if (/\/src\//.test(normalized) &&
|
|
287
|
+
!/\/test\//.test(normalized) &&
|
|
288
|
+
!normalized.endsWith(".test.ts")) {
|
|
289
|
+
signals.push({
|
|
290
|
+
id: "behavior-changing-writer",
|
|
291
|
+
category: "codeChange",
|
|
292
|
+
message: `exclusive writer ${writer.id} may change runtime behavior via "${entry}"`,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return signals;
|
|
298
|
+
}
|
|
299
|
+
function buildProfileReasons(profile, process, delivery, codeChange, supervisedTopology) {
|
|
300
|
+
const reasons = [];
|
|
301
|
+
if (profile === "supervised") {
|
|
302
|
+
reasons.push("supervised topology detected (supervisor + repair + write-set/process/review gates)");
|
|
303
|
+
return reasons;
|
|
304
|
+
}
|
|
305
|
+
if (supervisedTopology) {
|
|
306
|
+
reasons.push("partial supervised markers present but topology incomplete; profile downgraded");
|
|
307
|
+
}
|
|
308
|
+
const signalIds = [
|
|
309
|
+
...process.map((signal) => signal.id),
|
|
310
|
+
...delivery.map((signal) => signal.id),
|
|
311
|
+
...codeChange.map((signal) => signal.id),
|
|
312
|
+
];
|
|
313
|
+
if (profile === "minimal") {
|
|
314
|
+
reasons.push("single narrow writer with deterministic post-implementation shell verification and no elevated process/delivery/code-change signals");
|
|
315
|
+
return reasons;
|
|
316
|
+
}
|
|
317
|
+
if (profile === "reviewed") {
|
|
318
|
+
if (process.some((signal) => signal.id === "repair-node-present")) {
|
|
319
|
+
reasons.push("repair writer requires reviewed-or-higher governance");
|
|
320
|
+
}
|
|
321
|
+
if (process.some((signal) => signal.id === "multiple-exclusive-writers")) {
|
|
322
|
+
reasons.push("multiple exclusive writers require coordination/review gates");
|
|
323
|
+
}
|
|
324
|
+
if (delivery.some((signal) => signal.id === "loop-agent-runtime-paths")) {
|
|
325
|
+
reasons.push("loop-agent core path writes require reviewed governance");
|
|
326
|
+
}
|
|
327
|
+
if (process.some((signal) => signal.id === "weak-post-implementation-verification")) {
|
|
328
|
+
reasons.push("weak post-implementation verification elevates governance to reviewed");
|
|
329
|
+
}
|
|
330
|
+
if (signalIds.length === 0) {
|
|
331
|
+
reasons.push("explicit review-gate topology detected");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (profile === "standard") {
|
|
335
|
+
reasons.push(`standard hybrid implementation profile (${signalIds.length} governance signal(s): ${signalIds.join(", ") || "implementer present"})`);
|
|
336
|
+
}
|
|
337
|
+
return reasons;
|
|
338
|
+
}
|
|
339
|
+
function inferProfileName(spec, process, delivery, codeChange, ranks) {
|
|
340
|
+
if (hasSupervisedTopology(spec)) {
|
|
341
|
+
return "supervised";
|
|
342
|
+
}
|
|
343
|
+
const processIds = new Set(process.map((signal) => signal.id));
|
|
344
|
+
const deliveryIds = new Set(delivery.map((signal) => signal.id));
|
|
345
|
+
const hasImplementer = collectImplementerWriters(spec).length > 0;
|
|
346
|
+
if (processIds.has("repair-node-present") ||
|
|
347
|
+
processIds.has("multiple-exclusive-writers") ||
|
|
348
|
+
hasReviewGateTopology(spec) ||
|
|
349
|
+
deliveryIds.has("loop-agent-runtime-paths") ||
|
|
350
|
+
deliveryIds.has("scripts-ci-harness-paths") ||
|
|
351
|
+
(processIds.has("weak-post-implementation-verification") && hasImplementer) ||
|
|
352
|
+
(processIds.has("high-complexity-implementer") &&
|
|
353
|
+
(delivery.length > 0 || codeChange.length > 0))) {
|
|
354
|
+
return "reviewed";
|
|
355
|
+
}
|
|
356
|
+
const totalSignals = process.length + delivery.length + codeChange.length;
|
|
357
|
+
if (hasImplementer && totalSignals > 0) {
|
|
358
|
+
return "standard";
|
|
359
|
+
}
|
|
360
|
+
if (hasImplementer &&
|
|
361
|
+
totalSignals === 0 &&
|
|
362
|
+
hasPostImplementationShellVerification(spec, ranks) &&
|
|
363
|
+
collectExclusiveWriters(spec).length === 1) {
|
|
364
|
+
return "minimal";
|
|
365
|
+
}
|
|
366
|
+
return totalSignals > 1 ? "reviewed" : "standard";
|
|
367
|
+
}
|
|
368
|
+
export function requiresSupervisedQualityGate(result) {
|
|
369
|
+
const deliveryIds = new Set(result.delivery.map((signal) => signal.id));
|
|
370
|
+
const signalIds = SUPERVISED_QUALITY_GATE_DELIVERY_SIGNALS.filter((id) => deliveryIds.has(id));
|
|
371
|
+
if (signalIds.length === 0) {
|
|
372
|
+
return { required: false, signalIds: [], reasons: [] };
|
|
373
|
+
}
|
|
374
|
+
const reasons = [];
|
|
375
|
+
if (signalIds.includes("loop-agent-runtime-paths")) {
|
|
376
|
+
reasons.push("supervised-quality-gate-required: loop-agent runtime/core path writes need process supervisor, repair, and hard verification gates");
|
|
377
|
+
}
|
|
378
|
+
if (signalIds.includes("scripts-ci-harness-paths")) {
|
|
379
|
+
reasons.push("supervised-quality-gate-required: scripts/CI/harness governance path writes need supervised repair and verification gates");
|
|
380
|
+
}
|
|
381
|
+
if (signalIds.includes("public-contract-paths")) {
|
|
382
|
+
reasons.push("supervised-quality-gate-required: public contract/schema/prompt-template path writes need write-set audit and review gates before closeout");
|
|
383
|
+
}
|
|
384
|
+
return { required: true, signalIds: [...signalIds], reasons };
|
|
385
|
+
}
|
|
386
|
+
export function resolveAutoRoutingProfile(candidateProfile) {
|
|
387
|
+
const gate = requiresSupervisedQualityGate(candidateProfile);
|
|
388
|
+
return gate.required ? "supervised" : candidateProfile.profile;
|
|
389
|
+
}
|
|
390
|
+
export function inferGovernanceProfile(spec) {
|
|
391
|
+
const { ranks } = topoSortToRanks(spec);
|
|
392
|
+
const process = collectProcessSignals(spec, ranks);
|
|
393
|
+
const delivery = collectDeliverySignals(spec);
|
|
394
|
+
const codeChange = collectCodeChangeSignals(spec);
|
|
395
|
+
const supervisedTopology = hasSupervisedTopology(spec);
|
|
396
|
+
const profile = inferProfileName(spec, process, delivery, codeChange, ranks);
|
|
397
|
+
const reasons = buildProfileReasons(profile, process, delivery, codeChange, supervisedTopology);
|
|
398
|
+
return {
|
|
399
|
+
profile,
|
|
400
|
+
process,
|
|
401
|
+
delivery,
|
|
402
|
+
codeChange,
|
|
403
|
+
reasons,
|
|
404
|
+
};
|
|
405
|
+
}
|