@agentplaneorg/core 0.1.6 → 0.1.8

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.
@@ -5,7 +5,6 @@ export type AgentplaneConfig = {
5
5
  workflow_mode: WorkflowMode;
6
6
  status_commit_policy: StatusCommitPolicy;
7
7
  finish_auto_status_commit: boolean;
8
- base_branch: string;
9
8
  agents?: {
10
9
  approvals: {
11
10
  require_plan: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAClD,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,CAAC,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,oBAAoB,EAAE,kBAAkB,CAAC;IACzC,yBAAyB,EAAE,OAAO,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE;QACP,SAAS,EAAE;YACT,YAAY,EAAE,OAAO,CAAC;YACtB,eAAe,EAAE,OAAO,CAAC;YACzB,cAAc,EAAE,OAAO,CAAC;SACzB,CAAC;KACH,CAAC;IACF,OAAO,CAAC,EAAE;QACR,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;KAC7C,CAAC;IACF,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC1D,KAAK,EAAE;QACL,wBAAwB,EAAE,MAAM,CAAC;QACjC,MAAM,EAAE;YAAE,aAAa,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC;QACpC,GAAG,EAAE;YAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;YAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC;QACzD,QAAQ,EAAE;YACR,KAAK,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;YAC7C,OAAO,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;YAC/C,QAAQ,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;SACjD,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACrC,aAAa,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,gCAAgC,EAAE,OAAO,CAAC;CAC3C,CAAC;AAEF,wBAAgB,aAAa,IAAI,gBAAgB,CAEhD;AA2CD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAU7D;AAID,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B,CAAC;AAQF,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyB7E;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,IAAI,CAgBN;AAED,wBAAsB,UAAU,CAC9B,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAO3B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,CAAC;AAClD,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,CAAC,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,oBAAoB,EAAE,kBAAkB,CAAC;IACzC,yBAAyB,EAAE,OAAO,CAAC;IACnC,MAAM,CAAC,EAAE;QACP,SAAS,EAAE;YACT,YAAY,EAAE,OAAO,CAAC;YACtB,eAAe,EAAE,OAAO,CAAC;YACzB,cAAc,EAAE,OAAO,CAAC;SACzB,CAAC;KACH,CAAC;IACF,OAAO,CAAC,EAAE;QACR,eAAe,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;KAC7C,CAAC;IACF,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,SAAS,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC1D,KAAK,EAAE;QACL,wBAAwB,EAAE,MAAM,CAAC;QACjC,MAAM,EAAE;YAAE,aAAa,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC;QACpC,GAAG,EAAE;YAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;YAAC,iBAAiB,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC;QACzD,QAAQ,EAAE;YACR,KAAK,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;YAC7C,OAAO,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;YAC/C,QAAQ,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,SAAS,EAAE,MAAM,CAAA;aAAE,CAAC;SACjD,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACrC,aAAa,EAAE;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,gCAAgC,EAAE,OAAO,CAAC;CAC3C,CAAC;AAEF,wBAAgB,aAAa,IAAI,gBAAgB,CAEhD;AAkED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAa7D;AAID,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B,CAAC;AAQF,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CA8B7E;AAkBD,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,IAAI,CAgBN;AAED,wBAAsB,UAAU,CAC9B,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAS3B"}
@@ -1,9 +1,10 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import AjvModule from "ajv";
6
6
  import AjvFormatsModule from "ajv-formats";
7
+ import { atomicWriteFile } from "../fs/atomic-write.js";
7
8
  export function defaultConfig() {
8
9
  return structuredClone(DEFAULT_CONFIG);
9
10
  }
@@ -29,8 +30,28 @@ function formatSchemaErrors(errors) {
29
30
  return "config schema validation failed";
30
31
  return AJV.errorsText(errors, { dataVar: "config" });
31
32
  }
33
+ const DEPRECATED_CONFIG_KEYS = ["base_branch"];
34
+ function stripDeprecatedConfigKeys(raw) {
35
+ const sanitized = { ...raw };
36
+ const removed = [];
37
+ for (const key of DEPRECATED_CONFIG_KEYS) {
38
+ if (key in sanitized) {
39
+ delete sanitized[key];
40
+ removed.push(key);
41
+ }
42
+ }
43
+ return { sanitized, removed };
44
+ }
45
+ function warnDeprecatedConfigKeys(keys) {
46
+ for (const key of keys) {
47
+ console.warn(`config key "${key}" is deprecated and ignored`);
48
+ }
49
+ }
32
50
  export function validateConfig(raw) {
33
- const candidate = raw && typeof raw === "object" ? structuredClone(raw) : raw;
51
+ let candidate = raw && typeof raw === "object" ? structuredClone(raw) : raw;
52
+ if (isRecord(candidate)) {
53
+ candidate = stripDeprecatedConfigKeys(candidate).sanitized;
54
+ }
34
55
  if (!validateSchema(candidate)) {
35
56
  throw new Error(formatSchemaErrors(validateSchema.errors));
36
57
  }
@@ -52,12 +73,18 @@ export async function loadConfig(agentplaneDir) {
52
73
  try {
53
74
  const rawText = await readFile(filePath, "utf8");
54
75
  const parsed = JSON.parse(rawText);
55
- const validated = validateConfig(parsed);
76
+ const rawRecord = isRecord(parsed) ? parsed : null;
77
+ const sanitized = rawRecord
78
+ ? stripDeprecatedConfigKeys(rawRecord)
79
+ : { sanitized: parsed, removed: [] };
80
+ if (sanitized.removed.length > 0)
81
+ warnDeprecatedConfigKeys(sanitized.removed);
82
+ const validated = validateConfig(sanitized.sanitized);
56
83
  return {
57
84
  path: filePath,
58
85
  exists: true,
59
86
  config: validated,
60
- raw: parsed,
87
+ raw: (sanitized.sanitized ?? parsed),
61
88
  };
62
89
  }
63
90
  catch (err) {
@@ -115,10 +142,13 @@ export function setByDottedKey(obj, dottedKey, value) {
115
142
  current[last] = parseScalar(value);
116
143
  }
117
144
  export async function saveConfig(agentplaneDir, raw) {
118
- const validated = validateConfig(raw);
145
+ const sanitized = stripDeprecatedConfigKeys(raw);
146
+ if (sanitized.removed.length > 0)
147
+ warnDeprecatedConfigKeys(sanitized.removed);
148
+ const validated = validateConfig(sanitized.sanitized);
119
149
  await mkdir(agentplaneDir, { recursive: true });
120
150
  const filePath = path.join(agentplaneDir, "config.json");
121
- const text = `${JSON.stringify(raw, null, 2)}\n`;
122
- await writeFile(filePath, text, "utf8");
151
+ const text = `${JSON.stringify(sanitized.sanitized, null, 2)}\n`;
152
+ await atomicWriteFile(filePath, text, "utf8");
123
153
  return validated;
124
154
  }
@@ -1,3 +1,4 @@
1
+ import type { WorkflowMode } from "../config/config.js";
1
2
  export declare function getPinnedBaseBranch(opts: {
2
3
  cwd: string;
3
4
  rootOverride?: string | null;
@@ -11,4 +12,14 @@ export declare function setPinnedBaseBranch(opts: {
11
12
  rootOverride?: string | null;
12
13
  value: string;
13
14
  }): Promise<string>;
15
+ export declare function clearPinnedBaseBranch(opts: {
16
+ cwd: string;
17
+ rootOverride?: string | null;
18
+ }): Promise<boolean>;
19
+ export declare function resolveBaseBranch(opts: {
20
+ cwd: string;
21
+ rootOverride?: string | null;
22
+ cliBaseOpt?: string | null;
23
+ mode: WorkflowMode;
24
+ }): Promise<string | null>;
14
25
  //# sourceMappingURL=base-branch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-branch.d.ts","sourceRoot":"","sources":["../../src/git/base-branch.ts"],"names":[],"mappings":"AA0BA,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGzB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAMlB"}
1
+ {"version":3,"file":"base-branch.d.ts","sourceRoot":"","sources":["../../src/git/base-branch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAiDxD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGzB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAMlB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,OAAO,CAAC,CAGnB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,YAAY,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBzB"}
@@ -20,6 +20,35 @@ async function gitConfigGet(cwd, key) {
20
20
  async function gitConfigSet(cwd, key, value) {
21
21
  await execFileAsync("git", ["config", "--local", key, value], { cwd });
22
22
  }
23
+ async function gitConfigUnset(cwd, key) {
24
+ try {
25
+ await execFileAsync("git", ["config", "--local", "--unset", key], { cwd });
26
+ return true;
27
+ }
28
+ catch (err) {
29
+ const code = err?.code;
30
+ if (code === 1 || code === 5)
31
+ return false;
32
+ throw err;
33
+ }
34
+ }
35
+ async function gitCurrentBranch(cwd) {
36
+ try {
37
+ const { stdout } = await execFileAsync("git", ["symbolic-ref", "--short", "HEAD"], { cwd });
38
+ const trimmed = stdout.trim();
39
+ if (trimmed)
40
+ return trimmed;
41
+ }
42
+ catch {
43
+ // fall through
44
+ }
45
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd });
46
+ const trimmed = stdout.trim();
47
+ if (!trimmed || trimmed === "HEAD") {
48
+ throw new Error("Failed to resolve current branch");
49
+ }
50
+ return trimmed;
51
+ }
23
52
  export async function getPinnedBaseBranch(opts) {
24
53
  const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
25
54
  return await gitConfigGet(resolved.gitRoot, GIT_CONFIG_BASE_BRANCH_KEY);
@@ -36,3 +65,26 @@ export async function setPinnedBaseBranch(opts) {
36
65
  await gitConfigSet(resolved.gitRoot, GIT_CONFIG_BASE_BRANCH_KEY, trimmed);
37
66
  return trimmed;
38
67
  }
68
+ export async function clearPinnedBaseBranch(opts) {
69
+ const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
70
+ return await gitConfigUnset(resolved.gitRoot, GIT_CONFIG_BASE_BRANCH_KEY);
71
+ }
72
+ export async function resolveBaseBranch(opts) {
73
+ const explicit = (opts.cliBaseOpt ?? "").trim();
74
+ if (explicit.length > 0)
75
+ return explicit;
76
+ const pinned = await getPinnedBaseBranch({
77
+ cwd: opts.cwd,
78
+ rootOverride: opts.rootOverride ?? null,
79
+ });
80
+ if (pinned)
81
+ return pinned;
82
+ if (opts.mode === "branch_pr") {
83
+ const resolved = await resolveProject({
84
+ cwd: opts.cwd,
85
+ rootOverride: opts.rootOverride ?? null,
86
+ });
87
+ return await gitCurrentBranch(resolved.gitRoot);
88
+ }
89
+ return null;
90
+ }
package/dist/index.d.ts CHANGED
@@ -2,13 +2,14 @@ export declare const CORE_VERSION = "0.0.0";
2
2
  export { findGitRoot, resolveProject, type ResolvedProject, type ResolveProjectOptions, } from "./project/project-root.js";
3
3
  export { defaultConfig, loadConfig, saveConfig, setByDottedKey, validateConfig, type AgentplaneConfig, type LoadedConfig, type StatusCommitPolicy, type WorkflowMode, } from "./config/config.js";
4
4
  export { parseTaskReadme, renderTaskFrontmatter, renderTaskReadme, type ParsedTaskReadme, } from "./tasks/task-readme.js";
5
+ export { readTaskReadme, updateTaskReadmeAtomic } from "./tasks/task-readme-io.js";
5
6
  export { docChanged, ensureDocSections, extractTaskDoc, mergeTaskDoc, normalizeDocSectionName, normalizeTaskDoc, parseDocSections, setMarkdownSection, splitCombinedHeadingLines, } from "./tasks/task-doc.js";
6
7
  export { atomicWriteFile } from "./fs/atomic-write.js";
7
8
  export { generateTaskId, timestampIdPrefix, TASK_ID_ALPHABET } from "./tasks/task-id.js";
8
9
  export { createTask, getTasksDir, listTasks, readTask, setTaskDocSection, taskReadmePath, validateTaskDocMetadata, type TaskFrontmatter, type TaskPriority, type TaskRecord, type TaskStatus, } from "./tasks/task-store.js";
9
10
  export { buildTasksExportSnapshot, canonicalTasksPayload, canonicalizeJson, computeTasksChecksum, writeTasksExport, type TasksExportMeta, type TasksExportSnapshot, type TasksExportTask, } from "./tasks/tasks-export.js";
10
11
  export { lintTasksFile, lintTasksSnapshot, readTasksExport, type TasksLintResult, } from "./tasks/tasks-lint.js";
11
- export { getBaseBranch, getPinnedBaseBranch, setPinnedBaseBranch } from "./git/base-branch.js";
12
+ export { clearPinnedBaseBranch, getBaseBranch, getPinnedBaseBranch, resolveBaseBranch, setPinnedBaseBranch, } from "./git/base-branch.js";
12
13
  export { extractTaskSuffix, isGenericSubject, validateCommitSubject, type CommitPolicyResult, } from "./commit/commit-policy.js";
13
14
  export { getStagedFiles, getUnstagedFiles } from "./git/git-utils.js";
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,OAAO,EACL,WAAW,EACX,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACd,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE/F,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,OAAO,EACL,WAAW,EACX,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,qBAAqB,GAC3B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAEnF,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEzF,OAAO,EACL,UAAU,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACd,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,kBAAkB,GACxB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -2,12 +2,13 @@ export const CORE_VERSION = "0.0.0";
2
2
  export { findGitRoot, resolveProject, } from "./project/project-root.js";
3
3
  export { defaultConfig, loadConfig, saveConfig, setByDottedKey, validateConfig, } from "./config/config.js";
4
4
  export { parseTaskReadme, renderTaskFrontmatter, renderTaskReadme, } from "./tasks/task-readme.js";
5
+ export { readTaskReadme, updateTaskReadmeAtomic } from "./tasks/task-readme-io.js";
5
6
  export { docChanged, ensureDocSections, extractTaskDoc, mergeTaskDoc, normalizeDocSectionName, normalizeTaskDoc, parseDocSections, setMarkdownSection, splitCombinedHeadingLines, } from "./tasks/task-doc.js";
6
7
  export { atomicWriteFile } from "./fs/atomic-write.js";
7
8
  export { generateTaskId, timestampIdPrefix, TASK_ID_ALPHABET } from "./tasks/task-id.js";
8
9
  export { createTask, getTasksDir, listTasks, readTask, setTaskDocSection, taskReadmePath, validateTaskDocMetadata, } from "./tasks/task-store.js";
9
10
  export { buildTasksExportSnapshot, canonicalTasksPayload, canonicalizeJson, computeTasksChecksum, writeTasksExport, } from "./tasks/tasks-export.js";
10
11
  export { lintTasksFile, lintTasksSnapshot, readTasksExport, } from "./tasks/tasks-lint.js";
11
- export { getBaseBranch, getPinnedBaseBranch, setPinnedBaseBranch } from "./git/base-branch.js";
12
+ export { clearPinnedBaseBranch, getBaseBranch, getPinnedBaseBranch, resolveBaseBranch, setPinnedBaseBranch, } from "./git/base-branch.js";
12
13
  export { extractTaskSuffix, isGenericSubject, validateCommitSubject, } from "./commit/commit-policy.js";
13
14
  export { getStagedFiles, getUnstagedFiles } from "./git/git-utils.js";
@@ -0,0 +1,7 @@
1
+ import { type ParsedTaskReadme } from "./task-readme.js";
2
+ export declare function readTaskReadme(readmePath: string): Promise<ParsedTaskReadme>;
3
+ export declare function updateTaskReadmeAtomic(readmePath: string, updater: (parsed: ParsedTaskReadme) => {
4
+ frontmatter: Record<string, unknown>;
5
+ body: string;
6
+ }): Promise<void>;
7
+ //# sourceMappingURL=task-readme-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-readme-io.d.ts","sourceRoot":"","sources":["../../src/tasks/task-readme-io.ts"],"names":[],"mappings":"AAIA,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAE5F,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAGlF;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5F,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,14 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { atomicWriteFile } from "../fs/atomic-write.js";
3
+ import { parseTaskReadme, renderTaskReadme } from "./task-readme.js";
4
+ export async function readTaskReadme(readmePath) {
5
+ const text = await readFile(readmePath, "utf8");
6
+ return parseTaskReadme(text);
7
+ }
8
+ export async function updateTaskReadmeAtomic(readmePath, updater) {
9
+ const text = await readFile(readmePath, "utf8");
10
+ const parsed = parseTaskReadme(text);
11
+ const next = updater(parsed);
12
+ const rendered = renderTaskReadme(next.frontmatter, next.body);
13
+ await atomicWriteFile(readmePath, rendered.endsWith("\n") ? rendered : `${rendered}\n`);
14
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"task-readme.d.ts","sourceRoot":"","sources":["../../src/tasks/task-readme.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAqBF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAYlE;AAiED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA8BlF;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3F"}
1
+ {"version":3,"file":"task-readme.d.ts","sourceRoot":"","sources":["../../src/tasks/task-readme.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAqBF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAYlE;AAkFD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiClF;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3F"}
@@ -2,6 +2,18 @@ import { parse as parseYaml } from "yaml";
2
2
  function isRecord(value) {
3
3
  return !!value && typeof value === "object" && !Array.isArray(value);
4
4
  }
5
+ function orderedKeys(value, preferredKeyOrder) {
6
+ const keys = Object.keys(value);
7
+ const ordered = [];
8
+ if (preferredKeyOrder) {
9
+ for (const k of preferredKeyOrder)
10
+ if (k in value)
11
+ ordered.push(k);
12
+ }
13
+ const remaining = keys.filter((k) => !ordered.includes(k)).toSorted((a, b) => a.localeCompare(b));
14
+ ordered.push(...remaining);
15
+ return ordered;
16
+ }
5
17
  function stripLeadingFrontmatterBlocks(body) {
6
18
  let next = body.replaceAll("\r\n", "\n");
7
19
  while (true) {
@@ -44,58 +56,69 @@ function renderScalar(value) {
44
56
  return value ? "true" : "false";
45
57
  throw new TypeError(`Unsupported scalar type: ${typeof value}`);
46
58
  }
47
- function renderInlineMap(value, preferredKeyOrder) {
48
- const keys = Object.keys(value);
49
- const ordered = [];
50
- if (preferredKeyOrder) {
51
- for (const k of preferredKeyOrder)
52
- if (k in value)
53
- ordered.push(k);
54
- }
55
- const remaining = keys.filter((k) => !ordered.includes(k)).toSorted((a, b) => a.localeCompare(b));
56
- ordered.push(...remaining);
57
- const parts = ordered.map((k) => {
58
- const v = value[k];
59
- if (Array.isArray(v))
60
- return `${k}: ${renderFlowSeq(v)}`;
61
- if (isRecord(v))
62
- return `${k}: ${renderInlineMap(v, null)}`;
63
- return `${k}: ${renderScalar(v)}`;
64
- });
65
- return `{ ${parts.join(", ")} }`;
66
- }
67
59
  function renderFlowSeq(value) {
68
60
  const parts = value.map((v) => {
69
61
  if (Array.isArray(v))
70
62
  return renderFlowSeq(v);
71
63
  if (isRecord(v))
72
- return renderInlineMap(v, null);
64
+ return `{ ${orderedKeys(v, null)
65
+ .map((k) => `${k}: ${renderScalar(v[k])}`)
66
+ .join(", ")} }`;
73
67
  return renderScalar(v);
74
68
  });
75
69
  return `[${parts.join(", ")}]`;
76
70
  }
77
- function renderValue(key, value) {
71
+ function renderMapLines(value, indent, preferredKeyOrder) {
72
+ const keys = orderedKeys(value, preferredKeyOrder);
73
+ const lines = [];
74
+ for (const k of keys) {
75
+ const v = value[k];
76
+ lines.push(...renderValueLines(k, v, indent));
77
+ }
78
+ return lines;
79
+ }
80
+ function isStringArray(value) {
81
+ return value.every((v) => typeof v === "string");
82
+ }
83
+ function renderValueLines(key, value, indent) {
78
84
  if (Array.isArray(value)) {
79
85
  if (value.length === 0)
80
- return [`${key}: []`];
86
+ return [`${indent}${key}: []`];
87
+ if (isStringArray(value)) {
88
+ return [`${indent}${key}:`, ...value.map((item) => `${indent} - ${renderScalar(item)}`)];
89
+ }
81
90
  const allObjects = value.every((v) => isRecord(v));
82
91
  if (!allObjects)
83
- return [`${key}: ${renderFlowSeq(value)}`];
92
+ return [`${indent}${key}: ${renderFlowSeq(value)}`];
84
93
  return [
85
- `${key}:`,
86
- ...value.map((item) => {
94
+ `${indent}${key}:`,
95
+ ...value.flatMap((item) => {
87
96
  if (!isRecord(item))
88
97
  throw new TypeError("Expected an object item in YAML sequence");
89
98
  const preferred = key === "comments" ? ["author", "body"] : null;
90
- return ` - ${renderInlineMap(item, preferred)}`;
99
+ const itemLines = renderMapLines(item, `${indent} `, preferred);
100
+ if (itemLines.length === 0)
101
+ return [`${indent} - {}`];
102
+ return [`${indent} -`, ...itemLines];
91
103
  }),
92
104
  ];
93
105
  }
94
106
  if (isRecord(value)) {
95
- const preferred = key === "commit" ? ["hash", "message"] : null;
96
- return [`${key}: ${renderInlineMap(value, preferred)}`];
107
+ const preferred = key === "origin"
108
+ ? ["system", "issue_id", "url"]
109
+ : key === "plan_approval"
110
+ ? ["state", "updated_at", "updated_by", "note"]
111
+ : key === "verification"
112
+ ? ["state", "updated_at", "updated_by", "note"]
113
+ : key === "commit"
114
+ ? ["hash", "message"]
115
+ : null;
116
+ const inner = renderMapLines(value, `${indent} `, preferred);
117
+ if (inner.length === 0)
118
+ return [`${indent}${key}: {}`];
119
+ return [`${indent}${key}:`, ...inner];
97
120
  }
98
- return [`${key}: ${renderScalar(value)}`];
121
+ return [`${indent}${key}: ${renderScalar(value)}`];
99
122
  }
100
123
  export function renderTaskFrontmatter(frontmatter) {
101
124
  const preferredKeyOrder = [
@@ -104,26 +127,27 @@ export function renderTaskFrontmatter(frontmatter) {
104
127
  "status",
105
128
  "priority",
106
129
  "owner",
130
+ "created_at",
131
+ "created_by",
132
+ "origin",
107
133
  "depends_on",
108
134
  "tags",
109
135
  "verify",
136
+ "plan_approval",
137
+ "verification",
110
138
  "commit",
111
139
  "comments",
112
140
  "doc_version",
113
141
  "doc_updated_at",
114
142
  "doc_updated_by",
115
143
  "description",
144
+ "id_source",
145
+ "dirty",
116
146
  ];
117
- const keys = Object.keys(frontmatter);
118
- const ordered = [];
119
- for (const k of preferredKeyOrder)
120
- if (k in frontmatter)
121
- ordered.push(k);
122
- const remaining = keys.filter((k) => !ordered.includes(k)).toSorted((a, b) => a.localeCompare(b));
123
- ordered.push(...remaining);
147
+ const ordered = orderedKeys(frontmatter, preferredKeyOrder);
124
148
  const lines = [];
125
149
  for (const k of ordered) {
126
- lines.push(...renderValue(k, frontmatter[k]));
150
+ lines.push(...renderValueLines(k, frontmatter[k], ""));
127
151
  }
128
152
  return `---\n${lines.join("\n")}\n---\n`;
129
153
  }
@@ -10,6 +10,18 @@ export type TaskFrontmatter = {
10
10
  depends_on: string[];
11
11
  tags: string[];
12
12
  verify: string[];
13
+ plan_approval?: {
14
+ state: "pending" | "approved" | "rejected";
15
+ updated_at: string | null;
16
+ updated_by: string | null;
17
+ note: string | null;
18
+ };
19
+ verification?: {
20
+ state: "pending" | "ok" | "needs_rework";
21
+ updated_at: string | null;
22
+ updated_by: string | null;
23
+ note: string | null;
24
+ };
13
25
  comments: {
14
26
  author: string;
15
27
  body: string;
@@ -1 +1 @@
1
- {"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../src/tasks/task-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../src/tasks/task-store.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { loadConfig } from "../config/config.js";
4
+ import { atomicWriteFile } from "../fs/atomic-write.js";
4
5
  import { resolveProject } from "../project/project-root.js";
5
6
  import { parseTaskReadme, renderTaskReadme } from "./task-readme.js";
7
+ import { updateTaskReadmeAtomic } from "./task-readme-io.js";
6
8
  import { ensureDocSections, setMarkdownSection } from "./task-doc.js";
7
9
  import { generateTaskId } from "./task-id.js";
8
10
  function nowIso() {
@@ -55,15 +57,25 @@ function defaultTaskBody() {
55
57
  "## Scope",
56
58
  "",
57
59
  "",
58
- "## Risks",
60
+ "## Plan",
59
61
  "",
60
62
  "",
61
- "## Verify Steps",
63
+ "## Risks",
62
64
  "",
63
65
  "",
64
66
  "## Verification",
65
67
  "",
66
68
  "",
69
+ "### Plan",
70
+ "",
71
+ "",
72
+ "### Results",
73
+ "",
74
+ "",
75
+ "<!-- BEGIN VERIFICATION RESULTS -->",
76
+ "<!-- END VERIFICATION RESULTS -->",
77
+ "",
78
+ "",
67
79
  "## Rollback Plan",
68
80
  "",
69
81
  ].join("\n");
@@ -133,6 +145,18 @@ export async function createTask(opts) {
133
145
  depends_on: opts.dependsOn,
134
146
  tags: opts.tags,
135
147
  verify: opts.verify,
148
+ plan_approval: {
149
+ state: "pending",
150
+ updated_at: null,
151
+ updated_by: null,
152
+ note: null,
153
+ },
154
+ verification: {
155
+ state: "pending",
156
+ updated_at: null,
157
+ updated_by: null,
158
+ note: null,
159
+ },
136
160
  comments: [],
137
161
  doc_version: 2,
138
162
  doc_updated_at: nowIso(),
@@ -141,8 +165,7 @@ export async function createTask(opts) {
141
165
  };
142
166
  const body = defaultTaskBody();
143
167
  const text = renderTaskReadme(frontmatter, body);
144
- await mkdir(path.dirname(readmePath), { recursive: true });
145
- await writeFile(readmePath, text, "utf8");
168
+ await atomicWriteFile(readmePath, text, "utf8");
146
169
  return { id, readmePath };
147
170
  }
148
171
  export async function setTaskDocSection(opts) {
@@ -154,19 +177,18 @@ export async function setTaskDocSection(opts) {
154
177
  }
155
178
  const tasksDir = path.join(resolved.gitRoot, loaded.config.paths.workflow_dir);
156
179
  const readmePath = taskReadmePath(tasksDir, opts.taskId);
157
- const original = await readFile(readmePath, "utf8");
158
- const parsed = parseTaskReadme(original);
159
- const updatedBy = resolveDocUpdatedBy(parsed.frontmatter, opts.updatedBy);
160
- const nextFrontmatter = {
161
- ...parsed.frontmatter,
162
- doc_version: 2,
163
- doc_updated_at: nowIso(),
164
- doc_updated_by: updatedBy,
165
- };
166
- const baseDoc = ensureDocSections(parsed.body, loaded.config.tasks.doc.required_sections);
167
- const nextBody = ensureDocSections(setMarkdownSection(baseDoc, opts.section, opts.text), loaded.config.tasks.doc.required_sections);
168
- const nextText = renderTaskReadme(nextFrontmatter, nextBody);
169
- await writeFile(readmePath, nextText, "utf8");
180
+ await updateTaskReadmeAtomic(readmePath, (parsed) => {
181
+ const updatedBy = resolveDocUpdatedBy(parsed.frontmatter, opts.updatedBy);
182
+ const nextFrontmatter = {
183
+ ...parsed.frontmatter,
184
+ doc_version: 2,
185
+ doc_updated_at: nowIso(),
186
+ doc_updated_by: updatedBy,
187
+ };
188
+ const baseDoc = ensureDocSections(parsed.body, loaded.config.tasks.doc.required_sections);
189
+ const nextBody = ensureDocSections(setMarkdownSection(baseDoc, opts.section, opts.text), loaded.config.tasks.doc.required_sections);
190
+ return { frontmatter: nextFrontmatter, body: nextBody };
191
+ });
170
192
  return { readmePath };
171
193
  }
172
194
  export async function readTask(opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentplaneorg/core",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Core utilities and models for the Agent Plane CLI.",
5
5
  "keywords": [
6
6
  "agentplane",
@@ -18,7 +18,6 @@
18
18
  "default": "warn"
19
19
  },
20
20
  "finish_auto_status_commit": { "type": "boolean", "default": true },
21
- "base_branch": { "type": "string", "minLength": 1, "default": "main" },
22
21
  "agents": {
23
22
  "type": "object",
24
23
  "additionalProperties": true,
@@ -112,6 +111,7 @@
112
111
  "Summary",
113
112
  "Context",
114
113
  "Scope",
114
+ "Plan",
115
115
  "Risks",
116
116
  "Verify Steps",
117
117
  "Verification",
@@ -121,8 +121,8 @@
121
121
  "required_sections": [
122
122
  "Summary",
123
123
  "Scope",
124
+ "Plan",
124
125
  "Risks",
125
- "Verify Steps",
126
126
  "Verification",
127
127
  "Rollback Plan"
128
128
  ]
@@ -163,6 +163,7 @@
163
163
  "Summary",
164
164
  "Context",
165
165
  "Scope",
166
+ "Plan",
166
167
  "Risks",
167
168
  "Verify Steps",
168
169
  "Verification",
@@ -172,8 +173,8 @@
172
173
  "required_sections": [
173
174
  "Summary",
174
175
  "Scope",
176
+ "Plan",
175
177
  "Risks",
176
- "Verify Steps",
177
178
  "Verification",
178
179
  "Rollback Plan"
179
180
  ]
@@ -186,6 +187,7 @@
186
187
  "Summary",
187
188
  "Context",
188
189
  "Scope",
190
+ "Plan",
189
191
  "Risks",
190
192
  "Verify Steps",
191
193
  "Verification",
@@ -196,14 +198,7 @@
196
198
  "required_sections": {
197
199
  "type": "array",
198
200
  "items": { "type": "string", "minLength": 1 },
199
- "default": [
200
- "Summary",
201
- "Scope",
202
- "Risks",
203
- "Verify Steps",
204
- "Verification",
205
- "Rollback Plan"
206
- ]
201
+ "default": ["Summary", "Scope", "Plan", "Risks", "Verification", "Rollback Plan"]
207
202
  }
208
203
  }
209
204
  },