agentplane 0.3.1 → 0.3.3
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/assets/AGENTS.md +5 -4
- package/assets/agents/CODER.json +4 -3
- package/assets/agents/DOCS.json +1 -1
- package/assets/agents/INTEGRATOR.json +1 -1
- package/assets/agents/ORCHESTRATOR.json +1 -0
- package/assets/agents/PLANNER.json +1 -0
- package/assets/agents/TESTER.json +3 -1
- package/assets/policy/dod.code.md +2 -2
- package/assets/policy/dod.core.md +16 -2
- package/assets/policy/dod.docs.md +2 -2
- package/assets/policy/incidents.md +44 -1
- package/assets/policy/workflow.direct.md +10 -5
- package/bin/agentplane.js +116 -18
- package/bin/dist-guard.js +78 -10
- package/bin/runtime-context.d.ts +23 -0
- package/bin/runtime-context.js +94 -0
- package/bin/runtime-watch.d.ts +26 -0
- package/bin/runtime-watch.js +116 -0
- package/bin/stale-dist-policy.d.ts +6 -0
- package/bin/stale-dist-policy.js +44 -0
- package/dist/.build-manifest.json +2480 -5
- package/dist/backends/task-backend/local-backend.d.ts.map +1 -1
- package/dist/backends/task-backend/local-backend.js +9 -12
- package/dist/backends/task-backend/redmine-backend.d.ts.map +1 -1
- package/dist/backends/task-backend/redmine-backend.js +23 -18
- package/dist/backends/task-backend/shared/constants.d.ts +1 -0
- package/dist/backends/task-backend/shared/constants.d.ts.map +1 -1
- package/dist/backends/task-backend/shared/constants.js +1 -0
- package/dist/backends/task-backend/shared/doc.d.ts +1 -0
- package/dist/backends/task-backend/shared/doc.d.ts.map +1 -1
- package/dist/backends/task-backend/shared/doc.js +4 -1
- package/dist/backends/task-backend/shared/export.js +3 -3
- package/dist/cli/bootstrap-guide.d.ts +16 -0
- package/dist/cli/bootstrap-guide.d.ts.map +1 -0
- package/dist/cli/bootstrap-guide.js +112 -0
- package/dist/cli/command-guide.d.ts.map +1 -1
- package/dist/cli/command-guide.js +62 -203
- package/dist/cli/command-snippets.d.ts +2 -2
- package/dist/cli/command-snippets.js +2 -2
- package/dist/cli/run-cli/catalog.d.ts +7 -0
- package/dist/cli/run-cli/catalog.d.ts.map +1 -0
- package/dist/cli/run-cli/catalog.js +22 -0
- package/dist/cli/run-cli/command-catalog.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog.js +11 -0
- package/dist/cli/run-cli/commands/core.js +1 -1
- package/dist/cli/run-cli/commands/init/write-config.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/write-config.js +2 -0
- package/dist/cli/run-cli/commands/init.js +5 -14
- package/dist/cli/run-cli/error-guidance.d.ts +9 -0
- package/dist/cli/run-cli/error-guidance.d.ts.map +1 -0
- package/dist/cli/run-cli/error-guidance.js +180 -0
- package/dist/cli/run-cli/globals.d.ts +22 -0
- package/dist/cli/run-cli/globals.d.ts.map +1 -0
- package/dist/cli/run-cli/globals.js +197 -0
- package/dist/cli/run-cli/update-warning.d.ts +6 -0
- package/dist/cli/run-cli/update-warning.d.ts.map +1 -0
- package/dist/cli/run-cli/update-warning.js +64 -0
- package/dist/cli/run-cli.d.ts.map +1 -1
- package/dist/cli/run-cli.js +5 -476
- package/dist/cli/spec/docs-render.d.ts.map +1 -1
- package/dist/cli/spec/docs-render.js +14 -1
- package/dist/commands/doctor/archive.d.ts +4 -0
- package/dist/commands/doctor/archive.d.ts.map +1 -0
- package/dist/commands/doctor/archive.js +211 -0
- package/dist/commands/doctor/fixes.d.ts +9 -0
- package/dist/commands/doctor/fixes.d.ts.map +1 -0
- package/dist/commands/doctor/fixes.js +40 -0
- package/dist/commands/doctor/layering.d.ts +2 -0
- package/dist/commands/doctor/layering.d.ts.map +1 -0
- package/dist/commands/doctor/layering.js +87 -0
- package/dist/commands/doctor/runtime.d.ts +4 -0
- package/dist/commands/doctor/runtime.d.ts.map +1 -0
- package/dist/commands/doctor/runtime.js +56 -0
- package/dist/commands/doctor/workflow.d.ts +6 -0
- package/dist/commands/doctor/workflow.d.ts.map +1 -0
- package/dist/commands/doctor/workflow.js +62 -0
- package/dist/commands/doctor/workspace.d.ts +2 -0
- package/dist/commands/doctor/workspace.d.ts.map +1 -0
- package/dist/commands/doctor/workspace.js +165 -0
- package/dist/commands/doctor.run.d.ts.map +1 -1
- package/dist/commands/doctor.run.js +16 -305
- package/dist/commands/doctor.spec.d.ts +1 -0
- package/dist/commands/doctor.spec.d.ts.map +1 -1
- package/dist/commands/doctor.spec.js +15 -1
- package/dist/commands/finish.run.d.ts.map +1 -1
- package/dist/commands/finish.run.js +1 -0
- package/dist/commands/finish.spec.d.ts +1 -0
- package/dist/commands/finish.spec.d.ts.map +1 -1
- package/dist/commands/finish.spec.js +23 -2
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +19 -0
- package/dist/commands/release/apply.command.d.ts +2 -7
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +159 -382
- package/dist/commands/release/apply.mutation.d.ts +7 -0
- package/dist/commands/release/apply.mutation.d.ts.map +1 -0
- package/dist/commands/release/apply.mutation.js +107 -0
- package/dist/commands/release/apply.preflight.d.ts +25 -0
- package/dist/commands/release/apply.preflight.d.ts.map +1 -0
- package/dist/commands/release/apply.preflight.js +338 -0
- package/dist/commands/release/apply.reporting.d.ts +4 -0
- package/dist/commands/release/apply.reporting.d.ts.map +1 -0
- package/dist/commands/release/apply.reporting.js +24 -0
- package/dist/commands/release/apply.types.d.ts +46 -0
- package/dist/commands/release/apply.types.d.ts.map +1 -0
- package/dist/commands/release/apply.types.js +1 -0
- package/dist/commands/runtime.command.d.ts +28 -0
- package/dist/commands/runtime.command.d.ts.map +1 -0
- package/dist/commands/runtime.command.js +169 -0
- package/dist/commands/shared/task-store.d.ts.map +1 -1
- package/dist/commands/shared/task-store.js +7 -3
- package/dist/commands/task/add.d.ts.map +1 -1
- package/dist/commands/task/add.js +3 -33
- package/dist/commands/task/block.d.ts.map +1 -1
- package/dist/commands/task/block.js +2 -2
- package/dist/commands/task/close-duplicate.d.ts.map +1 -1
- package/dist/commands/task/close-duplicate.js +2 -2
- package/dist/commands/task/close-noop.d.ts.map +1 -1
- package/dist/commands/task/close-noop.js +2 -2
- package/dist/commands/task/comment.js +2 -2
- package/dist/commands/task/derive.d.ts.map +1 -1
- package/dist/commands/task/derive.js +3 -3
- package/dist/commands/task/doc-template.d.ts +10 -0
- package/dist/commands/task/doc-template.d.ts.map +1 -0
- package/dist/commands/task/doc-template.js +104 -0
- package/dist/commands/task/doc.d.ts.map +1 -1
- package/dist/commands/task/doc.js +36 -1
- package/dist/commands/task/finish.d.ts +1 -0
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +26 -10
- package/dist/commands/task/migrate-doc.command.d.ts.map +1 -1
- package/dist/commands/task/migrate-doc.command.js +5 -1
- package/dist/commands/task/migrate-doc.d.ts.map +1 -1
- package/dist/commands/task/migrate-doc.js +136 -2
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +4 -110
- package/dist/commands/task/new.spec.js +3 -3
- package/dist/commands/task/plan.d.ts.map +1 -1
- package/dist/commands/task/plan.js +5 -4
- package/dist/commands/task/scaffold.d.ts.map +1 -1
- package/dist/commands/task/scaffold.js +7 -52
- package/dist/commands/task/set-status.d.ts.map +1 -1
- package/dist/commands/task/set-status.js +2 -2
- package/dist/commands/task/shared/dependencies.d.ts +15 -0
- package/dist/commands/task/shared/dependencies.d.ts.map +1 -0
- package/dist/commands/task/shared/dependencies.js +143 -0
- package/dist/commands/task/shared/docs.d.ts +21 -0
- package/dist/commands/task/shared/docs.d.ts.map +1 -0
- package/dist/commands/task/shared/docs.js +121 -0
- package/dist/commands/task/shared/listing.d.ts +20 -0
- package/dist/commands/task/shared/listing.d.ts.map +1 -0
- package/dist/commands/task/shared/listing.js +127 -0
- package/dist/commands/task/shared/tags.d.ts +24 -0
- package/dist/commands/task/shared/tags.d.ts.map +1 -0
- package/dist/commands/task/shared/tags.js +177 -0
- package/dist/commands/task/shared/transitions.d.ts +42 -0
- package/dist/commands/task/shared/transitions.d.ts.map +1 -0
- package/dist/commands/task/shared/transitions.js +175 -0
- package/dist/commands/task/shared.d.ts +5 -106
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +5 -681
- package/dist/commands/task/start.d.ts.map +1 -1
- package/dist/commands/task/start.js +7 -5
- package/dist/commands/task/verify-record.d.ts.map +1 -1
- package/dist/commands/task/verify-record.js +9 -25
- package/dist/commands/task/verify-show.command.d.ts.map +1 -1
- package/dist/commands/task/verify-show.command.js +5 -1
- package/dist/commands/upgrade/apply.d.ts +44 -0
- package/dist/commands/upgrade/apply.d.ts.map +1 -0
- package/dist/commands/upgrade/apply.js +180 -0
- package/dist/commands/upgrade/report.d.ts +21 -0
- package/dist/commands/upgrade/report.d.ts.map +1 -0
- package/dist/commands/upgrade/report.js +81 -0
- package/dist/commands/upgrade/source.d.ts +35 -0
- package/dist/commands/upgrade/source.d.ts.map +1 -0
- package/dist/commands/upgrade/source.js +109 -0
- package/dist/commands/upgrade/types.d.ts +31 -0
- package/dist/commands/upgrade/types.d.ts.map +1 -0
- package/dist/commands/upgrade/types.js +1 -0
- package/dist/commands/upgrade.command.d.ts.map +1 -1
- package/dist/commands/upgrade.command.js +11 -7
- package/dist/commands/upgrade.d.ts +1 -35
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +54 -320
- package/dist/shared/diagnostics.d.ts +23 -0
- package/dist/shared/diagnostics.d.ts.map +1 -0
- package/dist/shared/diagnostics.js +57 -0
- package/dist/shared/errors.d.ts +2 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +2 -0
- package/dist/shared/repo-cli-version.d.ts +13 -0
- package/dist/shared/repo-cli-version.d.ts.map +1 -0
- package/dist/shared/repo-cli-version.js +63 -0
- package/dist/shared/runtime-source.d.ts +33 -0
- package/dist/shared/runtime-source.d.ts.map +1 -0
- package/dist/shared/runtime-source.js +156 -0
- package/dist/shared/version-compare.d.ts +7 -0
- package/dist/shared/version-compare.d.ts.map +1 -0
- package/dist/shared/version-compare.js +30 -0
- package/package.json +2 -2
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { CliError } from "../../../shared/errors.js";
|
|
2
|
+
import { dedupeStrings } from "../../../shared/strings.js";
|
|
3
|
+
import { toStringArray } from "./tags.js";
|
|
4
|
+
function hasDependsOnCycle(dependsOnMap) {
|
|
5
|
+
const visiting = new Set();
|
|
6
|
+
const visited = new Set();
|
|
7
|
+
const stack = [];
|
|
8
|
+
function dfs(taskId) {
|
|
9
|
+
if (visited.has(taskId))
|
|
10
|
+
return null;
|
|
11
|
+
if (visiting.has(taskId)) {
|
|
12
|
+
const start = stack.indexOf(taskId);
|
|
13
|
+
return start === -1 ? [taskId] : [...stack.slice(start), taskId];
|
|
14
|
+
}
|
|
15
|
+
visiting.add(taskId);
|
|
16
|
+
stack.push(taskId);
|
|
17
|
+
const deps = dependsOnMap.get(taskId) ?? [];
|
|
18
|
+
for (const depId of deps) {
|
|
19
|
+
const cycle = dfs(depId);
|
|
20
|
+
if (cycle)
|
|
21
|
+
return cycle;
|
|
22
|
+
}
|
|
23
|
+
stack.pop();
|
|
24
|
+
visiting.delete(taskId);
|
|
25
|
+
visited.add(taskId);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
for (const taskId of dependsOnMap.keys()) {
|
|
29
|
+
const cycle = dfs(taskId);
|
|
30
|
+
if (cycle)
|
|
31
|
+
return cycle;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export async function ensureTaskDependsOnGraphIsAcyclic(opts) {
|
|
36
|
+
const nextDepends = dedupeStrings(opts.dependsOn);
|
|
37
|
+
if (nextDepends.includes(opts.taskId)) {
|
|
38
|
+
throw new CliError({
|
|
39
|
+
exitCode: 2,
|
|
40
|
+
code: "E_USAGE",
|
|
41
|
+
message: `depends_on cannot include task itself (${opts.taskId})`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const allTasks = await opts.backend.listTasks();
|
|
45
|
+
const depMap = new Map();
|
|
46
|
+
for (const task of allTasks) {
|
|
47
|
+
const taskId = String(task.id || "").trim();
|
|
48
|
+
if (!taskId)
|
|
49
|
+
continue;
|
|
50
|
+
if (taskId === opts.taskId)
|
|
51
|
+
continue;
|
|
52
|
+
depMap.set(taskId, dedupeStrings(toStringArray(task.depends_on)));
|
|
53
|
+
}
|
|
54
|
+
depMap.set(opts.taskId, nextDepends);
|
|
55
|
+
const cycle = hasDependsOnCycle(depMap);
|
|
56
|
+
if (!cycle)
|
|
57
|
+
return;
|
|
58
|
+
throw new CliError({
|
|
59
|
+
exitCode: 2,
|
|
60
|
+
code: "E_USAGE",
|
|
61
|
+
message: `depends_on cycle detected: ${cycle.join(" -> ")}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function resolveTaskDependencyState(task, backend) {
|
|
65
|
+
const dependsOn = dedupeStrings(toStringArray(task.depends_on));
|
|
66
|
+
if (dependsOn.length === 0) {
|
|
67
|
+
return { dependsOn, missing: [], incomplete: [] };
|
|
68
|
+
}
|
|
69
|
+
const loaded = backend.getTasks
|
|
70
|
+
? await backend.getTasks(dependsOn)
|
|
71
|
+
: await Promise.all(dependsOn.map(async (depId) => await backend.getTask(depId)));
|
|
72
|
+
const missing = [];
|
|
73
|
+
const incomplete = [];
|
|
74
|
+
for (const [idx, depId] of dependsOn.entries()) {
|
|
75
|
+
const dep = loaded[idx] ?? null;
|
|
76
|
+
if (!dep) {
|
|
77
|
+
missing.push(depId);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const status = String(dep.status || "TODO").toUpperCase();
|
|
81
|
+
if (status !== "DONE")
|
|
82
|
+
incomplete.push(depId);
|
|
83
|
+
}
|
|
84
|
+
return { dependsOn, missing, incomplete };
|
|
85
|
+
}
|
|
86
|
+
export function buildDependencyState(tasks) {
|
|
87
|
+
const byId = new Map(tasks.map((task) => [task.id, task]));
|
|
88
|
+
const state = new Map();
|
|
89
|
+
for (const task of tasks) {
|
|
90
|
+
const dependsOn = dedupeStrings(toStringArray(task.depends_on));
|
|
91
|
+
const missing = [];
|
|
92
|
+
const incomplete = [];
|
|
93
|
+
for (const depId of dependsOn) {
|
|
94
|
+
const dep = byId.get(depId);
|
|
95
|
+
if (!dep) {
|
|
96
|
+
missing.push(depId);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const status = String(dep.status || "TODO").toUpperCase();
|
|
100
|
+
if (status !== "DONE") {
|
|
101
|
+
incomplete.push(depId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
state.set(task.id, { dependsOn, missing, incomplete });
|
|
105
|
+
}
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
function formatDepsSummary(dep) {
|
|
109
|
+
if (!dep)
|
|
110
|
+
return null;
|
|
111
|
+
if (dep.dependsOn.length === 0)
|
|
112
|
+
return "deps=none";
|
|
113
|
+
if (dep.missing.length === 0 && dep.incomplete.length === 0)
|
|
114
|
+
return "deps=ready";
|
|
115
|
+
const parts = [];
|
|
116
|
+
if (dep.missing.length > 0) {
|
|
117
|
+
parts.push(`missing:${dep.missing.join(",")}`);
|
|
118
|
+
}
|
|
119
|
+
if (dep.incomplete.length > 0) {
|
|
120
|
+
parts.push(`wait:${dep.incomplete.join(",")}`);
|
|
121
|
+
}
|
|
122
|
+
return `deps=${parts.join(",")}`;
|
|
123
|
+
}
|
|
124
|
+
export function formatTaskLine(task, depState) {
|
|
125
|
+
const status = String(task.status || "TODO").toUpperCase();
|
|
126
|
+
const extras = [];
|
|
127
|
+
if (task.owner?.trim())
|
|
128
|
+
extras.push(`owner=${task.owner.trim()}`);
|
|
129
|
+
if (task.priority !== undefined && String(task.priority).trim()) {
|
|
130
|
+
extras.push(`prio=${String(task.priority).trim()}`);
|
|
131
|
+
}
|
|
132
|
+
const depsSummary = formatDepsSummary(depState);
|
|
133
|
+
if (depsSummary)
|
|
134
|
+
extras.push(depsSummary);
|
|
135
|
+
const tags = dedupeStrings(toStringArray(task.tags));
|
|
136
|
+
if (tags.length > 0)
|
|
137
|
+
extras.push(`tags=${tags.join(",")}`);
|
|
138
|
+
const verify = dedupeStrings(toStringArray(task.verify));
|
|
139
|
+
if (verify.length > 0)
|
|
140
|
+
extras.push(`verify=${verify.length}`);
|
|
141
|
+
const suffix = extras.length > 0 ? ` (${extras.join(", ")})` : "";
|
|
142
|
+
return `${task.id} [${status}] ${task.title?.trim() || "(untitled task)"}${suffix}`;
|
|
143
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentplaneConfig } from "@agentplaneorg/core";
|
|
2
|
+
import type { TaskData } from "../../../backends/task-backend.js";
|
|
3
|
+
export declare function nowIso(): string;
|
|
4
|
+
export declare const VERIFY_STEPS_PLACEHOLDER = "<!-- TODO: REPLACE WITH TASK-SPECIFIC ACCEPTANCE STEPS -->";
|
|
5
|
+
export declare const VERIFICATION_RESULTS_BEGIN = "<!-- BEGIN VERIFICATION RESULTS -->";
|
|
6
|
+
export declare const VERIFICATION_RESULTS_END = "<!-- END VERIFICATION RESULTS -->";
|
|
7
|
+
export type TaskDocVersion = 2 | 3;
|
|
8
|
+
export declare function extractDocSection(doc: string, sectionName: string): string | null;
|
|
9
|
+
export declare function isVerifyStepsFilled(sectionText: string | null): boolean;
|
|
10
|
+
export declare function normalizeTaskDocVersion(value: unknown, fallback?: TaskDocVersion): TaskDocVersion;
|
|
11
|
+
export declare function normalizeVerificationSectionLayout(sectionText: string | null, version: TaskDocVersion): string;
|
|
12
|
+
export declare function taskObservationSectionName(version: TaskDocVersion): "Notes" | "Findings";
|
|
13
|
+
export declare function extractTaskObservationSection(doc: string, version: TaskDocVersion): string | null;
|
|
14
|
+
export declare function isDocSectionFilled(sectionText: string | null): boolean;
|
|
15
|
+
export declare function ensureAgentFilledRequiredDocSections(opts: {
|
|
16
|
+
task: Pick<TaskData, "id">;
|
|
17
|
+
config: AgentplaneConfig;
|
|
18
|
+
doc: string;
|
|
19
|
+
action: string;
|
|
20
|
+
}): void;
|
|
21
|
+
//# sourceMappingURL=docs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAElE,wBAAgB,MAAM,IAAI,MAAM,CAE/B;AAED,eAAO,MAAM,wBAAwB,+DACyB,CAAC;AAC/D,eAAO,MAAM,0BAA0B,wCAAwC,CAAC;AAChF,eAAO,MAAM,wBAAwB,sCAAsC,CAAC;AAC5E,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,CAAC;AAEnC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBjF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKvE;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,cAAkB,GAC3B,cAAc,CAEhB;AAED,wBAAgB,kCAAkC,CAChD,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,OAAO,EAAE,cAAc,GACtB,MAAM,CAyCR;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,GAAG,UAAU,CAExF;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAIjG;AAMD,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAKtE;AAED,wBAAgB,oCAAoC,CAAC,IAAI,EAAE;IACzD,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CA6BP"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { CliError } from "../../../shared/errors.js";
|
|
2
|
+
import { dedupeStrings } from "../../../shared/strings.js";
|
|
3
|
+
export function nowIso() {
|
|
4
|
+
return new Date().toISOString();
|
|
5
|
+
}
|
|
6
|
+
export const VERIFY_STEPS_PLACEHOLDER = "<!-- TODO: REPLACE WITH TASK-SPECIFIC ACCEPTANCE STEPS -->";
|
|
7
|
+
export const VERIFICATION_RESULTS_BEGIN = "<!-- BEGIN VERIFICATION RESULTS -->";
|
|
8
|
+
export const VERIFICATION_RESULTS_END = "<!-- END VERIFICATION RESULTS -->";
|
|
9
|
+
export function extractDocSection(doc, sectionName) {
|
|
10
|
+
const lines = doc.replaceAll("\r\n", "\n").split("\n");
|
|
11
|
+
let capturing = false;
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const match = /^##\s+(.*)$/.exec(line.trim());
|
|
15
|
+
if (match) {
|
|
16
|
+
if (capturing)
|
|
17
|
+
break;
|
|
18
|
+
capturing = (match[1] ?? "").trim() === sectionName;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (capturing)
|
|
22
|
+
out.push(line);
|
|
23
|
+
}
|
|
24
|
+
if (!capturing)
|
|
25
|
+
return null;
|
|
26
|
+
return out.join("\n").trimEnd();
|
|
27
|
+
}
|
|
28
|
+
export function isVerifyStepsFilled(sectionText) {
|
|
29
|
+
const normalized = (sectionText ?? "").trim();
|
|
30
|
+
if (!normalized)
|
|
31
|
+
return false;
|
|
32
|
+
if (normalized.includes(VERIFY_STEPS_PLACEHOLDER))
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeTaskDocVersion(value, fallback = 2) {
|
|
37
|
+
return value === 3 ? 3 : value === 2 ? 2 : fallback;
|
|
38
|
+
}
|
|
39
|
+
export function normalizeVerificationSectionLayout(sectionText, version) {
|
|
40
|
+
const normalized = (sectionText ?? "").replaceAll("\r\n", "\n").trimEnd();
|
|
41
|
+
if (version === 3) {
|
|
42
|
+
const stripped = normalized
|
|
43
|
+
.split("\n")
|
|
44
|
+
.filter((line) => {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
return trimmed !== "### Plan" && trimmed !== "### Results";
|
|
47
|
+
})
|
|
48
|
+
.join("\n")
|
|
49
|
+
.replaceAll(/\n{3,}/g, "\n\n")
|
|
50
|
+
.trim();
|
|
51
|
+
if (!stripped)
|
|
52
|
+
return [VERIFICATION_RESULTS_BEGIN, VERIFICATION_RESULTS_END].join("\n");
|
|
53
|
+
const hasBegin = stripped.includes(VERIFICATION_RESULTS_BEGIN);
|
|
54
|
+
const hasEnd = stripped.includes(VERIFICATION_RESULTS_END);
|
|
55
|
+
if (hasBegin && hasEnd)
|
|
56
|
+
return stripped;
|
|
57
|
+
return [stripped, "", VERIFICATION_RESULTS_BEGIN, VERIFICATION_RESULTS_END].join("\n");
|
|
58
|
+
}
|
|
59
|
+
if (!normalized) {
|
|
60
|
+
return [
|
|
61
|
+
"### Plan",
|
|
62
|
+
"",
|
|
63
|
+
"",
|
|
64
|
+
"### Results",
|
|
65
|
+
"",
|
|
66
|
+
"",
|
|
67
|
+
VERIFICATION_RESULTS_BEGIN,
|
|
68
|
+
VERIFICATION_RESULTS_END,
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
const hasBegin = normalized.includes(VERIFICATION_RESULTS_BEGIN);
|
|
72
|
+
const hasEnd = normalized.includes(VERIFICATION_RESULTS_END);
|
|
73
|
+
if (hasBegin && hasEnd)
|
|
74
|
+
return normalized;
|
|
75
|
+
return [normalized, "", VERIFICATION_RESULTS_BEGIN, VERIFICATION_RESULTS_END].join("\n");
|
|
76
|
+
}
|
|
77
|
+
export function taskObservationSectionName(version) {
|
|
78
|
+
return version === 3 ? "Findings" : "Notes";
|
|
79
|
+
}
|
|
80
|
+
export function extractTaskObservationSection(doc, version) {
|
|
81
|
+
const primary = taskObservationSectionName(version);
|
|
82
|
+
const fallback = primary === "Findings" ? "Notes" : "Findings";
|
|
83
|
+
return extractDocSection(doc, primary) ?? extractDocSection(doc, fallback);
|
|
84
|
+
}
|
|
85
|
+
const DOC_PLACEHOLDER_RE = /<!--\s*TODO\b/i;
|
|
86
|
+
const DOC_SECTIONS_AUTO_MANAGED = new Set(["verification"]);
|
|
87
|
+
const DOC_SECTIONS_CONDITIONAL = new Set(["verify steps", "notes"]);
|
|
88
|
+
export function isDocSectionFilled(sectionText) {
|
|
89
|
+
const normalized = (sectionText ?? "").trim();
|
|
90
|
+
if (!normalized)
|
|
91
|
+
return false;
|
|
92
|
+
if (DOC_PLACEHOLDER_RE.test(normalized))
|
|
93
|
+
return false;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
export function ensureAgentFilledRequiredDocSections(opts) {
|
|
97
|
+
const required = dedupeStrings((opts.config.tasks.doc.required_sections ?? [])
|
|
98
|
+
.map((section) => String(section ?? "").trim())
|
|
99
|
+
.filter(Boolean));
|
|
100
|
+
const missing = [];
|
|
101
|
+
for (const section of required) {
|
|
102
|
+
const normalizedSection = section.trim().toLowerCase();
|
|
103
|
+
if (DOC_SECTIONS_AUTO_MANAGED.has(normalizedSection))
|
|
104
|
+
continue;
|
|
105
|
+
if (DOC_SECTIONS_CONDITIONAL.has(normalizedSection))
|
|
106
|
+
continue;
|
|
107
|
+
const sectionText = extractDocSection(opts.doc, section);
|
|
108
|
+
if (!isDocSectionFilled(sectionText)) {
|
|
109
|
+
missing.push(section);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (missing.length === 0)
|
|
113
|
+
return;
|
|
114
|
+
const sectionList = missing.map((section) => `## ${section}`).join(", ");
|
|
115
|
+
throw new CliError({
|
|
116
|
+
exitCode: 3,
|
|
117
|
+
code: "E_VALIDATION",
|
|
118
|
+
message: `${opts.task.id}: cannot ${opts.action}: required task doc sections are missing/empty: ${sectionList} ` +
|
|
119
|
+
`(fill via \`agentplane task doc set ${opts.task.id} --section <name> --text "..."\`)`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TaskData } from "../../../backends/task-backend.js";
|
|
2
|
+
export type TaskListFilters = {
|
|
3
|
+
status: string[];
|
|
4
|
+
owner: string[];
|
|
5
|
+
tag: string[];
|
|
6
|
+
limit?: number;
|
|
7
|
+
quiet: boolean;
|
|
8
|
+
strictRead?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function parseTaskListFilters(args: string[], opts?: {
|
|
11
|
+
allowLimit?: boolean;
|
|
12
|
+
}): TaskListFilters;
|
|
13
|
+
export declare function handleTaskListWarnings(opts: {
|
|
14
|
+
backend: {
|
|
15
|
+
getLastListWarnings?: () => string[];
|
|
16
|
+
};
|
|
17
|
+
strictRead?: boolean;
|
|
18
|
+
}): void;
|
|
19
|
+
export declare function taskTextBlob(task: TaskData): string;
|
|
20
|
+
//# sourceMappingURL=listing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listing.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/listing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAIlE,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC9B,eAAe,CAkFjB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE;QAAE,mBAAmB,CAAC,EAAE,MAAM,MAAM,EAAE,CAAA;KAAE,CAAC;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,IAAI,CAcP;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAiBnD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { invalidValueForFlag, missingValueMessage, warnMessage } from "../../../cli/output.js";
|
|
2
|
+
import { exitCodeForError } from "../../../cli/exit-codes.js";
|
|
3
|
+
import { CliError } from "../../../shared/errors.js";
|
|
4
|
+
import { toStringArray } from "./tags.js";
|
|
5
|
+
export function parseTaskListFilters(args, opts) {
|
|
6
|
+
const out = { status: [], owner: [], tag: [], quiet: false, strictRead: false };
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i];
|
|
9
|
+
if (!arg)
|
|
10
|
+
continue;
|
|
11
|
+
if (arg === "--quiet") {
|
|
12
|
+
out.quiet = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (arg === "--strict-read") {
|
|
16
|
+
out.strictRead = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (arg === "--status") {
|
|
20
|
+
const next = args[i + 1];
|
|
21
|
+
if (!next) {
|
|
22
|
+
throw new CliError({
|
|
23
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
24
|
+
code: "E_USAGE",
|
|
25
|
+
message: missingValueMessage("--status"),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
out.status.push(next);
|
|
29
|
+
i++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg === "--owner") {
|
|
33
|
+
const next = args[i + 1];
|
|
34
|
+
if (!next) {
|
|
35
|
+
throw new CliError({
|
|
36
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
37
|
+
code: "E_USAGE",
|
|
38
|
+
message: missingValueMessage("--owner"),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
out.owner.push(next);
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (arg === "--tag") {
|
|
46
|
+
const next = args[i + 1];
|
|
47
|
+
if (!next) {
|
|
48
|
+
throw new CliError({
|
|
49
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
50
|
+
code: "E_USAGE",
|
|
51
|
+
message: missingValueMessage("--tag"),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
out.tag.push(next);
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (opts?.allowLimit && arg === "--limit") {
|
|
59
|
+
const next = args[i + 1];
|
|
60
|
+
if (!next) {
|
|
61
|
+
throw new CliError({
|
|
62
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
63
|
+
code: "E_USAGE",
|
|
64
|
+
message: missingValueMessage("--limit"),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const parsed = Number.parseInt(next, 10);
|
|
68
|
+
if (!Number.isFinite(parsed)) {
|
|
69
|
+
throw new CliError({
|
|
70
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
71
|
+
code: "E_USAGE",
|
|
72
|
+
message: invalidValueForFlag("--limit", next, "integer"),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
out.limit = parsed;
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg.startsWith("--")) {
|
|
80
|
+
throw new CliError({
|
|
81
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
82
|
+
code: "E_USAGE",
|
|
83
|
+
message: `Unknown flag: ${arg}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
export function handleTaskListWarnings(opts) {
|
|
90
|
+
const warnings = opts.backend.getLastListWarnings?.() ?? [];
|
|
91
|
+
if (warnings.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
const preview = warnings.slice(0, 3).join("; ");
|
|
94
|
+
const suffix = warnings.length > 3 ? `; +${warnings.length - 3} more` : "";
|
|
95
|
+
const message = `skipped ${warnings.length} task files during scan (${preview}${suffix})`;
|
|
96
|
+
if (opts.strictRead) {
|
|
97
|
+
throw new CliError({
|
|
98
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
99
|
+
code: "E_VALIDATION",
|
|
100
|
+
message: `task scan strict mode failed: ${message}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
process.stderr.write(`${warnMessage(message)}\n`);
|
|
104
|
+
}
|
|
105
|
+
export function taskTextBlob(task) {
|
|
106
|
+
const parts = [];
|
|
107
|
+
for (const key of ["id", "title", "description", "status", "priority", "owner"]) {
|
|
108
|
+
const value = task[key];
|
|
109
|
+
if (typeof value === "string" && value.trim())
|
|
110
|
+
parts.push(value.trim());
|
|
111
|
+
}
|
|
112
|
+
const tags = toStringArray(task.tags);
|
|
113
|
+
parts.push(...tags.filter(Boolean));
|
|
114
|
+
const comments = Array.isArray(task.comments) ? task.comments : [];
|
|
115
|
+
for (const comment of comments) {
|
|
116
|
+
if (comment && typeof comment.author === "string")
|
|
117
|
+
parts.push(comment.author);
|
|
118
|
+
if (comment && typeof comment.body === "string")
|
|
119
|
+
parts.push(comment.body);
|
|
120
|
+
}
|
|
121
|
+
const commit = task.commit ?? null;
|
|
122
|
+
if (commit && typeof commit.hash === "string")
|
|
123
|
+
parts.push(commit.hash);
|
|
124
|
+
if (commit && typeof commit.message === "string")
|
|
125
|
+
parts.push(commit.message);
|
|
126
|
+
return parts.join("\n");
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentplaneConfig } from "@agentplaneorg/core";
|
|
2
|
+
import type { CommandContext } from "../../shared/task-backend.js";
|
|
3
|
+
export declare function normalizeDependsOnInput(value: string): string[];
|
|
4
|
+
export declare function normalizeTaskStatus(value: string): string;
|
|
5
|
+
export declare function toStringArray(value: unknown): string[];
|
|
6
|
+
export declare function requiresVerify(tags: string[], requiredTags: string[]): boolean;
|
|
7
|
+
export type PrimaryTagResolution = {
|
|
8
|
+
primary: string;
|
|
9
|
+
matched: string[];
|
|
10
|
+
usedFallback: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type TaskTagPolicy = {
|
|
13
|
+
primaryAllowlist: string[];
|
|
14
|
+
strictPrimary: boolean;
|
|
15
|
+
fallbackPrimary: string;
|
|
16
|
+
lockPrimaryOnUpdate: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function readTaskTagPolicy(input: CommandContext | AgentplaneConfig): TaskTagPolicy;
|
|
19
|
+
export declare function resolvePrimaryTagFromConfig(tags: string[], config: AgentplaneConfig): PrimaryTagResolution;
|
|
20
|
+
export declare function requiresVerifyStepsByPrimary(tags: string[], config: AgentplaneConfig): boolean;
|
|
21
|
+
export declare function requiresVerificationByPrimary(tags: string[], config: AgentplaneConfig): boolean;
|
|
22
|
+
export declare function resolvePrimaryTag(tags: string[], ctx: CommandContext): PrimaryTagResolution;
|
|
23
|
+
export declare function warnIfUnknownOwner(ctx: CommandContext, owner: string): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=tags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags.d.ts","sourceRoot":"","sources":["../../../../src/commands/task/shared/tags.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAQ5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAiBnE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAI/D;AAID,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAczD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAKtD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAI9E;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAAC;AAsCF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAkBzF;AAoCD,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,gBAAgB,GACvB,oBAAoB,CA+CtB;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAG9F;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAG/F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,cAAc,GAAG,oBAAoB,CAE3F;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe1F"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileExists } from "../../../cli/fs-utils.js";
|
|
4
|
+
import { exitCodeForError } from "../../../cli/exit-codes.js";
|
|
5
|
+
import { invalidValueMessage } from "../../../cli/output.js";
|
|
6
|
+
import { CliError } from "../../../shared/errors.js";
|
|
7
|
+
import { dedupeStrings } from "../../../shared/strings.js";
|
|
8
|
+
import { isRecord } from "../../../shared/guards.js";
|
|
9
|
+
async function listAgentIdsMemo(ctx) {
|
|
10
|
+
ctx.memo.agentIds ??= (async () => {
|
|
11
|
+
const agentsDir = path.join(ctx.resolvedProject.gitRoot, ctx.config.paths.agents_dir);
|
|
12
|
+
if (!(await fileExists(agentsDir)))
|
|
13
|
+
return [];
|
|
14
|
+
const entries = await readdir(agentsDir);
|
|
15
|
+
return entries
|
|
16
|
+
.filter((name) => name.endsWith(".json"))
|
|
17
|
+
.map((name) => name.slice(0, -".json".length))
|
|
18
|
+
.map((id) => id.trim())
|
|
19
|
+
.filter((id) => id.length > 0);
|
|
20
|
+
})();
|
|
21
|
+
return await ctx.memo.agentIds;
|
|
22
|
+
}
|
|
23
|
+
export function normalizeDependsOnInput(value) {
|
|
24
|
+
const trimmed = value.trim();
|
|
25
|
+
if (!trimmed || trimmed === "[]")
|
|
26
|
+
return [];
|
|
27
|
+
return [trimmed];
|
|
28
|
+
}
|
|
29
|
+
const ALLOWED_TASK_STATUSES = new Set(["TODO", "DOING", "DONE", "BLOCKED"]);
|
|
30
|
+
export function normalizeTaskStatus(value) {
|
|
31
|
+
const normalized = value.trim().toUpperCase();
|
|
32
|
+
if (!ALLOWED_TASK_STATUSES.has(normalized)) {
|
|
33
|
+
throw new CliError({
|
|
34
|
+
exitCode: 2,
|
|
35
|
+
code: "E_USAGE",
|
|
36
|
+
message: invalidValueMessage("status", value, `one of ${[...ALLOWED_TASK_STATUSES].join(", ")}`),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
export function toStringArray(value) {
|
|
42
|
+
if (!Array.isArray(value))
|
|
43
|
+
return [];
|
|
44
|
+
return value
|
|
45
|
+
.filter((item) => typeof item === "string")
|
|
46
|
+
.map((item) => item.trim());
|
|
47
|
+
}
|
|
48
|
+
export function requiresVerify(tags, requiredTags) {
|
|
49
|
+
const required = new Set(requiredTags.map((tag) => tag.trim().toLowerCase()).filter(Boolean));
|
|
50
|
+
if (required.size === 0)
|
|
51
|
+
return false;
|
|
52
|
+
return tags.some((tag) => required.has(tag.trim().toLowerCase()));
|
|
53
|
+
}
|
|
54
|
+
function readStringArray(value) {
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
return [];
|
|
57
|
+
return value
|
|
58
|
+
.filter((item) => typeof item === "string")
|
|
59
|
+
.map((item) => item.trim())
|
|
60
|
+
.filter((item) => item.length > 0);
|
|
61
|
+
}
|
|
62
|
+
function readBoolean(value, fallback) {
|
|
63
|
+
return typeof value === "boolean" ? value : fallback;
|
|
64
|
+
}
|
|
65
|
+
function readString(value, fallback) {
|
|
66
|
+
if (typeof value !== "string")
|
|
67
|
+
return fallback;
|
|
68
|
+
const trimmed = value.trim();
|
|
69
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
70
|
+
}
|
|
71
|
+
function configFromInput(input) {
|
|
72
|
+
return "config" in input ? input.config : input;
|
|
73
|
+
}
|
|
74
|
+
export function readTaskTagPolicy(input) {
|
|
75
|
+
const config = configFromInput(input);
|
|
76
|
+
const tasks = isRecord(config.tasks) ? config.tasks : {};
|
|
77
|
+
const rawTagsCandidate = tasks.tags;
|
|
78
|
+
const rawTags = isRecord(rawTagsCandidate) ? rawTagsCandidate : {};
|
|
79
|
+
const fallbackAllowlist = ["code", "data", "research", "docs", "ops", "product", "meta"];
|
|
80
|
+
const normalizedAllowlist = dedupeStrings(readStringArray(rawTags.primary_allowlist)
|
|
81
|
+
.map((tag) => tag.toLowerCase())
|
|
82
|
+
.filter(Boolean));
|
|
83
|
+
return {
|
|
84
|
+
primaryAllowlist: normalizedAllowlist.length > 0 ? normalizedAllowlist : fallbackAllowlist,
|
|
85
|
+
strictPrimary: readBoolean(rawTags.strict_primary, false),
|
|
86
|
+
fallbackPrimary: readString(rawTags.fallback_primary, "meta").toLowerCase(),
|
|
87
|
+
lockPrimaryOnUpdate: readBoolean(rawTags.lock_primary_on_update, true),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function readVerifyPrimaryPolicy(config) {
|
|
91
|
+
const tasks = isRecord(config.tasks) ? config.tasks : {};
|
|
92
|
+
const rawVerifyCandidate = tasks.verify;
|
|
93
|
+
const rawVerify = isRecord(rawVerifyCandidate)
|
|
94
|
+
? rawVerifyCandidate
|
|
95
|
+
: {};
|
|
96
|
+
const explicitSteps = dedupeStrings(readStringArray(rawVerify.require_steps_for_primary)
|
|
97
|
+
.map((tag) => tag.toLowerCase())
|
|
98
|
+
.filter(Boolean));
|
|
99
|
+
const legacySteps = dedupeStrings(readStringArray(rawVerify.require_steps_for_tags ?? rawVerify.required_tags)
|
|
100
|
+
.map((tag) => tag.toLowerCase())
|
|
101
|
+
.filter(Boolean));
|
|
102
|
+
const requireStepsForPrimary = new Set(explicitSteps.length > 0 ? explicitSteps : legacySteps);
|
|
103
|
+
const explicitVerification = dedupeStrings(readStringArray(rawVerify.require_verification_for_primary)
|
|
104
|
+
.map((tag) => tag.toLowerCase())
|
|
105
|
+
.filter(Boolean));
|
|
106
|
+
const requireVerificationForPrimary = new Set(explicitVerification.length > 0 ? explicitVerification : [...requireStepsForPrimary.values()]);
|
|
107
|
+
return { requireStepsForPrimary, requireVerificationForPrimary };
|
|
108
|
+
}
|
|
109
|
+
export function resolvePrimaryTagFromConfig(tags, config) {
|
|
110
|
+
const policy = readTaskTagPolicy(config);
|
|
111
|
+
const allowlist = policy.primaryAllowlist;
|
|
112
|
+
if (allowlist.length === 0) {
|
|
113
|
+
throw new CliError({
|
|
114
|
+
exitCode: 3,
|
|
115
|
+
code: "E_VALIDATION",
|
|
116
|
+
message: "tasks.tags.primary_allowlist must contain at least one tag.",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const normalizedTags = dedupeStrings(tags.map((t) => t.trim().toLowerCase()).filter(Boolean));
|
|
120
|
+
const matched = normalizedTags.filter((tag) => allowlist.includes(tag));
|
|
121
|
+
if (matched.length > 1) {
|
|
122
|
+
throw new CliError({
|
|
123
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
124
|
+
code: "E_USAGE",
|
|
125
|
+
message: `Task must include exactly one primary tag from allowlist (${allowlist.join(", ")}); ` +
|
|
126
|
+
`found multiple: ${matched.join(", ")}`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (matched.length === 1) {
|
|
130
|
+
return { primary: matched[0], matched, usedFallback: false };
|
|
131
|
+
}
|
|
132
|
+
if (policy.strictPrimary) {
|
|
133
|
+
throw new CliError({
|
|
134
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
135
|
+
code: "E_USAGE",
|
|
136
|
+
message: `Task must include exactly one primary tag from allowlist (${allowlist.join(", ")}); ` +
|
|
137
|
+
"none found and strict_primary=true",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const fallback = policy.fallbackPrimary;
|
|
141
|
+
if (!fallback || !allowlist.includes(fallback)) {
|
|
142
|
+
throw new CliError({
|
|
143
|
+
exitCode: 3,
|
|
144
|
+
code: "E_VALIDATION",
|
|
145
|
+
message: `tasks.tags.fallback_primary=${JSON.stringify(policy.fallbackPrimary)} ` +
|
|
146
|
+
`must be present in tasks.tags.primary_allowlist (${allowlist.join(", ")}).`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return { primary: fallback, matched, usedFallback: true };
|
|
150
|
+
}
|
|
151
|
+
export function requiresVerifyStepsByPrimary(tags, config) {
|
|
152
|
+
const primary = resolvePrimaryTagFromConfig(tags, config).primary;
|
|
153
|
+
return readVerifyPrimaryPolicy(config).requireStepsForPrimary.has(primary);
|
|
154
|
+
}
|
|
155
|
+
export function requiresVerificationByPrimary(tags, config) {
|
|
156
|
+
const primary = resolvePrimaryTagFromConfig(tags, config).primary;
|
|
157
|
+
return readVerifyPrimaryPolicy(config).requireVerificationForPrimary.has(primary);
|
|
158
|
+
}
|
|
159
|
+
export function resolvePrimaryTag(tags, ctx) {
|
|
160
|
+
return resolvePrimaryTagFromConfig(tags, ctx.config);
|
|
161
|
+
}
|
|
162
|
+
export async function warnIfUnknownOwner(ctx, owner) {
|
|
163
|
+
const trimmed = owner.trim();
|
|
164
|
+
if (!trimmed)
|
|
165
|
+
return;
|
|
166
|
+
const ids = await listAgentIdsMemo(ctx);
|
|
167
|
+
if (ids.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
if (ids.includes(trimmed))
|
|
170
|
+
return;
|
|
171
|
+
throw new CliError({
|
|
172
|
+
exitCode: 3,
|
|
173
|
+
code: "E_VALIDATION",
|
|
174
|
+
message: `unknown task owner id: ${trimmed} (not found under ${ctx.config.paths.agents_dir}; ` +
|
|
175
|
+
`pick an existing agent id or create ${ctx.config.paths.agents_dir}/${trimmed}.json)`,
|
|
176
|
+
});
|
|
177
|
+
}
|