agentplane 0.3.5 → 0.3.6
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/README.md +103 -75
- package/assets/AGENTS.md +4 -2
- package/bin/dist-guard.js +13 -3
- package/bin/runtime-watch.d.ts +1 -0
- package/bin/runtime-watch.js +22 -5
- package/bin/stale-dist-policy.js +9 -2
- package/dist/.build-manifest.json +196 -776
- package/dist/backends/task-backend.test-helpers.d.ts +4 -0
- package/dist/backends/task-backend.test-helpers.d.ts.map +1 -0
- package/dist/backends/task-backend.test-helpers.js +33 -0
- package/dist/cli/bootstrap-guide.d.ts.map +1 -1
- package/dist/cli/bootstrap-guide.js +1 -0
- package/dist/cli/command-guide.d.ts.map +1 -1
- package/dist/cli/command-guide.js +3 -2
- package/dist/cli/reason-codes.d.ts.map +1 -1
- package/dist/cli/reason-codes.js +30 -0
- package/dist/cli/run-cli/command-catalog/core.d.ts +3 -0
- package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -0
- package/dist/cli/run-cli/command-catalog/core.js +137 -0
- package/dist/cli/run-cli/command-catalog/lifecycle.d.ts +3 -0
- package/dist/cli/run-cli/command-catalog/lifecycle.d.ts.map +1 -0
- package/dist/cli/run-cli/command-catalog/lifecycle.js +52 -0
- package/dist/cli/run-cli/command-catalog/project.d.ts +3 -0
- package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -0
- package/dist/cli/run-cli/command-catalog/project.js +78 -0
- package/dist/cli/run-cli/command-catalog/shared.d.ts +19 -0
- package/dist/cli/run-cli/command-catalog/shared.d.ts.map +1 -0
- package/dist/cli/run-cli/command-catalog/shared.js +9 -0
- package/dist/cli/run-cli/command-catalog/task.d.ts +3 -0
- package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -0
- package/dist/cli/run-cli/command-catalog/task.js +85 -0
- package/dist/cli/run-cli/command-catalog.d.ts +3 -18
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog.js +8 -337
- package/dist/cli/run-cli/commands/ide.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/ide.js +64 -2
- package/dist/cli/run-cli/commands/init/ui.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/ui.js +33 -13
- package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts +3 -0
- package/dist/cli/run-cli.core.pr-flow.test-helpers.d.ts.map +1 -0
- package/dist/cli/run-cli.core.pr-flow.test-helpers.js +41 -0
- package/dist/cli/run-cli.core.tasks.test-helpers.d.ts +2 -0
- package/dist/cli/run-cli.core.tasks.test-helpers.d.ts.map +1 -0
- package/dist/cli/run-cli.core.tasks.test-helpers.js +6 -0
- package/dist/cli/run-cli.test-helpers.d.ts +3 -0
- package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
- package/dist/cli/run-cli.test-helpers.js +138 -6
- package/dist/commands/commit.spec.d.ts.map +1 -1
- package/dist/commands/commit.spec.js +2 -2
- package/dist/commands/doctor/runtime.d.ts.map +1 -1
- package/dist/commands/doctor/runtime.js +3 -6
- package/dist/commands/guard/commit.command.js +1 -1
- package/dist/commands/guard/impl/allow.d.ts +5 -0
- package/dist/commands/guard/impl/allow.d.ts.map +1 -1
- package/dist/commands/guard/impl/allow.js +15 -10
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +137 -18
- package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
- package/dist/commands/guard/impl/comment-commit.js +2 -0
- package/dist/commands/hooks/index.d.ts.map +1 -1
- package/dist/commands/hooks/index.js +8 -35
- package/dist/commands/recipes/impl/apply.d.ts +4 -0
- package/dist/commands/recipes/impl/apply.d.ts.map +1 -1
- package/dist/commands/recipes/impl/apply.js +34 -0
- package/dist/commands/recipes/impl/commands/explain.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/explain.js +70 -11
- package/dist/commands/recipes/impl/commands/info.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/info.js +24 -12
- package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/install.js +32 -36
- package/dist/commands/recipes/impl/commands/list.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/list.js +7 -4
- package/dist/commands/recipes/impl/commands/remove.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/remove.js +9 -11
- package/dist/commands/recipes/impl/constants.d.ts +2 -0
- package/dist/commands/recipes/impl/constants.d.ts.map +1 -1
- package/dist/commands/recipes/impl/constants.js +2 -0
- package/dist/commands/recipes/impl/manifest.d.ts.map +1 -1
- package/dist/commands/recipes/impl/manifest.js +219 -23
- package/dist/commands/recipes/impl/normalize.d.ts +3 -0
- package/dist/commands/recipes/impl/normalize.d.ts.map +1 -1
- package/dist/commands/recipes/impl/normalize.js +28 -24
- package/dist/commands/recipes/impl/paths.d.ts +9 -0
- package/dist/commands/recipes/impl/paths.d.ts.map +1 -1
- package/dist/commands/recipes/impl/paths.js +10 -1
- package/dist/commands/recipes/impl/project-installed-recipes.d.ts +7 -0
- package/dist/commands/recipes/impl/project-installed-recipes.d.ts.map +1 -0
- package/dist/commands/recipes/impl/project-installed-recipes.js +102 -0
- package/dist/commands/recipes/impl/resolver.d.ts +20 -0
- package/dist/commands/recipes/impl/resolver.d.ts.map +1 -0
- package/dist/commands/recipes/impl/resolver.js +220 -0
- package/dist/commands/recipes/impl/scenario.d.ts.map +1 -1
- package/dist/commands/recipes/impl/scenario.js +40 -11
- package/dist/commands/recipes/impl/types.d.ts +145 -16
- package/dist/commands/recipes/impl/types.d.ts.map +1 -1
- package/dist/commands/recipes/install.spec.d.ts.map +1 -1
- package/dist/commands/recipes/install.spec.js +3 -2
- package/dist/commands/recipes.d.ts +6 -4
- package/dist/commands/recipes.d.ts.map +1 -1
- package/dist/commands/recipes.js +5 -3
- package/dist/commands/recipes.test-helpers.d.ts +185 -0
- package/dist/commands/recipes.test-helpers.d.ts.map +1 -0
- package/dist/commands/recipes.test-helpers.js +339 -0
- package/dist/commands/scenario/impl/commands.d.ts.map +1 -1
- package/dist/commands/scenario/impl/commands.js +192 -336
- package/dist/commands/scenario/info.command.d.ts.map +1 -1
- package/dist/commands/scenario/info.command.js +7 -2
- package/dist/commands/scenario/list.command.js +2 -2
- package/dist/commands/scenario/run.command.d.ts.map +1 -1
- package/dist/commands/scenario/run.command.js +7 -2
- package/dist/commands/shared/reconcile-check.d.ts.map +1 -1
- package/dist/commands/shared/reconcile-check.js +77 -2
- package/dist/commands/shared/task-store.d.ts +32 -1
- package/dist/commands/shared/task-store.d.ts.map +1 -1
- package/dist/commands/shared/task-store.js +166 -42
- package/dist/commands/task/block.d.ts.map +1 -1
- package/dist/commands/task/block.js +46 -29
- package/dist/commands/task/close-duplicate.d.ts.map +1 -1
- package/dist/commands/task/close-duplicate.js +12 -37
- package/dist/commands/task/close-noop.d.ts.map +1 -1
- package/dist/commands/task/close-noop.js +12 -30
- package/dist/commands/task/close-shared.d.ts +14 -0
- package/dist/commands/task/close-shared.d.ts.map +1 -0
- package/dist/commands/task/close-shared.js +76 -0
- package/dist/commands/task/comment.d.ts.map +1 -1
- package/dist/commands/task/comment.js +35 -17
- package/dist/commands/task/doc-set.command.d.ts +2 -1
- package/dist/commands/task/doc-set.command.d.ts.map +1 -1
- package/dist/commands/task/doc-set.command.js +36 -4
- package/dist/commands/task/doc-template.d.ts.map +1 -1
- package/dist/commands/task/doc-template.js +2 -7
- package/dist/commands/task/doc.command.js +1 -1
- package/dist/commands/task/doc.d.ts +2 -1
- package/dist/commands/task/doc.d.ts.map +1 -1
- package/dist/commands/task/doc.js +123 -71
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +138 -76
- package/dist/commands/task/migrate-doc.d.ts.map +1 -1
- package/dist/commands/task/migrate-doc.js +2 -8
- package/dist/commands/task/plan-set.command.js +1 -1
- package/dist/commands/task/plan.command.d.ts +8 -0
- package/dist/commands/task/plan.command.d.ts.map +1 -0
- package/dist/commands/task/plan.command.js +37 -0
- package/dist/commands/task/plan.d.ts.map +1 -1
- package/dist/commands/task/plan.js +190 -93
- package/dist/commands/task/set-status.command.d.ts.map +1 -1
- package/dist/commands/task/set-status.command.js +1 -1
- package/dist/commands/task/set-status.d.ts.map +1 -1
- package/dist/commands/task/set-status.js +40 -3
- package/dist/commands/task/shared/docs.d.ts +1 -0
- package/dist/commands/task/shared/docs.d.ts.map +1 -1
- package/dist/commands/task/shared/docs.js +7 -0
- package/dist/commands/task/shared/transitions.d.ts +0 -2
- package/dist/commands/task/shared/transitions.d.ts.map +1 -1
- package/dist/commands/task/shared/transitions.js +0 -6
- package/dist/commands/task/shared.d.ts +2 -2
- package/dist/commands/task/shared.d.ts.map +1 -1
- package/dist/commands/task/shared.js +2 -2
- package/dist/commands/task/start.d.ts.map +1 -1
- package/dist/commands/task/start.js +88 -63
- package/dist/commands/task/task.command.d.ts +8 -0
- package/dist/commands/task/task.command.d.ts.map +1 -0
- package/dist/commands/task/task.command.js +71 -0
- package/dist/commands/task/verify-command-shared.d.ts +16 -0
- package/dist/commands/task/verify-command-shared.d.ts.map +1 -0
- package/dist/commands/task/verify-command-shared.js +53 -0
- package/dist/commands/task/verify-ok.command.d.ts +2 -6
- package/dist/commands/task/verify-ok.command.d.ts.map +1 -1
- package/dist/commands/task/verify-ok.command.js +8 -50
- package/dist/commands/task/verify-record.d.ts.map +1 -1
- package/dist/commands/task/verify-record.js +119 -140
- package/dist/commands/task/verify-rework.command.d.ts +2 -6
- package/dist/commands/task/verify-rework.command.d.ts.map +1 -1
- package/dist/commands/task/verify-rework.command.js +8 -50
- package/dist/commands/verify.spec.d.ts.map +1 -1
- package/dist/commands/verify.spec.js +3 -12
- package/dist/policy/rules/allowlist.d.ts.map +1 -1
- package/dist/policy/rules/allowlist.js +13 -4
- package/dist/policy/rules/protected-paths.d.ts.map +1 -1
- package/dist/policy/rules/protected-paths.js +6 -1
- package/dist/shared/agent-emoji.d.ts.map +1 -1
- package/dist/shared/protected-paths.d.ts +7 -0
- package/dist/shared/protected-paths.d.ts.map +1 -1
- package/dist/shared/protected-paths.js +26 -10
- package/dist/shared/repo-cli-version.d.ts.map +1 -1
- package/dist/shared/repo-cli-version.js +9 -3
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { exitCodeForError } from "../../cli/exit-codes.js";
|
|
2
|
+
import { withDiagnosticContext } from "../../shared/diagnostics.js";
|
|
2
3
|
import { CliError } from "../../shared/errors.js";
|
|
3
4
|
import { listTasksMemo } from "./task-backend.js";
|
|
4
5
|
function compactError(err) {
|
|
@@ -8,11 +9,85 @@ function compactError(err) {
|
|
|
8
9
|
}
|
|
9
10
|
return String(err);
|
|
10
11
|
}
|
|
12
|
+
function parseTaskScanWarning(raw) {
|
|
13
|
+
const trimmed = raw.trim();
|
|
14
|
+
const match = /^skip:([^:]+):\s*(.+)$/.exec(trimmed);
|
|
15
|
+
if (!match) {
|
|
16
|
+
return { raw: trimmed, taskId: null, kind: null };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
raw: trimmed,
|
|
20
|
+
taskId: match[1]?.trim() || null,
|
|
21
|
+
kind: match[2]?.trim() || null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function describePrimaryWarning(warning) {
|
|
25
|
+
const subject = warning.taskId ? `task README for ${warning.taskId}` : "a task README";
|
|
26
|
+
switch (warning.kind) {
|
|
27
|
+
case "invalid_readme_frontmatter": {
|
|
28
|
+
return {
|
|
29
|
+
summary: `${subject} has invalid frontmatter and could not be parsed`,
|
|
30
|
+
likelyCause: `${subject} has invalid frontmatter, so reconcile skipped it before the mutating command could run`,
|
|
31
|
+
hint: "Fix the malformed task README frontmatter, then rerun the mutating command.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
case "empty_or_invalid_frontmatter": {
|
|
35
|
+
return {
|
|
36
|
+
summary: `${subject} has empty or invalid frontmatter and could not be parsed`,
|
|
37
|
+
likelyCause: `${subject} is missing required frontmatter fields, so reconcile skipped it before the mutating command could run`,
|
|
38
|
+
hint: "Restore valid task README frontmatter, then rerun the mutating command.",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
case "unreadable_readme":
|
|
42
|
+
case "missing_or_unreadable_readme": {
|
|
43
|
+
return {
|
|
44
|
+
summary: `${subject} could not be read during task scan`,
|
|
45
|
+
likelyCause: `${subject} is missing or unreadable, so reconcile skipped it before the mutating command could run`,
|
|
46
|
+
hint: "Restore or fix the task README file, then rerun the mutating command.",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
default: {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
11
54
|
function summarizeWarnings(warnings) {
|
|
55
|
+
const primary = describePrimaryWarning(parseTaskScanWarning(warnings[0] ?? ""));
|
|
56
|
+
if (primary) {
|
|
57
|
+
const extra = warnings.length - 1;
|
|
58
|
+
if (extra <= 0)
|
|
59
|
+
return primary.summary;
|
|
60
|
+
return `${primary.summary}; +${extra} more task scan warning${extra === 1 ? "" : "s"}`;
|
|
61
|
+
}
|
|
12
62
|
const preview = warnings.slice(0, 3).join("; ");
|
|
13
63
|
const suffix = warnings.length > 3 ? `; +${warnings.length - 3} more` : "";
|
|
14
64
|
return `skipped ${warnings.length} task files during scan (${preview}${suffix})`;
|
|
15
65
|
}
|
|
66
|
+
function buildWarningDiagnostic(warnings) {
|
|
67
|
+
const primary = describePrimaryWarning(parseTaskScanWarning(warnings[0] ?? ""));
|
|
68
|
+
if (primary) {
|
|
69
|
+
return {
|
|
70
|
+
state: "mutation preflight cannot reconcile task artifacts",
|
|
71
|
+
likelyCause: primary.likelyCause,
|
|
72
|
+
hint: primary.hint,
|
|
73
|
+
nextAction: {
|
|
74
|
+
command: "agentplane task list --strict-read",
|
|
75
|
+
reason: "surface the malformed or unreadable task README before retrying mutating commands",
|
|
76
|
+
reasonCode: "reconcile_task_scan_incomplete",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
state: "mutation preflight found skipped task artifacts",
|
|
82
|
+
likelyCause: "task scan skipped one or more task artifacts due to parse/read warnings, so the mutating command stopped before touching git state",
|
|
83
|
+
hint: "Reconcile check failed due to task scan drift or parse/read errors.",
|
|
84
|
+
nextAction: {
|
|
85
|
+
command: "agentplane task list --strict-read",
|
|
86
|
+
reason: "surface task scan/read failures before retrying mutating commands",
|
|
87
|
+
reasonCode: "reconcile_task_scan_incomplete",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
16
91
|
export async function ensureReconciledBeforeMutation(opts) {
|
|
17
92
|
try {
|
|
18
93
|
await opts.ctx.git.statusChangedPaths();
|
|
@@ -51,10 +126,10 @@ export async function ensureReconciledBeforeMutation(opts) {
|
|
|
51
126
|
exitCode: exitCodeForError("E_VALIDATION"),
|
|
52
127
|
code: "E_VALIDATION",
|
|
53
128
|
message: `reconcile check failed: ${summarizeWarnings(warnings)}`,
|
|
54
|
-
context: {
|
|
129
|
+
context: withDiagnosticContext({
|
|
55
130
|
command: opts.command,
|
|
56
131
|
reason_code: "reconcile_task_scan_incomplete",
|
|
57
132
|
warning_count: warnings.length,
|
|
58
|
-
},
|
|
133
|
+
}, buildWarningDiagnostic(warnings)),
|
|
59
134
|
});
|
|
60
135
|
}
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { type TaskData } from "../../backends/task-backend.js";
|
|
1
|
+
import { type TaskData, type TaskEvent } from "../../backends/task-backend.js";
|
|
2
2
|
import { type CommandContext } from "./task-backend.js";
|
|
3
|
+
type TaskComment = NonNullable<TaskData["comments"]>[number];
|
|
4
|
+
export type TaskStoreTaskPatch = Partial<Omit<TaskData, "doc" | "comments" | "events" | "doc_version" | "doc_updated_at" | "doc_updated_by">>;
|
|
5
|
+
export type TaskStoreDocPatch = {
|
|
6
|
+
kind: "replace-doc";
|
|
7
|
+
doc: string;
|
|
8
|
+
expectedCurrentDoc?: string | null;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "set-section";
|
|
11
|
+
section: string;
|
|
12
|
+
text: string;
|
|
13
|
+
requiredSections: string[];
|
|
14
|
+
expectedCurrentText?: string | null;
|
|
15
|
+
};
|
|
16
|
+
export type TaskStorePatch = {
|
|
17
|
+
task?: TaskStoreTaskPatch;
|
|
18
|
+
appendComments?: TaskComment[];
|
|
19
|
+
appendEvents?: TaskEvent[];
|
|
20
|
+
doc?: TaskStoreDocPatch;
|
|
21
|
+
docMeta?: {
|
|
22
|
+
touch?: boolean;
|
|
23
|
+
updatedBy?: string;
|
|
24
|
+
version?: 2 | 3;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
3
27
|
export declare class TaskStore {
|
|
4
28
|
private ctx;
|
|
5
29
|
private cache;
|
|
@@ -10,7 +34,14 @@ export declare class TaskStore {
|
|
|
10
34
|
changed: boolean;
|
|
11
35
|
task: TaskData;
|
|
12
36
|
}>;
|
|
37
|
+
patch(taskId: string, builder: (current: TaskData) => Promise<TaskStorePatch | null | undefined> | TaskStorePatch | null | undefined): Promise<{
|
|
38
|
+
changed: boolean;
|
|
39
|
+
task: TaskData;
|
|
40
|
+
}>;
|
|
41
|
+
private runWithRetry;
|
|
42
|
+
private writeNextTask;
|
|
13
43
|
}
|
|
14
44
|
export declare function getTaskStore(ctx: CommandContext): TaskStore;
|
|
15
45
|
export declare function backendIsLocalFileBackend(ctx: CommandContext): boolean;
|
|
46
|
+
export {};
|
|
16
47
|
//# sourceMappingURL=task-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/task-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/task-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,SAAS,EAEf,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AASpG,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,IAAI,CACF,QAAQ,EACR,KAAK,GAAG,UAAU,GAAG,QAAQ,GAAG,aAAa,GAAG,gBAAgB,GAAG,gBAAgB,CACpF,CACF,CAAC;AAEF,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,CAAC;AAEN,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,cAAc,CAAC,EAAE,WAAW,EAAE,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,EAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,iBAAiB,CAAC;IACxB,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;KACjB,CAAC;CACH,CAAC;AA2KF,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAAiB;IAC5B,OAAO,CAAC,KAAK,CAA0C;gBAE3C,GAAG,EAAE,cAAc;IAIzB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YAK9B,SAAS;IAsBjB,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,GAC3D,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;IAM1C,KAAK,CACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CACP,OAAO,EAAE,QAAQ,KACd,OAAO,CAAC,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,cAAc,GAAG,IAAI,GAAG,SAAS,GAClF,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;YAqClC,YAAY;YAyBZ,aAAa;CAyD5B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,SAAS,CAI3D;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAEtE"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { docChanged, extractTaskDoc, mergeTaskDoc, parseTaskReadme, renderTaskReadme, } from "@agentplaneorg/core";
|
|
4
|
-
import { LocalBackend, taskRecordToData } from "../../backends/task-backend.js";
|
|
3
|
+
import { ensureDocSections, docChanged, extractTaskDoc, mergeTaskDoc, parseTaskReadme, renderTaskReadme, setMarkdownSection, } from "@agentplaneorg/core";
|
|
4
|
+
import { LocalBackend, taskRecordToData, } from "../../backends/task-backend.js";
|
|
5
5
|
import { exitCodeForError } from "../../cli/exit-codes.js";
|
|
6
6
|
import { CliError } from "../../shared/errors.js";
|
|
7
7
|
import { writeTextIfChanged } from "../../shared/write-if-changed.js";
|
|
@@ -12,6 +12,94 @@ function taskReadmePath(ctx, taskId) {
|
|
|
12
12
|
function normalizeTaskDocVersion(value, fallback = 2) {
|
|
13
13
|
return value === 3 ? 3 : value === 2 ? 2 : fallback;
|
|
14
14
|
}
|
|
15
|
+
function normalizeDocComparison(text) {
|
|
16
|
+
return String(text ?? "")
|
|
17
|
+
.replaceAll("\r\n", "\n")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
function extractDocSectionText(doc, sectionName) {
|
|
21
|
+
const lines = doc.replaceAll("\r\n", "\n").split("\n");
|
|
22
|
+
let capturing = false;
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
const match = /^##\s+(.*)$/.exec(line.trim());
|
|
26
|
+
if (match) {
|
|
27
|
+
if (capturing)
|
|
28
|
+
break;
|
|
29
|
+
capturing = (match[1] ?? "").trim() === sectionName;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (capturing)
|
|
33
|
+
out.push(line);
|
|
34
|
+
}
|
|
35
|
+
if (!capturing)
|
|
36
|
+
return null;
|
|
37
|
+
return out.join("\n").trimEnd();
|
|
38
|
+
}
|
|
39
|
+
function normalizeComments(task) {
|
|
40
|
+
return Array.isArray(task.comments)
|
|
41
|
+
? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
|
|
42
|
+
: [];
|
|
43
|
+
}
|
|
44
|
+
function normalizeEvents(task) {
|
|
45
|
+
return Array.isArray(task.events)
|
|
46
|
+
? task.events.filter((item) => !!item &&
|
|
47
|
+
typeof item.type === "string" &&
|
|
48
|
+
typeof item.at === "string" &&
|
|
49
|
+
typeof item.author === "string")
|
|
50
|
+
: [];
|
|
51
|
+
}
|
|
52
|
+
function isConcurrentReadmeChangeError(err) {
|
|
53
|
+
return (err instanceof CliError &&
|
|
54
|
+
err.code === "E_IO" &&
|
|
55
|
+
err.message.startsWith("Task README changed concurrently:"));
|
|
56
|
+
}
|
|
57
|
+
function throwTaskSectionConflict(opts) {
|
|
58
|
+
throw new CliError({
|
|
59
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
60
|
+
code: "E_VALIDATION",
|
|
61
|
+
message: `Task README section changed concurrently: ${opts.taskId} ## ${opts.section} ` +
|
|
62
|
+
"(re-read the task and re-apply your change)",
|
|
63
|
+
context: {
|
|
64
|
+
task_id: opts.taskId,
|
|
65
|
+
section: opts.section,
|
|
66
|
+
reason_code: "task_readme_section_conflict",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function throwTaskDocConflict(opts) {
|
|
71
|
+
throw new CliError({
|
|
72
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
73
|
+
code: "E_VALIDATION",
|
|
74
|
+
message: `Task README changed concurrently: ${opts.taskId} ` +
|
|
75
|
+
"(re-read the task and re-apply your change)",
|
|
76
|
+
context: {
|
|
77
|
+
task_id: opts.taskId,
|
|
78
|
+
reason_code: "task_readme_conflict",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function applyTaskDocPatch(opts) {
|
|
83
|
+
if (opts.patch.kind === "replace-doc") {
|
|
84
|
+
if (opts.patch.expectedCurrentDoc !== undefined) {
|
|
85
|
+
const currentDoc = normalizeDocComparison(opts.currentDocRaw);
|
|
86
|
+
const expectedDoc = normalizeDocComparison(opts.patch.expectedCurrentDoc);
|
|
87
|
+
if (currentDoc !== expectedDoc) {
|
|
88
|
+
throwTaskDocConflict({ taskId: opts.taskId });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return opts.patch.doc;
|
|
92
|
+
}
|
|
93
|
+
const baseDoc = ensureDocSections(opts.currentDocRaw, opts.patch.requiredSections);
|
|
94
|
+
if (opts.patch.expectedCurrentText !== undefined) {
|
|
95
|
+
const currentSection = normalizeDocComparison(extractDocSectionText(baseDoc, opts.patch.section));
|
|
96
|
+
const expectedSection = normalizeDocComparison(opts.patch.expectedCurrentText);
|
|
97
|
+
if (currentSection !== expectedSection) {
|
|
98
|
+
throwTaskSectionConflict({ taskId: opts.taskId, section: opts.patch.section });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return ensureDocSections(setMarkdownSection(baseDoc, opts.patch.section, opts.patch.text), opts.patch.requiredSections);
|
|
102
|
+
}
|
|
15
103
|
async function readTaskReadmeCached(opts) {
|
|
16
104
|
const readmePath = taskReadmePath(opts.ctx, opts.taskId);
|
|
17
105
|
let text;
|
|
@@ -82,64 +170,100 @@ export class TaskStore {
|
|
|
82
170
|
return await load;
|
|
83
171
|
}
|
|
84
172
|
async update(taskId, updater) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
173
|
+
return await this.runWithRetry(taskId, async (entry) => {
|
|
174
|
+
return await updater({ ...entry.task });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async patch(taskId, builder) {
|
|
178
|
+
return await this.runWithRetry(taskId, async (entry) => {
|
|
179
|
+
const patch = await builder({ ...entry.task });
|
|
180
|
+
if (!patch)
|
|
181
|
+
return { ...entry.task };
|
|
88
182
|
const current = entry.task;
|
|
89
|
-
const next =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let body = entry.parsed.body ?? "";
|
|
93
|
-
const existingDoc = extractTaskDoc(body);
|
|
94
|
-
const now = new Date().toISOString();
|
|
95
|
-
const currentDocVersion = normalizeTaskDocVersion(entry.parsed.frontmatter.doc_version);
|
|
96
|
-
const requestedDocVersion = normalizeTaskDocVersion(next.doc_version, currentDocVersion);
|
|
97
|
-
if (next.doc !== undefined) {
|
|
98
|
-
const nextDoc = String(next.doc ?? "");
|
|
99
|
-
body = mergeTaskDoc(body, nextDoc);
|
|
100
|
-
if (docChanged(existingDoc, nextDoc) || !frontmatter.doc_updated_at) {
|
|
101
|
-
frontmatter.doc_version = requestedDocVersion;
|
|
102
|
-
frontmatter.doc_updated_at = now;
|
|
103
|
-
frontmatter.doc_updated_by = resolveDocUpdatedBy(next);
|
|
104
|
-
}
|
|
183
|
+
const next = patch.task ? { ...current, ...patch.task } : { ...current };
|
|
184
|
+
if (patch.appendComments && patch.appendComments.length > 0) {
|
|
185
|
+
next.comments = [...normalizeComments(current), ...patch.appendComments];
|
|
105
186
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
frontmatter.doc_updated_at.trim() === "") {
|
|
109
|
-
frontmatter.doc_updated_at = now;
|
|
187
|
+
if (patch.appendEvents && patch.appendEvents.length > 0) {
|
|
188
|
+
next.events = [...normalizeEvents(current), ...patch.appendEvents];
|
|
110
189
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
190
|
+
if (patch.doc) {
|
|
191
|
+
next.doc = applyTaskDocPatch({
|
|
192
|
+
taskId: current.id,
|
|
193
|
+
currentDocRaw: String(current.doc ?? ""),
|
|
194
|
+
patch: patch.doc,
|
|
195
|
+
});
|
|
114
196
|
}
|
|
115
|
-
const
|
|
197
|
+
const touchDoc = patch.doc !== undefined || patch.docMeta?.touch === true;
|
|
198
|
+
if (touchDoc) {
|
|
199
|
+
const currentDocVersion = normalizeTaskDocVersion(entry.parsed.frontmatter.doc_version);
|
|
200
|
+
next.doc_version = normalizeTaskDocVersion(patch.docMeta?.version ?? current.doc_version, currentDocVersion);
|
|
201
|
+
next.doc_updated_at = new Date().toISOString();
|
|
202
|
+
next.doc_updated_by = patch.docMeta?.updatedBy ?? resolveDocUpdatedBy(next);
|
|
203
|
+
}
|
|
204
|
+
return next;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async runWithRetry(taskId, computeNext) {
|
|
208
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
209
|
+
const entry = await this.getCached(taskId);
|
|
210
|
+
const next = await computeNext(entry);
|
|
116
211
|
try {
|
|
117
|
-
await
|
|
118
|
-
readmePath: entry.readmePath,
|
|
119
|
-
expectedMtimeMs: entry.mtimeMs,
|
|
120
|
-
});
|
|
212
|
+
return await this.writeNextTask(taskId, entry, next);
|
|
121
213
|
}
|
|
122
214
|
catch (err) {
|
|
123
|
-
if (attempt === 0 && err
|
|
215
|
+
if (attempt === 0 && isConcurrentReadmeChangeError(err)) {
|
|
124
216
|
// Refresh cache and retry once.
|
|
125
217
|
this.cache.delete(taskId);
|
|
126
218
|
continue;
|
|
127
219
|
}
|
|
128
220
|
throw err;
|
|
129
221
|
}
|
|
130
|
-
const nextText = rendered.endsWith("\n") ? rendered : `${rendered}\n`;
|
|
131
|
-
const changed = await writeTextIfChanged(entry.readmePath, nextText);
|
|
132
|
-
// Refresh cache with latest content on disk.
|
|
133
|
-
this.cache.set(taskId, (async () => {
|
|
134
|
-
return await readTaskReadmeCached({ ctx: this.ctx, taskId });
|
|
135
|
-
})());
|
|
136
|
-
const updated = await this.get(taskId);
|
|
137
|
-
return { changed, task: updated };
|
|
138
222
|
}
|
|
139
223
|
// Unreachable, but keeps TS happy.
|
|
140
224
|
const task = await this.get(taskId);
|
|
141
225
|
return { changed: false, task };
|
|
142
226
|
}
|
|
227
|
+
async writeNextTask(taskId, entry, next) {
|
|
228
|
+
// Start from existing frontmatter to preserve any unknown keys.
|
|
229
|
+
const frontmatter = { ...entry.parsed.frontmatter, ...taskDataToFrontmatter(next) };
|
|
230
|
+
let body = entry.parsed.body ?? "";
|
|
231
|
+
const existingDoc = extractTaskDoc(body);
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
const currentDocVersion = normalizeTaskDocVersion(entry.parsed.frontmatter.doc_version);
|
|
234
|
+
const requestedDocVersion = normalizeTaskDocVersion(next.doc_version, currentDocVersion);
|
|
235
|
+
if (next.doc !== undefined) {
|
|
236
|
+
const nextDoc = String(next.doc ?? "");
|
|
237
|
+
body = mergeTaskDoc(body, nextDoc);
|
|
238
|
+
if (docChanged(existingDoc, nextDoc) || !frontmatter.doc_updated_at) {
|
|
239
|
+
frontmatter.doc_version = requestedDocVersion;
|
|
240
|
+
frontmatter.doc_updated_at = now;
|
|
241
|
+
frontmatter.doc_updated_by = resolveDocUpdatedBy(next);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
frontmatter.doc_version = normalizeTaskDocVersion(frontmatter.doc_version, requestedDocVersion);
|
|
245
|
+
if (typeof frontmatter.doc_updated_at !== "string" ||
|
|
246
|
+
frontmatter.doc_updated_at.trim() === "") {
|
|
247
|
+
frontmatter.doc_updated_at = now;
|
|
248
|
+
}
|
|
249
|
+
if (typeof frontmatter.doc_updated_by !== "string" ||
|
|
250
|
+
frontmatter.doc_updated_by.trim() === "") {
|
|
251
|
+
frontmatter.doc_updated_by = resolveDocUpdatedBy(next);
|
|
252
|
+
}
|
|
253
|
+
const rendered = renderTaskReadme(frontmatter, body);
|
|
254
|
+
await ensureUnchangedOnDisk({
|
|
255
|
+
readmePath: entry.readmePath,
|
|
256
|
+
expectedMtimeMs: entry.mtimeMs,
|
|
257
|
+
});
|
|
258
|
+
const nextText = rendered.endsWith("\n") ? rendered : `${rendered}\n`;
|
|
259
|
+
const changed = await writeTextIfChanged(entry.readmePath, nextText);
|
|
260
|
+
// Refresh cache with latest content on disk.
|
|
261
|
+
this.cache.set(taskId, (async () => {
|
|
262
|
+
return await readTaskReadmeCached({ ctx: this.ctx, taskId });
|
|
263
|
+
})());
|
|
264
|
+
const updated = await this.get(taskId);
|
|
265
|
+
return { changed, task: updated };
|
|
266
|
+
}
|
|
143
267
|
}
|
|
144
268
|
export function getTaskStore(ctx) {
|
|
145
269
|
const memo = ctx.memo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../../src/commands/task/block.ts"],"names":[],"mappings":"AAOA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAiBnC,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACnC,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2IlB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mapBackendError } from "../../cli/error-map.js";
|
|
2
|
-
import {
|
|
2
|
+
import { successMessage } from "../../cli/output.js";
|
|
3
3
|
import { formatCommentBodyForCommit } from "../../shared/comment-format.js";
|
|
4
4
|
import { CliError } from "../../shared/errors.js";
|
|
5
5
|
import { commitFromComment } from "../guard/index.js";
|
|
@@ -7,7 +7,7 @@ import { ensureActionApproved } from "../shared/approval-requirements.js";
|
|
|
7
7
|
import { loadCommandContext, loadTaskFromContext, } from "../shared/task-backend.js";
|
|
8
8
|
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
9
9
|
import { readDirectWorkLock } from "../../shared/direct-work-lock.js";
|
|
10
|
-
import { appendTaskEvent,
|
|
10
|
+
import { appendTaskEvent, defaultCommitEmojiForStatus, ensureCommentCommitAllowed, ensureStatusTransitionAllowed, normalizeTaskDocVersion, nowIso, requireStructuredComment, resolvePrimaryTag, toStringArray, } from "./shared.js";
|
|
11
11
|
export async function cmdBlock(opts) {
|
|
12
12
|
try {
|
|
13
13
|
const ctx = opts.ctx ??
|
|
@@ -51,25 +51,50 @@ export async function cmdBlock(opts) {
|
|
|
51
51
|
: [];
|
|
52
52
|
const commentsValue = [...existingComments, { author: opts.author, body: commentBody }];
|
|
53
53
|
const at = nowIso();
|
|
54
|
-
const nextTask = {
|
|
55
|
-
...task,
|
|
56
|
-
status: "BLOCKED",
|
|
57
|
-
comments: commentsValue,
|
|
58
|
-
events: appendTaskEvent(task, {
|
|
59
|
-
type: "status",
|
|
60
|
-
at,
|
|
61
|
-
author: opts.author,
|
|
62
|
-
from: currentStatus,
|
|
63
|
-
to: "BLOCKED",
|
|
64
|
-
note: commentBody,
|
|
65
|
-
}),
|
|
66
|
-
doc_version: normalizeTaskDocVersion(task.doc_version),
|
|
67
|
-
doc_updated_at: at,
|
|
68
|
-
doc_updated_by: opts.author,
|
|
69
|
-
};
|
|
70
54
|
await (useStore
|
|
71
|
-
? store.
|
|
72
|
-
|
|
55
|
+
? store.patch(opts.taskId, (current) => {
|
|
56
|
+
const currentStatus = String(current.status || "TODO").toUpperCase();
|
|
57
|
+
ensureStatusTransitionAllowed({
|
|
58
|
+
currentStatus,
|
|
59
|
+
nextStatus: "BLOCKED",
|
|
60
|
+
force: opts.force,
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
task: { status: "BLOCKED" },
|
|
64
|
+
appendComments: [{ author: opts.author, body: commentBody }],
|
|
65
|
+
appendEvents: [
|
|
66
|
+
{
|
|
67
|
+
type: "status",
|
|
68
|
+
at,
|
|
69
|
+
author: opts.author,
|
|
70
|
+
from: currentStatus,
|
|
71
|
+
to: "BLOCKED",
|
|
72
|
+
note: commentBody,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
docMeta: {
|
|
76
|
+
touch: true,
|
|
77
|
+
updatedBy: opts.author,
|
|
78
|
+
version: normalizeTaskDocVersion(current.doc_version),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
})
|
|
82
|
+
: ctx.taskBackend.writeTask({
|
|
83
|
+
...task,
|
|
84
|
+
status: "BLOCKED",
|
|
85
|
+
comments: commentsValue,
|
|
86
|
+
events: appendTaskEvent(task, {
|
|
87
|
+
type: "status",
|
|
88
|
+
at,
|
|
89
|
+
author: opts.author,
|
|
90
|
+
from: currentStatus,
|
|
91
|
+
to: "BLOCKED",
|
|
92
|
+
note: commentBody,
|
|
93
|
+
}),
|
|
94
|
+
doc_version: normalizeTaskDocVersion(task.doc_version),
|
|
95
|
+
doc_updated_at: at,
|
|
96
|
+
doc_updated_by: opts.author,
|
|
97
|
+
}));
|
|
73
98
|
let commitInfo = null;
|
|
74
99
|
if (opts.commitFromComment) {
|
|
75
100
|
const mode = ctx.config.workflow_mode;
|
|
@@ -80,14 +105,6 @@ export async function cmdBlock(opts) {
|
|
|
80
105
|
if (lockAgent)
|
|
81
106
|
executorAgent = lockAgent;
|
|
82
107
|
}
|
|
83
|
-
const expectedEmoji = await defaultCommitEmojiForAgentId(ctx, executorAgent);
|
|
84
|
-
if (typeof opts.commitEmoji === "string" && opts.commitEmoji.trim() !== expectedEmoji) {
|
|
85
|
-
throw new CliError({
|
|
86
|
-
exitCode: 2,
|
|
87
|
-
code: "E_USAGE",
|
|
88
|
-
message: invalidValueMessage("--commit-emoji", opts.commitEmoji, `${expectedEmoji} (executor agent=${executorAgent})`),
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
108
|
commitInfo = await commitFromComment({
|
|
92
109
|
ctx,
|
|
93
110
|
cwd: opts.cwd,
|
|
@@ -100,7 +117,7 @@ export async function cmdBlock(opts) {
|
|
|
100
117
|
statusTo: "BLOCKED",
|
|
101
118
|
commentBody: opts.body,
|
|
102
119
|
formattedComment,
|
|
103
|
-
emoji: opts.commitEmoji ??
|
|
120
|
+
emoji: opts.commitEmoji ?? defaultCommitEmojiForStatus("BLOCKED"),
|
|
104
121
|
allow: opts.commitAllow,
|
|
105
122
|
autoAllow: opts.commitAutoAllow || opts.commitAllow.length === 0,
|
|
106
123
|
allowTasks: opts.commitAllowTasks,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close-duplicate.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-duplicate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"close-duplicate.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-duplicate.ts"],"names":[],"mappings":"AAGA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAIrF,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DlB"}
|
|
@@ -3,7 +3,7 @@ import { CliError } from "../../shared/errors.js";
|
|
|
3
3
|
import { ensureActionApproved } from "../shared/approval-requirements.js";
|
|
4
4
|
import { loadTaskFromContext } from "../shared/task-backend.js";
|
|
5
5
|
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
6
|
-
import {
|
|
6
|
+
import { recordVerifiedNoopClosure } from "./close-shared.js";
|
|
7
7
|
export async function cmdTaskCloseDuplicate(opts) {
|
|
8
8
|
try {
|
|
9
9
|
const sourceId = opts.taskId.trim();
|
|
@@ -36,47 +36,22 @@ export async function cmdTaskCloseDuplicate(opts) {
|
|
|
36
36
|
const task = useStore
|
|
37
37
|
? await store.get(sourceId)
|
|
38
38
|
: await loadTaskFromContext({ ctx: opts.ctx, taskId: sourceId });
|
|
39
|
-
if (!opts.force && String(task.status || "TODO").toUpperCase() === "DONE") {
|
|
40
|
-
throw new CliError({
|
|
41
|
-
exitCode: 2,
|
|
42
|
-
code: "E_USAGE",
|
|
43
|
-
message: `Task is already DONE: ${sourceId} (use --force to override)`,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
39
|
const reason = opts.note?.trim();
|
|
47
40
|
const canonicalTitle = canonical.title?.trim() ? ` (${canonical.title.trim()})` : "";
|
|
48
41
|
const baseBody = `Verified: ${sourceId} is a bookkeeping duplicate of ${duplicateOf}${canonicalTitle}; ` +
|
|
49
42
|
"no code/config changes are expected in this task and closure is recorded as no-op.";
|
|
50
43
|
const body = reason ? `${baseBody}\n\nReason: ${reason}` : baseBody;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
type: "status",
|
|
63
|
-
at,
|
|
64
|
-
author: opts.author,
|
|
65
|
-
from: String(task.status || "TODO").toUpperCase(),
|
|
66
|
-
to: "DONE",
|
|
67
|
-
note: body,
|
|
68
|
-
}),
|
|
69
|
-
result_summary: `Closed as duplicate of ${duplicateOf}.`,
|
|
70
|
-
risk_level: "low",
|
|
71
|
-
breaking: false,
|
|
72
|
-
doc_version: normalizeTaskDocVersion(task.doc_version),
|
|
73
|
-
doc_updated_at: at,
|
|
74
|
-
doc_updated_by: opts.author,
|
|
75
|
-
};
|
|
76
|
-
await (useStore ? store.update(sourceId, () => next) : opts.ctx.taskBackend.writeTask(next));
|
|
77
|
-
if (!opts.quiet) {
|
|
78
|
-
process.stdout.write(`task.done: ${sourceId} (duplicate of ${duplicateOf})\n`);
|
|
79
|
-
}
|
|
44
|
+
await recordVerifiedNoopClosure({
|
|
45
|
+
ctx: opts.ctx,
|
|
46
|
+
task,
|
|
47
|
+
taskId: sourceId,
|
|
48
|
+
author: opts.author,
|
|
49
|
+
body,
|
|
50
|
+
resultSummary: `Closed as duplicate of ${duplicateOf}.`,
|
|
51
|
+
quiet: opts.quiet,
|
|
52
|
+
successMessage: `task.done: ${sourceId} (duplicate of ${duplicateOf})`,
|
|
53
|
+
force: opts.force,
|
|
54
|
+
});
|
|
80
55
|
return 0;
|
|
81
56
|
}
|
|
82
57
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close-noop.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-noop.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"close-noop.d.ts","sourceRoot":"","sources":["../../../src/commands/task/close-noop.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGpF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,CAAC,EAAE,cAAc,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoDlB"}
|
|
@@ -3,7 +3,7 @@ import { CliError } from "../../shared/errors.js";
|
|
|
3
3
|
import { ensureActionApproved } from "../shared/approval-requirements.js";
|
|
4
4
|
import { backendIsLocalFileBackend, getTaskStore } from "../shared/task-store.js";
|
|
5
5
|
import { loadCommandContext } from "../shared/task-backend.js";
|
|
6
|
-
import {
|
|
6
|
+
import { recordVerifiedNoopClosure } from "./close-shared.js";
|
|
7
7
|
export async function cmdTaskCloseNoop(opts) {
|
|
8
8
|
try {
|
|
9
9
|
const ctx = opts.ctx ??
|
|
@@ -38,35 +38,17 @@ export async function cmdTaskCloseNoop(opts) {
|
|
|
38
38
|
const normalizedNote = opts.note?.trim();
|
|
39
39
|
const baseBody = "Verified: no implementation changes were required; closure is recorded as no-op bookkeeping.";
|
|
40
40
|
const body = normalizedNote ? `${baseBody}\n\nNote: ${normalizedNote}` : baseBody;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
type: "status",
|
|
53
|
-
at,
|
|
54
|
-
author: opts.author,
|
|
55
|
-
from: String(task.status || "TODO").toUpperCase(),
|
|
56
|
-
to: "DONE",
|
|
57
|
-
note: body,
|
|
58
|
-
}),
|
|
59
|
-
result_summary: "No-op closure recorded.",
|
|
60
|
-
risk_level: "low",
|
|
61
|
-
breaking: false,
|
|
62
|
-
doc_version: normalizeTaskDocVersion(task.doc_version),
|
|
63
|
-
doc_updated_at: at,
|
|
64
|
-
doc_updated_by: opts.author,
|
|
65
|
-
};
|
|
66
|
-
await (useStore ? store.update(opts.taskId, () => next) : ctx.taskBackend.writeTask(next));
|
|
67
|
-
if (!opts.quiet) {
|
|
68
|
-
process.stdout.write(`task.done: ${opts.taskId} (no-op)\n`);
|
|
69
|
-
}
|
|
41
|
+
await recordVerifiedNoopClosure({
|
|
42
|
+
ctx,
|
|
43
|
+
task,
|
|
44
|
+
taskId: opts.taskId,
|
|
45
|
+
author: opts.author,
|
|
46
|
+
body,
|
|
47
|
+
resultSummary: "No-op closure recorded.",
|
|
48
|
+
quiet: opts.quiet,
|
|
49
|
+
successMessage: `task.done: ${opts.taskId} (no-op)`,
|
|
50
|
+
force: opts.force,
|
|
51
|
+
});
|
|
70
52
|
return 0;
|
|
71
53
|
}
|
|
72
54
|
catch (err) {
|