@agentplaneorg/core 0.1.0
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/LICENSE +22 -0
- package/README.md +5 -0
- package/dist/base-branch.d.ts +14 -0
- package/dist/base-branch.d.ts.map +1 -0
- package/dist/base-branch.js +38 -0
- package/dist/commit-policy.d.ts +12 -0
- package/dist/commit-policy.d.ts.map +1 -0
- package/dist/commit-policy.js +31 -0
- package/dist/config.d.ts +74 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +237 -0
- package/dist/git-utils.d.ts +9 -0
- package/dist/git-utils.d.ts.map +1 -0
- package/dist/git-utils.js +31 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/project-root.d.ts +11 -0
- package/dist/project-root.d.ts.map +1 -0
- package/dist/project-root.js +38 -0
- package/dist/task-readme.d.ts +8 -0
- package/dist/task-readme.d.ts.map +1 -0
- package/dist/task-readme.js +118 -0
- package/dist/task-store.d.ts +74 -0
- package/dist/task-store.d.ts.map +1 -0
- package/dist/task-store.js +211 -0
- package/dist/tasks-export.d.ts +49 -0
- package/dist/tasks-export.d.ts.map +1 -0
- package/dist/tasks-export.js +96 -0
- package/dist/tasks-lint.d.ts +19 -0
- package/dist/tasks-lint.d.ts.map +1 -0
- package/dist/tasks-lint.js +167 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Denis Smirnov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function getPinnedBaseBranch(opts: {
|
|
2
|
+
cwd: string;
|
|
3
|
+
rootOverride?: string | null;
|
|
4
|
+
}): Promise<string | null>;
|
|
5
|
+
export declare function getBaseBranch(opts: {
|
|
6
|
+
cwd: string;
|
|
7
|
+
rootOverride?: string | null;
|
|
8
|
+
}): Promise<string>;
|
|
9
|
+
export declare function setPinnedBaseBranch(opts: {
|
|
10
|
+
cwd: string;
|
|
11
|
+
rootOverride?: string | null;
|
|
12
|
+
value: string;
|
|
13
|
+
}): Promise<string>;
|
|
14
|
+
//# sourceMappingURL=base-branch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-branch.d.ts","sourceRoot":"","sources":["../src/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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { resolveProject } from "./project-root.js";
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
const DEFAULT_BASE_BRANCH = "main";
|
|
6
|
+
const GIT_CONFIG_BASE_BRANCH_KEY = "agentplane.baseBranch";
|
|
7
|
+
async function gitConfigGet(cwd, key) {
|
|
8
|
+
try {
|
|
9
|
+
const { stdout } = await execFileAsync("git", ["config", "--local", "--get", key], { cwd });
|
|
10
|
+
const trimmed = stdout.trim();
|
|
11
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
const code = err?.code;
|
|
15
|
+
if (code === 1)
|
|
16
|
+
return null;
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function gitConfigSet(cwd, key, value) {
|
|
21
|
+
await execFileAsync("git", ["config", "--local", key, value], { cwd });
|
|
22
|
+
}
|
|
23
|
+
export async function getPinnedBaseBranch(opts) {
|
|
24
|
+
const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
|
|
25
|
+
return await gitConfigGet(resolved.gitRoot, GIT_CONFIG_BASE_BRANCH_KEY);
|
|
26
|
+
}
|
|
27
|
+
export async function getBaseBranch(opts) {
|
|
28
|
+
const pinned = await getPinnedBaseBranch(opts);
|
|
29
|
+
return pinned ?? DEFAULT_BASE_BRANCH;
|
|
30
|
+
}
|
|
31
|
+
export async function setPinnedBaseBranch(opts) {
|
|
32
|
+
const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
|
|
33
|
+
const trimmed = opts.value.trim();
|
|
34
|
+
if (trimmed.length === 0)
|
|
35
|
+
throw new Error("base branch must be non-empty");
|
|
36
|
+
await gitConfigSet(resolved.gitRoot, GIT_CONFIG_BASE_BRANCH_KEY, trimmed);
|
|
37
|
+
return trimmed;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CommitPolicyResult = {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
errors: string[];
|
|
4
|
+
};
|
|
5
|
+
export declare function extractTaskSuffix(taskId: string): string;
|
|
6
|
+
export declare function isGenericSubject(subject: string, genericTokens: string[]): boolean;
|
|
7
|
+
export declare function validateCommitSubject(opts: {
|
|
8
|
+
subject: string;
|
|
9
|
+
taskId: string;
|
|
10
|
+
genericTokens: string[];
|
|
11
|
+
}): CommitPolicyResult;
|
|
12
|
+
//# sourceMappingURL=commit-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-policy.d.ts","sourceRoot":"","sources":["../src/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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function stripPunctuation(input) {
|
|
2
|
+
return input.replaceAll(/[^\p{L}\p{N}\s-]/gu, " ");
|
|
3
|
+
}
|
|
4
|
+
export function extractTaskSuffix(taskId) {
|
|
5
|
+
const parts = taskId.split("-");
|
|
6
|
+
return parts.at(-1) ?? "";
|
|
7
|
+
}
|
|
8
|
+
export function isGenericSubject(subject, genericTokens) {
|
|
9
|
+
const normalized = stripPunctuation(subject).toLowerCase().trim();
|
|
10
|
+
if (!normalized)
|
|
11
|
+
return true;
|
|
12
|
+
const words = normalized.split(/\s+/).filter(Boolean);
|
|
13
|
+
if (words.length === 0)
|
|
14
|
+
return true;
|
|
15
|
+
const tokenSet = new Set(genericTokens.map((t) => t.toLowerCase()));
|
|
16
|
+
return words.length <= 3 && words.every((w) => tokenSet.has(w));
|
|
17
|
+
}
|
|
18
|
+
export function validateCommitSubject(opts) {
|
|
19
|
+
const errors = [];
|
|
20
|
+
const subject = opts.subject.trim();
|
|
21
|
+
if (!subject)
|
|
22
|
+
errors.push("commit subject must be non-empty");
|
|
23
|
+
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");
|
|
26
|
+
}
|
|
27
|
+
if (isGenericSubject(subject, opts.genericTokens)) {
|
|
28
|
+
errors.push("commit subject is too generic");
|
|
29
|
+
}
|
|
30
|
+
return { ok: errors.length === 0, errors };
|
|
31
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type WorkflowMode = "direct" | "branch_pr";
|
|
2
|
+
export type StatusCommitPolicy = "off" | "warn" | "confirm";
|
|
3
|
+
export type AgentplaneConfig = {
|
|
4
|
+
schema_version: 1;
|
|
5
|
+
workflow_mode: WorkflowMode;
|
|
6
|
+
status_commit_policy: StatusCommitPolicy;
|
|
7
|
+
finish_auto_status_commit: boolean;
|
|
8
|
+
base_branch: string;
|
|
9
|
+
agents?: {
|
|
10
|
+
approvals: {
|
|
11
|
+
require_plan: boolean;
|
|
12
|
+
require_network: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
recipes?: {
|
|
16
|
+
storage_default: "link" | "copy" | "global";
|
|
17
|
+
};
|
|
18
|
+
paths: {
|
|
19
|
+
agents_dir: string;
|
|
20
|
+
tasks_path: string;
|
|
21
|
+
workflow_dir: string;
|
|
22
|
+
worktrees_dir: string;
|
|
23
|
+
};
|
|
24
|
+
branch: {
|
|
25
|
+
task_prefix: string;
|
|
26
|
+
};
|
|
27
|
+
framework: {
|
|
28
|
+
source: string;
|
|
29
|
+
last_update: string | null;
|
|
30
|
+
};
|
|
31
|
+
tasks: {
|
|
32
|
+
id_suffix_length_default: number;
|
|
33
|
+
verify: {
|
|
34
|
+
required_tags: string[];
|
|
35
|
+
};
|
|
36
|
+
doc: {
|
|
37
|
+
sections: string[];
|
|
38
|
+
required_sections: string[];
|
|
39
|
+
};
|
|
40
|
+
comments: {
|
|
41
|
+
start: {
|
|
42
|
+
prefix: string;
|
|
43
|
+
min_chars: number;
|
|
44
|
+
};
|
|
45
|
+
blocked: {
|
|
46
|
+
prefix: string;
|
|
47
|
+
min_chars: number;
|
|
48
|
+
};
|
|
49
|
+
verified: {
|
|
50
|
+
prefix: string;
|
|
51
|
+
min_chars: number;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
commit: {
|
|
56
|
+
generic_tokens: string[];
|
|
57
|
+
};
|
|
58
|
+
tasks_backend: {
|
|
59
|
+
config_path: string;
|
|
60
|
+
};
|
|
61
|
+
closure_commit_requires_approval: boolean;
|
|
62
|
+
};
|
|
63
|
+
export declare function defaultConfig(): AgentplaneConfig;
|
|
64
|
+
export declare function validateConfig(raw: unknown): AgentplaneConfig;
|
|
65
|
+
export type LoadedConfig = {
|
|
66
|
+
path: string;
|
|
67
|
+
exists: boolean;
|
|
68
|
+
config: AgentplaneConfig;
|
|
69
|
+
raw: Record<string, unknown>;
|
|
70
|
+
};
|
|
71
|
+
export declare function loadConfig(agentplaneDir: string): Promise<LoadedConfig>;
|
|
72
|
+
export declare function setByDottedKey(obj: Record<string, unknown>, dottedKey: string, value: string): void;
|
|
73
|
+
export declare function saveConfig(agentplaneDir: string, raw: Record<string, unknown>): Promise<AgentplaneConfig>;
|
|
74
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,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;SAC1B,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,CAmDhD;AAMD,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAiG7D;AAED,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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function defaultConfig() {
|
|
4
|
+
return {
|
|
5
|
+
schema_version: 1,
|
|
6
|
+
workflow_mode: "direct",
|
|
7
|
+
status_commit_policy: "warn",
|
|
8
|
+
finish_auto_status_commit: true,
|
|
9
|
+
base_branch: "main",
|
|
10
|
+
agents: {
|
|
11
|
+
approvals: {
|
|
12
|
+
require_plan: true,
|
|
13
|
+
require_network: true,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
recipes: {
|
|
17
|
+
storage_default: "link",
|
|
18
|
+
},
|
|
19
|
+
paths: {
|
|
20
|
+
agents_dir: ".agentplane/agents",
|
|
21
|
+
tasks_path: ".agentplane/tasks.json",
|
|
22
|
+
workflow_dir: ".agentplane/tasks",
|
|
23
|
+
worktrees_dir: ".agentplane/worktrees",
|
|
24
|
+
},
|
|
25
|
+
branch: { task_prefix: "task" },
|
|
26
|
+
framework: { source: "https://github.com/basilisk-labs/agent-plane", last_update: null },
|
|
27
|
+
tasks: {
|
|
28
|
+
id_suffix_length_default: 6,
|
|
29
|
+
verify: { required_tags: ["code", "backend", "frontend"] },
|
|
30
|
+
doc: {
|
|
31
|
+
sections: [
|
|
32
|
+
"Summary",
|
|
33
|
+
"Context",
|
|
34
|
+
"Scope",
|
|
35
|
+
"Risks",
|
|
36
|
+
"Verify Steps",
|
|
37
|
+
"Rollback Plan",
|
|
38
|
+
"Notes",
|
|
39
|
+
],
|
|
40
|
+
required_sections: ["Summary", "Scope", "Risks", "Verify Steps", "Rollback Plan"],
|
|
41
|
+
},
|
|
42
|
+
comments: {
|
|
43
|
+
start: { prefix: "Start:", min_chars: 40 },
|
|
44
|
+
blocked: { prefix: "Blocked:", min_chars: 40 },
|
|
45
|
+
verified: { prefix: "Verified:", min_chars: 60 },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
commit: {
|
|
49
|
+
generic_tokens: ["start", "status", "mark", "done", "wip", "update", "tasks", "task"],
|
|
50
|
+
},
|
|
51
|
+
tasks_backend: { config_path: ".agentplane/backends/local/backend.json" },
|
|
52
|
+
closure_commit_requires_approval: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
export function validateConfig(raw) {
|
|
59
|
+
if (!isRecord(raw))
|
|
60
|
+
throw new TypeError("config must be an object");
|
|
61
|
+
if (raw.schema_version !== 1)
|
|
62
|
+
throw new Error("config.schema_version must be 1");
|
|
63
|
+
if (raw.workflow_mode !== "direct" && raw.workflow_mode !== "branch_pr") {
|
|
64
|
+
throw new Error("config.workflow_mode must be 'direct' or 'branch_pr'");
|
|
65
|
+
}
|
|
66
|
+
if (raw.status_commit_policy !== "off" &&
|
|
67
|
+
raw.status_commit_policy !== "warn" &&
|
|
68
|
+
raw.status_commit_policy !== "confirm") {
|
|
69
|
+
throw new Error("config.status_commit_policy must be 'off' | 'warn' | 'confirm'");
|
|
70
|
+
}
|
|
71
|
+
if (typeof raw.finish_auto_status_commit !== "boolean")
|
|
72
|
+
throw new Error("config.finish_auto_status_commit must be boolean");
|
|
73
|
+
if (typeof raw.base_branch !== "string" || raw.base_branch.length === 0)
|
|
74
|
+
throw new Error("config.base_branch must be string");
|
|
75
|
+
if (raw.agents !== undefined) {
|
|
76
|
+
if (!isRecord(raw.agents))
|
|
77
|
+
throw new Error("config.agents must be object");
|
|
78
|
+
if (!isRecord(raw.agents.approvals))
|
|
79
|
+
throw new Error("config.agents.approvals must be object");
|
|
80
|
+
if (typeof raw.agents.approvals.require_plan !== "boolean") {
|
|
81
|
+
throw new Error("config.agents.approvals.require_plan must be boolean");
|
|
82
|
+
}
|
|
83
|
+
if (typeof raw.agents.approvals.require_network !== "boolean") {
|
|
84
|
+
throw new Error("config.agents.approvals.require_network must be boolean");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (raw.recipes !== undefined) {
|
|
88
|
+
if (!isRecord(raw.recipes))
|
|
89
|
+
throw new Error("config.recipes must be object");
|
|
90
|
+
const storageDefault = raw.recipes.storage_default;
|
|
91
|
+
if (storageDefault !== "link" && storageDefault !== "copy" && storageDefault !== "global") {
|
|
92
|
+
throw new Error("config.recipes.storage_default must be 'link' | 'copy' | 'global'");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!isRecord(raw.paths))
|
|
96
|
+
throw new Error("config.paths must be object");
|
|
97
|
+
if (!isRecord(raw.branch))
|
|
98
|
+
throw new Error("config.branch must be object");
|
|
99
|
+
if (!isRecord(raw.framework))
|
|
100
|
+
throw new Error("config.framework must be object");
|
|
101
|
+
if (!isRecord(raw.tasks))
|
|
102
|
+
throw new Error("config.tasks must be object");
|
|
103
|
+
if (!isRecord(raw.commit))
|
|
104
|
+
throw new Error("config.commit must be object");
|
|
105
|
+
if (!isRecord(raw.tasks_backend))
|
|
106
|
+
throw new Error("config.tasks_backend must be object");
|
|
107
|
+
if (typeof raw.closure_commit_requires_approval !== "boolean") {
|
|
108
|
+
throw new Error("config.closure_commit_requires_approval must be boolean");
|
|
109
|
+
}
|
|
110
|
+
// Minimal path fields validation.
|
|
111
|
+
for (const key of ["agents_dir", "tasks_path", "workflow_dir", "worktrees_dir"]) {
|
|
112
|
+
const v = raw.paths[key];
|
|
113
|
+
if (typeof v !== "string" || v.length === 0)
|
|
114
|
+
throw new Error(`config.paths.${key} must be string`);
|
|
115
|
+
}
|
|
116
|
+
if (typeof raw.branch.task_prefix !== "string" || raw.branch.task_prefix.length === 0) {
|
|
117
|
+
throw new Error("config.branch.task_prefix must be string");
|
|
118
|
+
}
|
|
119
|
+
if (typeof raw.framework.source !== "string" || raw.framework.source.length === 0) {
|
|
120
|
+
throw new Error("config.framework.source must be string");
|
|
121
|
+
}
|
|
122
|
+
if (raw.framework.last_update !== null && typeof raw.framework.last_update !== "string") {
|
|
123
|
+
throw new Error("config.framework.last_update must be string or null");
|
|
124
|
+
}
|
|
125
|
+
if (!isRecord(raw.tasks.verify) || !Array.isArray(raw.tasks.verify.required_tags)) {
|
|
126
|
+
throw new Error("config.tasks.verify.required_tags must be array");
|
|
127
|
+
}
|
|
128
|
+
if (typeof raw.tasks.id_suffix_length_default !== "number" ||
|
|
129
|
+
!Number.isInteger(raw.tasks.id_suffix_length_default)) {
|
|
130
|
+
throw new Error("config.tasks.id_suffix_length_default must be integer");
|
|
131
|
+
}
|
|
132
|
+
if (!isRecord(raw.tasks.doc) ||
|
|
133
|
+
!Array.isArray(raw.tasks.doc.sections) ||
|
|
134
|
+
!Array.isArray(raw.tasks.doc.required_sections)) {
|
|
135
|
+
throw new Error("config.tasks.doc.sections and required_sections must be arrays");
|
|
136
|
+
}
|
|
137
|
+
if (!isRecord(raw.tasks.comments))
|
|
138
|
+
throw new Error("config.tasks.comments must be object");
|
|
139
|
+
for (const k of ["start", "blocked", "verified"]) {
|
|
140
|
+
const policy = raw.tasks.comments[k];
|
|
141
|
+
if (!isRecord(policy) ||
|
|
142
|
+
typeof policy.prefix !== "string" ||
|
|
143
|
+
typeof policy.min_chars !== "number") {
|
|
144
|
+
throw new Error(`config.tasks.comments.${k} must have prefix/min_chars`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(raw.commit.generic_tokens))
|
|
148
|
+
throw new Error("config.commit.generic_tokens must be array");
|
|
149
|
+
if (typeof raw.tasks_backend.config_path !== "string" ||
|
|
150
|
+
raw.tasks_backend.config_path.length === 0) {
|
|
151
|
+
throw new Error("config.tasks_backend.config_path must be string");
|
|
152
|
+
}
|
|
153
|
+
// At this point, raw satisfies the minimal contract; keep unknown fields by returning it as-is.
|
|
154
|
+
return raw;
|
|
155
|
+
}
|
|
156
|
+
function toErrnoException(err) {
|
|
157
|
+
if (!err || typeof err !== "object")
|
|
158
|
+
return null;
|
|
159
|
+
if (!("code" in err))
|
|
160
|
+
return null;
|
|
161
|
+
return err;
|
|
162
|
+
}
|
|
163
|
+
export async function loadConfig(agentplaneDir) {
|
|
164
|
+
const filePath = path.join(agentplaneDir, "config.json");
|
|
165
|
+
try {
|
|
166
|
+
const rawText = await readFile(filePath, "utf8");
|
|
167
|
+
const parsed = JSON.parse(rawText);
|
|
168
|
+
const validated = validateConfig(parsed);
|
|
169
|
+
return {
|
|
170
|
+
path: filePath,
|
|
171
|
+
exists: true,
|
|
172
|
+
config: validated,
|
|
173
|
+
raw: parsed,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const errno = toErrnoException(err);
|
|
178
|
+
if (errno?.code === "ENOENT") {
|
|
179
|
+
const def = defaultConfig();
|
|
180
|
+
return {
|
|
181
|
+
path: filePath,
|
|
182
|
+
exists: false,
|
|
183
|
+
config: def,
|
|
184
|
+
raw: def,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function parseScalar(value) {
|
|
191
|
+
const trimmed = value.trim();
|
|
192
|
+
if (trimmed === "true")
|
|
193
|
+
return true;
|
|
194
|
+
if (trimmed === "false")
|
|
195
|
+
return false;
|
|
196
|
+
if (trimmed === "null")
|
|
197
|
+
return null;
|
|
198
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(trimmed);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (/^-?\d+(?:\.\d+)?$/.test(trimmed))
|
|
207
|
+
return Number(trimmed);
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
export function setByDottedKey(obj, dottedKey, value) {
|
|
211
|
+
const parts = dottedKey.split(".").filter(Boolean);
|
|
212
|
+
if (parts.length === 0)
|
|
213
|
+
throw new Error("config key must be non-empty");
|
|
214
|
+
let current = obj;
|
|
215
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
216
|
+
const part = parts[i];
|
|
217
|
+
if (!part)
|
|
218
|
+
continue;
|
|
219
|
+
const next = current[part];
|
|
220
|
+
if (!isRecord(next)) {
|
|
221
|
+
current[part] = {};
|
|
222
|
+
}
|
|
223
|
+
current = current[part];
|
|
224
|
+
}
|
|
225
|
+
const last = parts.at(-1);
|
|
226
|
+
if (!last)
|
|
227
|
+
throw new Error("config key must be non-empty");
|
|
228
|
+
current[last] = parseScalar(value);
|
|
229
|
+
}
|
|
230
|
+
export async function saveConfig(agentplaneDir, raw) {
|
|
231
|
+
const validated = validateConfig(raw);
|
|
232
|
+
await mkdir(agentplaneDir, { recursive: true });
|
|
233
|
+
const filePath = path.join(agentplaneDir, "config.json");
|
|
234
|
+
const text = `${JSON.stringify(raw, null, 2)}\n`;
|
|
235
|
+
await writeFile(filePath, text, "utf8");
|
|
236
|
+
return validated;
|
|
237
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function getStagedFiles(opts: {
|
|
2
|
+
cwd: string;
|
|
3
|
+
rootOverride?: string | null;
|
|
4
|
+
}): Promise<string[]>;
|
|
5
|
+
export declare function getUnstagedFiles(opts: {
|
|
6
|
+
cwd: string;
|
|
7
|
+
rootOverride?: string | null;
|
|
8
|
+
}): Promise<string[]>;
|
|
9
|
+
//# sourceMappingURL=git-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { resolveProject } from "./project-root.js";
|
|
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);
|
|
11
|
+
}
|
|
12
|
+
export async function getStagedFiles(opts) {
|
|
13
|
+
const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
|
|
14
|
+
return await gitLines(resolved.gitRoot, ["diff", "--name-only", "--cached"]);
|
|
15
|
+
}
|
|
16
|
+
export async function getUnstagedFiles(opts) {
|
|
17
|
+
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;
|
|
31
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const CORE_VERSION = "0.0.0";
|
|
2
|
+
export { findGitRoot, resolveProject, type ResolvedProject, type ResolveProjectOptions, } from "./project-root.js";
|
|
3
|
+
export { defaultConfig, loadConfig, saveConfig, setByDottedKey, validateConfig, type AgentplaneConfig, type LoadedConfig, type StatusCommitPolicy, type WorkflowMode, } from "./config.js";
|
|
4
|
+
export { parseTaskReadme, renderTaskFrontmatter, renderTaskReadme, type ParsedTaskReadme, } from "./task-readme.js";
|
|
5
|
+
export { createTask, getTasksDir, listTasks, readTask, setTaskDocSection, taskReadmePath, validateTaskDocMetadata, type TaskFrontmatter, type TaskPriority, type TaskRecord, type TaskStatus, } from "./task-store.js";
|
|
6
|
+
export { buildTasksExportSnapshot, canonicalTasksPayload, canonicalizeJson, computeTasksChecksum, writeTasksExport, type TasksExportMeta, type TasksExportSnapshot, type TasksExportTask, } from "./tasks-export.js";
|
|
7
|
+
export { lintTasksFile, lintTasksSnapshot, readTasksExport, type TasksLintResult, } from "./tasks-lint.js";
|
|
8
|
+
export { getBaseBranch, getPinnedBaseBranch, setPinnedBaseBranch } from "./base-branch.js";
|
|
9
|
+
export { extractTaskSuffix, isGenericSubject, validateCommitSubject, type CommitPolicyResult, } from "./commit-policy.js";
|
|
10
|
+
export { getStagedFiles, getUnstagedFiles } from "./git-utils.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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,mBAAmB,CAAC;AAE3B,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,aAAa,CAAC;AAErB,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAE1B,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,iBAAiB,CAAC;AAEzB,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE3F,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const CORE_VERSION = "0.0.0";
|
|
2
|
+
export { findGitRoot, resolveProject, } from "./project-root.js";
|
|
3
|
+
export { defaultConfig, loadConfig, saveConfig, setByDottedKey, validateConfig, } from "./config.js";
|
|
4
|
+
export { parseTaskReadme, renderTaskFrontmatter, renderTaskReadme, } from "./task-readme.js";
|
|
5
|
+
export { createTask, getTasksDir, listTasks, readTask, setTaskDocSection, taskReadmePath, validateTaskDocMetadata, } from "./task-store.js";
|
|
6
|
+
export { buildTasksExportSnapshot, canonicalTasksPayload, canonicalizeJson, computeTasksChecksum, writeTasksExport, } from "./tasks-export.js";
|
|
7
|
+
export { lintTasksFile, lintTasksSnapshot, readTasksExport, } from "./tasks-lint.js";
|
|
8
|
+
export { getBaseBranch, getPinnedBaseBranch, setPinnedBaseBranch } from "./base-branch.js";
|
|
9
|
+
export { extractTaskSuffix, isGenericSubject, validateCommitSubject, } from "./commit-policy.js";
|
|
10
|
+
export { getStagedFiles, getUnstagedFiles } from "./git-utils.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function findGitRoot(startDir: string): Promise<string | null>;
|
|
2
|
+
export type ResolveProjectOptions = {
|
|
3
|
+
cwd: string;
|
|
4
|
+
rootOverride?: string | null;
|
|
5
|
+
};
|
|
6
|
+
export type ResolvedProject = {
|
|
7
|
+
gitRoot: string;
|
|
8
|
+
agentplaneDir: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function resolveProject(opts: ResolveProjectOptions): Promise<ResolvedProject>;
|
|
11
|
+
//# sourceMappingURL=project-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../src/project-root.ts"],"names":[],"mappings":"AAiBA,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAU1E;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAsB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAU1F"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
async function exists(filePath) {
|
|
4
|
+
try {
|
|
5
|
+
await access(filePath);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function isGitRoot(dir) {
|
|
13
|
+
const dotGit = path.join(dir, ".git");
|
|
14
|
+
return exists(dotGit);
|
|
15
|
+
}
|
|
16
|
+
export async function findGitRoot(startDir) {
|
|
17
|
+
let current = path.resolve(startDir);
|
|
18
|
+
// Walk upwards until filesystem root.
|
|
19
|
+
while (true) {
|
|
20
|
+
if (await isGitRoot(current))
|
|
21
|
+
return current;
|
|
22
|
+
const parent = path.dirname(current);
|
|
23
|
+
if (parent === current)
|
|
24
|
+
return null;
|
|
25
|
+
current = parent;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function resolveProject(opts) {
|
|
29
|
+
const start = opts.rootOverride ? path.resolve(opts.rootOverride) : path.resolve(opts.cwd);
|
|
30
|
+
const gitRoot = await findGitRoot(start);
|
|
31
|
+
if (!gitRoot) {
|
|
32
|
+
throw new Error(`Not a git repository (start: ${start})`);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
gitRoot,
|
|
36
|
+
agentplaneDir: path.join(gitRoot, ".agentplane"),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ParsedTaskReadme = {
|
|
2
|
+
frontmatter: Record<string, unknown>;
|
|
3
|
+
body: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function parseTaskReadme(markdown: string): ParsedTaskReadme;
|
|
6
|
+
export declare function renderTaskFrontmatter(frontmatter: Record<string, unknown>): string;
|
|
7
|
+
export declare function renderTaskReadme(frontmatter: Record<string, unknown>, body: string): string;
|
|
8
|
+
//# sourceMappingURL=task-readme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-readme.d.ts","sourceRoot":"","sources":["../src/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;AAQF,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"}
|