@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.
- package/dist/commit/commit-policy.d.ts.map +1 -1
- package/dist/commit/commit-policy.js +40 -3
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +3 -2
- package/dist/git/base-branch.d.ts.map +1 -1
- package/dist/git/base-branch.js +17 -14
- package/dist/git/git-utils.d.ts.map +1 -1
- package/dist/git/git-utils.js +17 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tasks/task-readme-io.d.ts +7 -0
- package/dist/tasks/task-readme-io.d.ts.map +1 -0
- package/dist/tasks/task-readme-io.js +14 -0
- package/dist/tasks/task-readme.d.ts.map +1 -1
- package/dist/tasks/task-readme.js +81 -40
- package/dist/tasks/task-store.d.ts +23 -0
- package/dist/tasks/task-store.d.ts.map +1 -1
- package/dist/tasks/task-store.js +41 -18
- package/dist/tasks/tasks-export.d.ts +10 -0
- package/dist/tasks/tasks-export.d.ts.map +1 -1
- package/dist/tasks/tasks-export.js +22 -1
- package/package.json +1 -1
- package/schemas/config.schema.json +6 -10
|
@@ -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;
|
|
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
|
-
|
|
25
|
-
|
|
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 (
|
|
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":"
|
|
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"}
|
package/dist/config/config.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile
|
|
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
|
|
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;
|
|
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"}
|
package/dist/git/base-branch.js
CHANGED
|
@@ -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
|
|
36
|
+
async function gitLocalBranchExists(cwd, branch) {
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|
package/dist/git/git-utils.js
CHANGED
|
@@ -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
|
|
6
|
-
const { stdout } = await execFileAsync("git", args, {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":"
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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"
|
|
90
|
-
|
|
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 === "
|
|
96
|
-
|
|
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
|
|
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
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../src/tasks/task-store.ts"],"names":[],"mappings":"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"}
|
package/dist/tasks/task-store.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile
|
|
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
|
-
"##
|
|
60
|
+
"## Plan",
|
|
59
61
|
"",
|
|
60
62
|
"",
|
|
61
|
-
"##
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
@@ -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
|
},
|