@agentplaneorg/core 0.1.7 → 0.1.9

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.
@@ -1 +1 @@
1
- {"version":3,"file":"commit-policy.d.ts","sourceRoot":"","sources":["../../src/commit/commit-policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAMF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAOlF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,kBAAkB,CAerB"}
1
+ {"version":3,"file":"commit-policy.d.ts","sourceRoot":"","sources":["../../src/commit/commit-policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAMF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAOlF;AA4BD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,kBAAkB,CAgCrB"}
@@ -15,17 +15,54 @@ export function isGenericSubject(subject, genericTokens) {
15
15
  const tokenSet = new Set(genericTokens.map((t) => t.toLowerCase()));
16
16
  return words.length <= 3 && words.every((w) => tokenSet.has(w));
17
17
  }
18
+ function parseSubjectTemplate(subject) {
19
+ const trimmed = subject.trim();
20
+ if (!trimmed)
21
+ return null;
22
+ const match = /^(\S+)\s+(\S+)\s+(.+)$/.exec(trimmed);
23
+ if (!match)
24
+ return null;
25
+ const emoji = match[1] ?? "";
26
+ const suffix = match[2] ?? "";
27
+ const rest = (match[3] ?? "").trim();
28
+ if (!emoji || !suffix || !rest)
29
+ return null;
30
+ const scopeMatch = /^([a-z][a-z0-9_-]*):\s+(.+)$/.exec(rest);
31
+ if (!scopeMatch)
32
+ return null;
33
+ const scope = scopeMatch[1] ?? "";
34
+ const summary = (scopeMatch[2] ?? "").trim();
35
+ if (!scope || !summary)
36
+ return null;
37
+ return { emoji, suffix, scope, summary };
38
+ }
18
39
  export function validateCommitSubject(opts) {
19
40
  const errors = [];
20
41
  const subject = opts.subject.trim();
21
42
  if (!subject)
22
43
  errors.push("commit subject must be non-empty");
23
44
  const suffix = extractTaskSuffix(opts.taskId);
24
- if (!subject.includes(opts.taskId) && (suffix.length === 0 || !subject.includes(suffix))) {
25
- errors.push("commit subject must include task id or suffix");
45
+ const template = parseSubjectTemplate(subject);
46
+ if (!template) {
47
+ errors.push("commit subject must match: <emoji> <suffix> <scope>: <summary>");
48
+ return { ok: false, errors };
49
+ }
50
+ if (!suffix) {
51
+ errors.push("task id has no suffix");
26
52
  }
27
- if (isGenericSubject(subject, opts.genericTokens)) {
53
+ else if (template.suffix.toLowerCase() !== suffix.toLowerCase()) {
54
+ errors.push("commit subject must include task suffix as the second token");
55
+ }
56
+ const normalizedSummary = stripPunctuation(template.summary).toLowerCase().trim();
57
+ if (!normalizedSummary) {
28
58
  errors.push("commit subject is too generic");
59
+ return { ok: errors.length === 0, errors };
29
60
  }
61
+ const words = normalizedSummary.split(/\s+/).filter(Boolean);
62
+ const tokenSet = new Set(opts.genericTokens.map((t) => t.toLowerCase()));
63
+ const nonGenericCount = words.filter((w) => !tokenSet.has(w)).length;
64
+ // Require at least two words in the summary and at least one non-generic token.
65
+ if (words.length < 2 || nonGenericCount < 1)
66
+ errors.push("commit subject is too generic");
30
67
  return { ok: errors.length === 0, errors };
31
68
  }
@@ -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,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
+ {"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
  }
@@ -148,6 +149,6 @@ export async function saveConfig(agentplaneDir, raw) {
148
149
  await mkdir(agentplaneDir, { recursive: true });
149
150
  const filePath = path.join(agentplaneDir, "config.json");
150
151
  const text = `${JSON.stringify(sanitized.sanitized, null, 2)}\n`;
151
- await writeFile(filePath, text, "utf8");
152
+ await atomicWriteFile(filePath, text, "utf8");
152
153
  return validated;
153
154
  }
@@ -1 +1 @@
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"}
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;AAgDxD,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,CAqBzB"}
@@ -4,6 +4,7 @@ import { resolveProject } from "../project/project-root.js";
4
4
  const execFileAsync = promisify(execFile);
5
5
  const DEFAULT_BASE_BRANCH = "main";
6
6
  const GIT_CONFIG_BASE_BRANCH_KEY = "agentplane.baseBranch";
7
+ const LEGACY_DEFAULT_BASE_BRANCH = "master";
7
8
  async function gitConfigGet(cwd, key) {
8
9
  try {
9
10
  const { stdout } = await execFileAsync("git", ["config", "--local", "--get", key], { cwd });
@@ -32,22 +33,19 @@ async function gitConfigUnset(cwd, key) {
32
33
  throw err;
33
34
  }
34
35
  }
35
- async function gitCurrentBranch(cwd) {
36
+ async function gitLocalBranchExists(cwd, branch) {
36
37
  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
38
+ await execFileAsync("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
39
+ cwd,
40
+ });
41
+ return true;
44
42
  }
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");
43
+ catch (err) {
44
+ const code = err?.code;
45
+ if (code === 1)
46
+ return false;
47
+ throw err;
49
48
  }
50
- return trimmed;
51
49
  }
52
50
  export async function getPinnedBaseBranch(opts) {
53
51
  const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
@@ -84,7 +82,12 @@ export async function resolveBaseBranch(opts) {
84
82
  cwd: opts.cwd,
85
83
  rootOverride: opts.rootOverride ?? null,
86
84
  });
87
- return await gitCurrentBranch(resolved.gitRoot);
85
+ if (await gitLocalBranchExists(resolved.gitRoot, DEFAULT_BASE_BRANCH))
86
+ return DEFAULT_BASE_BRANCH;
87
+ if (await gitLocalBranchExists(resolved.gitRoot, LEGACY_DEFAULT_BASE_BRANCH))
88
+ return LEGACY_DEFAULT_BASE_BRANCH;
89
+ // No safe default: require pinning to avoid silently treating feature branches as base.
90
+ return null;
88
91
  }
89
92
  return null;
90
93
  }
@@ -1 +1 @@
1
- {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/git/git-utils.ts"],"names":[],"mappings":"AAeA,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGpB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAcpB"}
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../../src/git/git-utils.ts"],"names":[],"mappings":"AAoBA,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGpB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOpB"}
@@ -2,30 +2,27 @@ import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { resolveProject } from "../project/project-root.js";
4
4
  const execFileAsync = promisify(execFile);
5
- async function gitLines(cwd, args) {
6
- const { stdout } = await execFileAsync("git", args, { cwd });
7
- return stdout
8
- .split("\n")
9
- .map((line) => line.trim())
10
- .filter((line) => line.length > 0);
5
+ async function gitNullSeparatedPaths(cwd, args) {
6
+ const { stdout } = await execFileAsync("git", args, {
7
+ cwd,
8
+ encoding: "buffer",
9
+ maxBuffer: 10 * 1024 * 1024,
10
+ });
11
+ const text = Buffer.isBuffer(stdout) ? stdout.toString("utf8") : String(stdout);
12
+ return text
13
+ .split("\0")
14
+ .map((entry) => entry.trim())
15
+ .filter((entry) => entry.length > 0);
11
16
  }
12
17
  export async function getStagedFiles(opts) {
13
18
  const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
14
- return await gitLines(resolved.gitRoot, ["diff", "--name-only", "--cached"]);
19
+ return await gitNullSeparatedPaths(resolved.gitRoot, ["diff", "--name-only", "--cached", "-z"]);
15
20
  }
16
21
  export async function getUnstagedFiles(opts) {
17
22
  const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
18
- const lines = await gitLines(resolved.gitRoot, ["status", "--porcelain"]);
19
- const files = [];
20
- for (const line of lines) {
21
- const status = line.slice(0, 2);
22
- const filePart = line.slice(3).trim();
23
- if (!filePart)
24
- continue;
25
- const name = filePart.includes("->") ? filePart.split("->").at(-1)?.trim() : filePart;
26
- if ((status === "??" || status[1] !== " ") && name) {
27
- files.push(name);
28
- }
29
- }
30
- return files;
23
+ const [unstaged, untracked] = await Promise.all([
24
+ gitNullSeparatedPaths(resolved.gitRoot, ["diff", "--name-only", "-z"]),
25
+ gitNullSeparatedPaths(resolved.gitRoot, ["ls-files", "--others", "--exclude-standard", "-z"]),
26
+ ]);
27
+ return [...new Set([...unstaged, ...untracked])].toSorted((a, b) => a.localeCompare(b));
31
28
  }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ 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";
@@ -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,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"}
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,6 +2,7 @@ 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";
@@ -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;AA6FD,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAoClF;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) {
@@ -34,6 +46,8 @@ export function parseTaskReadme(markdown) {
34
46
  return { frontmatter: parsed, body };
35
47
  }
36
48
  function renderScalar(value) {
49
+ if (value === undefined)
50
+ return "null";
37
51
  if (value === null)
38
52
  return "null";
39
53
  if (typeof value === "string")
@@ -44,58 +58,80 @@ function renderScalar(value) {
44
58
  return value ? "true" : "false";
45
59
  throw new TypeError(`Unsupported scalar type: ${typeof value}`);
46
60
  }
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
61
  function renderFlowSeq(value) {
68
- const parts = value.map((v) => {
62
+ const parts = value
63
+ .filter((v) => v !== undefined)
64
+ .map((v) => {
69
65
  if (Array.isArray(v))
70
66
  return renderFlowSeq(v);
71
67
  if (isRecord(v))
72
- return renderInlineMap(v, null);
68
+ return `{ ${orderedKeys(v, null)
69
+ .filter((k) => v[k] !== undefined)
70
+ .map((k) => `${k}: ${renderScalar(v[k])}`)
71
+ .join(", ")} }`;
73
72
  return renderScalar(v);
74
73
  });
75
74
  return `[${parts.join(", ")}]`;
76
75
  }
77
- function renderValue(key, value) {
76
+ function renderMapLines(value, indent, preferredKeyOrder) {
77
+ const keys = orderedKeys(value, preferredKeyOrder);
78
+ const lines = [];
79
+ for (const k of keys) {
80
+ const v = value[k];
81
+ if (v === undefined)
82
+ continue;
83
+ lines.push(...renderValueLines(k, v, indent));
84
+ }
85
+ return lines;
86
+ }
87
+ function isStringArray(value) {
88
+ return value.every((v) => typeof v === "string");
89
+ }
90
+ function renderValueLines(key, value, indent) {
91
+ if (value === undefined)
92
+ return [];
78
93
  if (Array.isArray(value)) {
79
94
  if (value.length === 0)
80
- return [`${key}: []`];
95
+ return [`${indent}${key}: []`];
96
+ if (isStringArray(value)) {
97
+ return [`${indent}${key}:`, ...value.map((item) => `${indent} - ${renderScalar(item)}`)];
98
+ }
81
99
  const allObjects = value.every((v) => isRecord(v));
82
100
  if (!allObjects)
83
- return [`${key}: ${renderFlowSeq(value)}`];
101
+ return [`${indent}${key}: ${renderFlowSeq(value)}`];
84
102
  return [
85
- `${key}:`,
86
- ...value.map((item) => {
103
+ `${indent}${key}:`,
104
+ ...value.flatMap((item) => {
87
105
  if (!isRecord(item))
88
106
  throw new TypeError("Expected an object item in YAML sequence");
89
- const preferred = key === "comments" ? ["author", "body"] : null;
90
- return ` - ${renderInlineMap(item, preferred)}`;
107
+ const preferred = key === "comments"
108
+ ? ["author", "body"]
109
+ : key === "events"
110
+ ? ["type", "at", "author", "from", "to", "state", "note", "body"]
111
+ : null;
112
+ const itemLines = renderMapLines(item, `${indent} `, preferred);
113
+ if (itemLines.length === 0)
114
+ return [`${indent} - {}`];
115
+ return [`${indent} -`, ...itemLines];
91
116
  }),
92
117
  ];
93
118
  }
94
119
  if (isRecord(value)) {
95
- const preferred = key === "commit" ? ["hash", "message"] : null;
96
- return [`${key}: ${renderInlineMap(value, preferred)}`];
120
+ const preferred = key === "origin"
121
+ ? ["system", "issue_id", "url"]
122
+ : key === "plan_approval"
123
+ ? ["state", "updated_at", "updated_by", "note"]
124
+ : key === "verification"
125
+ ? ["state", "updated_at", "updated_by", "note"]
126
+ : key === "commit"
127
+ ? ["hash", "message"]
128
+ : null;
129
+ const inner = renderMapLines(value, `${indent} `, preferred);
130
+ if (inner.length === 0)
131
+ return [`${indent}${key}: {}`];
132
+ return [`${indent}${key}:`, ...inner];
97
133
  }
98
- return [`${key}: ${renderScalar(value)}`];
134
+ return [`${indent}${key}: ${renderScalar(value)}`];
99
135
  }
100
136
  export function renderTaskFrontmatter(frontmatter) {
101
137
  const preferredKeyOrder = [
@@ -104,26 +140,31 @@ export function renderTaskFrontmatter(frontmatter) {
104
140
  "status",
105
141
  "priority",
106
142
  "owner",
143
+ "created_at",
144
+ "created_by",
145
+ "origin",
107
146
  "depends_on",
108
147
  "tags",
109
148
  "verify",
149
+ "plan_approval",
150
+ "verification",
110
151
  "commit",
111
152
  "comments",
153
+ "events",
112
154
  "doc_version",
113
155
  "doc_updated_at",
114
156
  "doc_updated_by",
115
157
  "description",
158
+ "id_source",
159
+ "dirty",
116
160
  ];
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);
161
+ const ordered = orderedKeys(frontmatter, preferredKeyOrder);
124
162
  const lines = [];
125
163
  for (const k of ordered) {
126
- lines.push(...renderValue(k, frontmatter[k]));
164
+ const value = frontmatter[k];
165
+ if (value === undefined)
166
+ continue;
167
+ lines.push(...renderValueLines(k, value, ""));
127
168
  }
128
169
  return `---\n${lines.join("\n")}\n---\n`;
129
170
  }
@@ -1,6 +1,16 @@
1
1
  import { generateTaskId } from "./task-id.js";
2
2
  export type TaskStatus = "TODO" | "DOING" | "DONE" | "BLOCKED";
3
3
  export type TaskPriority = "low" | "normal" | "med" | "high";
4
+ export type TaskEvent = {
5
+ type: "status" | "comment" | "verify";
6
+ at: string;
7
+ author: string;
8
+ from?: string;
9
+ to?: string;
10
+ state?: string;
11
+ note?: string;
12
+ body?: string;
13
+ };
4
14
  export type TaskFrontmatter = {
5
15
  id: string;
6
16
  title: string;
@@ -10,10 +20,23 @@ export type TaskFrontmatter = {
10
20
  depends_on: string[];
11
21
  tags: string[];
12
22
  verify: string[];
23
+ plan_approval?: {
24
+ state: "pending" | "approved" | "rejected";
25
+ updated_at: string | null;
26
+ updated_by: string | null;
27
+ note: string | null;
28
+ };
29
+ verification?: {
30
+ state: "pending" | "ok" | "needs_rework";
31
+ updated_at: string | null;
32
+ updated_by: string | null;
33
+ note: string | null;
34
+ };
13
35
  comments: {
14
36
  author: string;
15
37
  body: string;
16
38
  }[];
39
+ events?: TaskEvent[];
17
40
  doc_version: 2;
18
41
  doc_updated_at: string;
19
42
  doc_updated_by: 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,7 +145,20 @@ 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: [],
161
+ events: [],
137
162
  doc_version: 2,
138
163
  doc_updated_at: nowIso(),
139
164
  doc_updated_by: opts.owner,
@@ -141,8 +166,7 @@ export async function createTask(opts) {
141
166
  };
142
167
  const body = defaultTaskBody();
143
168
  const text = renderTaskReadme(frontmatter, body);
144
- await mkdir(path.dirname(readmePath), { recursive: true });
145
- await writeFile(readmePath, text, "utf8");
169
+ await atomicWriteFile(readmePath, text, "utf8");
146
170
  return { id, readmePath };
147
171
  }
148
172
  export async function setTaskDocSection(opts) {
@@ -154,19 +178,18 @@ export async function setTaskDocSection(opts) {
154
178
  }
155
179
  const tasksDir = path.join(resolved.gitRoot, loaded.config.paths.workflow_dir);
156
180
  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");
181
+ await updateTaskReadmeAtomic(readmePath, (parsed) => {
182
+ const updatedBy = resolveDocUpdatedBy(parsed.frontmatter, opts.updatedBy);
183
+ const nextFrontmatter = {
184
+ ...parsed.frontmatter,
185
+ doc_version: 2,
186
+ doc_updated_at: nowIso(),
187
+ doc_updated_by: updatedBy,
188
+ };
189
+ const baseDoc = ensureDocSections(parsed.body, loaded.config.tasks.doc.required_sections);
190
+ const nextBody = ensureDocSections(setMarkdownSection(baseDoc, opts.section, opts.text), loaded.config.tasks.doc.required_sections);
191
+ return { frontmatter: nextFrontmatter, body: nextBody };
192
+ });
170
193
  return { readmePath };
171
194
  }
172
195
  export async function readTask(opts) {
@@ -22,6 +22,16 @@ export type TasksExportTask = {
22
22
  author: string;
23
23
  body: string;
24
24
  }[];
25
+ events?: {
26
+ type: string;
27
+ at: string;
28
+ author: string;
29
+ from?: string;
30
+ to?: string;
31
+ state?: string;
32
+ note?: string;
33
+ body?: string;
34
+ }[];
25
35
  doc_version: 2;
26
36
  doc_updated_at: string;
27
37
  doc_updated_by: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-export.d.ts","sourceRoot":"","sources":["../../src/tasks/tasks-export.ts"],"names":[],"mappings":"AAaA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAWxD;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,CAAC,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,QAAQ,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,WAAW,EAAE,CAAC,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAGrE;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqE/B;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAW3D"}
1
+ {"version":3,"file":"tasks-export.d.ts","sourceRoot":"","sources":["../../src/tasks/tasks-export.ts"],"names":[],"mappings":"AAaA,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAWxD;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,CAAC,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,QAAQ,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,WAAW,EAAE,CAAC,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAGrE;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyG/B;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAW3D"}
@@ -55,7 +55,24 @@ export async function buildTasksExportSnapshot(opts) {
55
55
  .filter((c) => typeof c.author === "string" && typeof c.body === "string")
56
56
  .map((c) => ({ author: c.author, body: c.body }))
57
57
  : [];
58
- return {
58
+ const events = Array.isArray(fm.events)
59
+ ? fm.events
60
+ .filter((event) => isRecord(event))
61
+ .filter((event) => typeof event.type === "string" &&
62
+ typeof event.at === "string" &&
63
+ typeof event.author === "string")
64
+ .map((event) => ({
65
+ type: event.type,
66
+ at: event.at,
67
+ author: event.author,
68
+ from: typeof event.from === "string" ? event.from : undefined,
69
+ to: typeof event.to === "string" ? event.to : undefined,
70
+ state: typeof event.state === "string" ? event.state : undefined,
71
+ note: typeof event.note === "string" ? event.note : undefined,
72
+ body: typeof event.body === "string" ? event.body : undefined,
73
+ }))
74
+ : [];
75
+ const base = {
59
76
  id: typeof fm.id === "string" ? fm.id : t.id,
60
77
  title: typeof fm.title === "string" ? fm.title : "",
61
78
  status: typeof fm.status === "string" ? fm.status : "",
@@ -73,6 +90,10 @@ export async function buildTasksExportSnapshot(opts) {
73
90
  dirty: false,
74
91
  id_source: "generated",
75
92
  };
93
+ if (events.length > 0) {
94
+ return { ...base, events };
95
+ }
96
+ return base;
76
97
  });
77
98
  const sorted = exportTasks.toSorted((a, b) => a.id.localeCompare(b.id));
78
99
  const checksum = computeTasksChecksum(sorted);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentplaneorg/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Core utilities and models for the Agent Plane CLI.",
5
5
  "keywords": [
6
6
  "agentplane",
@@ -111,6 +111,7 @@
111
111
  "Summary",
112
112
  "Context",
113
113
  "Scope",
114
+ "Plan",
114
115
  "Risks",
115
116
  "Verify Steps",
116
117
  "Verification",
@@ -120,8 +121,8 @@
120
121
  "required_sections": [
121
122
  "Summary",
122
123
  "Scope",
124
+ "Plan",
123
125
  "Risks",
124
- "Verify Steps",
125
126
  "Verification",
126
127
  "Rollback Plan"
127
128
  ]
@@ -162,6 +163,7 @@
162
163
  "Summary",
163
164
  "Context",
164
165
  "Scope",
166
+ "Plan",
165
167
  "Risks",
166
168
  "Verify Steps",
167
169
  "Verification",
@@ -171,8 +173,8 @@
171
173
  "required_sections": [
172
174
  "Summary",
173
175
  "Scope",
176
+ "Plan",
174
177
  "Risks",
175
- "Verify Steps",
176
178
  "Verification",
177
179
  "Rollback Plan"
178
180
  ]
@@ -185,6 +187,7 @@
185
187
  "Summary",
186
188
  "Context",
187
189
  "Scope",
190
+ "Plan",
188
191
  "Risks",
189
192
  "Verify Steps",
190
193
  "Verification",
@@ -195,14 +198,7 @@
195
198
  "required_sections": {
196
199
  "type": "array",
197
200
  "items": { "type": "string", "minLength": 1 },
198
- "default": [
199
- "Summary",
200
- "Scope",
201
- "Risks",
202
- "Verify Steps",
203
- "Verification",
204
- "Rollback Plan"
205
- ]
201
+ "default": ["Summary", "Scope", "Plan", "Risks", "Verification", "Rollback Plan"]
206
202
  }
207
203
  }
208
204
  },