@agentplaneorg/core 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commit/commit-policy.d.ts +1 -1
- package/dist/commit/commit-policy.d.ts.map +1 -1
- package/dist/commit/commit-policy.js +53 -12
- package/dist/git/git-utils.d.ts.map +1 -1
- package/dist/git/git-utils.js +24 -1
- package/dist/project/project-root.d.ts.map +1 -1
- package/dist/project/project-root.js +5 -4
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ export declare function extractTaskSuffix(taskId: string): string;
|
|
|
6
6
|
export declare function isGenericSubject(subject: string, genericTokens: string[]): boolean;
|
|
7
7
|
export declare function validateCommitSubject(opts: {
|
|
8
8
|
subject: string;
|
|
9
|
-
taskId
|
|
9
|
+
taskId?: string;
|
|
10
10
|
genericTokens: string[];
|
|
11
11
|
}): CommitPolicyResult;
|
|
12
12
|
//# sourceMappingURL=commit-policy.d.ts.map
|
|
@@ -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;
|
|
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;AAQF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAOlF;AA8CD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB,GAAG,kBAAkB,CAwErB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const NON_TASK_SUFFIX = "DEV";
|
|
1
2
|
function stripPunctuation(input) {
|
|
2
3
|
return input.replaceAll(/[^\p{L}\p{N}\s-]/gu, " ");
|
|
3
4
|
}
|
|
@@ -15,7 +16,7 @@ export function isGenericSubject(subject, genericTokens) {
|
|
|
15
16
|
const tokenSet = new Set(genericTokens.map((t) => t.toLowerCase()));
|
|
16
17
|
return words.length <= 3 && words.every((w) => tokenSet.has(w));
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function parseTaskSubjectTemplate(subject) {
|
|
19
20
|
const trimmed = subject.trim();
|
|
20
21
|
if (!trimmed)
|
|
21
22
|
return null;
|
|
@@ -36,24 +37,64 @@ function parseSubjectTemplate(subject) {
|
|
|
36
37
|
return null;
|
|
37
38
|
return { emoji, suffix, scope, summary };
|
|
38
39
|
}
|
|
40
|
+
function parseNonTaskSubjectTemplate(subject) {
|
|
41
|
+
const trimmed = subject.trim();
|
|
42
|
+
if (!trimmed)
|
|
43
|
+
return null;
|
|
44
|
+
// Non-task: `<emoji> <scope>: <summary>`
|
|
45
|
+
const match = /^(\S+)\s+([a-z][a-z0-9_-]*):\s+(.+)$/.exec(trimmed);
|
|
46
|
+
if (!match)
|
|
47
|
+
return null;
|
|
48
|
+
const emoji = match[1] ?? "";
|
|
49
|
+
const scope = match[2] ?? "";
|
|
50
|
+
const summary = (match[3] ?? "").trim();
|
|
51
|
+
if (!emoji || !scope || !summary)
|
|
52
|
+
return null;
|
|
53
|
+
return { emoji, scope, summary };
|
|
54
|
+
}
|
|
39
55
|
export function validateCommitSubject(opts) {
|
|
40
56
|
const errors = [];
|
|
41
57
|
const subject = opts.subject.trim();
|
|
42
58
|
if (!subject)
|
|
43
59
|
errors.push("commit subject must be non-empty");
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
const taskId = (opts.taskId ?? "").trim();
|
|
61
|
+
const taskSuffix = taskId ? extractTaskSuffix(taskId) : "";
|
|
62
|
+
if (taskSuffix) {
|
|
63
|
+
const template = parseTaskSubjectTemplate(subject);
|
|
64
|
+
if (!template) {
|
|
65
|
+
errors.push("commit subject must match: <emoji> <suffix> <scope>: <summary>", `example: ✅ ${taskSuffix} close: <summary>`, `example: 🚧 ${taskSuffix} task: <summary>`);
|
|
66
|
+
return { ok: false, errors };
|
|
67
|
+
}
|
|
68
|
+
if (template.suffix.toLowerCase() !== taskSuffix.toLowerCase()) {
|
|
69
|
+
errors.push("commit subject must include the task suffix as the second token");
|
|
70
|
+
}
|
|
52
71
|
}
|
|
53
|
-
else
|
|
54
|
-
|
|
72
|
+
else {
|
|
73
|
+
// Non-task commits: `<emoji> <scope>: <summary>`.
|
|
74
|
+
// We also support the explicit legacy form: `<emoji> DEV <scope>: <summary>`.
|
|
75
|
+
const nonTask = parseNonTaskSubjectTemplate(subject);
|
|
76
|
+
if (!nonTask) {
|
|
77
|
+
const taskLike = parseTaskSubjectTemplate(subject);
|
|
78
|
+
if (taskLike?.suffix?.toLowerCase() === NON_TASK_SUFFIX.toLowerCase()) {
|
|
79
|
+
// Explicit non-task form is allowed.
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (taskLike?.suffix?.toLowerCase() !== NON_TASK_SUFFIX.toLowerCase()) {
|
|
83
|
+
errors.push("task-like commit subject found, but task context is missing (AGENTPLANE_TASK_ID is unset)", "Fix:", " 1) Use the non-task format: <emoji> <scope>: <summary>", " 2) Or run the commit via agentplane so task context is set", "Examples:", " ✨ ci: enforce full tests before push", " 🚧 ABCDEF task: implement upgrade allowlist (via agentplane)");
|
|
84
|
+
return { ok: false, errors };
|
|
85
|
+
}
|
|
86
|
+
errors.push("non-task commit subject must match: <emoji> <scope>: <summary>", "example: ✨ ci: enforce full tests before push", `example (legacy explicit): ✨ ${NON_TASK_SUFFIX} ci: enforce full tests before push`);
|
|
87
|
+
return { ok: false, errors };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
55
90
|
}
|
|
56
|
-
const
|
|
91
|
+
const parsedForSummary = parseNonTaskSubjectTemplate(subject) ??
|
|
92
|
+
(() => {
|
|
93
|
+
const t = parseTaskSubjectTemplate(subject);
|
|
94
|
+
return t ? { summary: t.summary } : null;
|
|
95
|
+
})();
|
|
96
|
+
const summary = parsedForSummary?.summary ?? "";
|
|
97
|
+
const normalizedSummary = stripPunctuation(summary).toLowerCase().trim();
|
|
57
98
|
if (!normalizedSummary) {
|
|
58
99
|
errors.push("commit subject is too generic");
|
|
59
100
|
return { ok: errors.length === 0, errors };
|
|
@@ -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":"AA2CA,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;AAID,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGpB"}
|
package/dist/git/git-utils.js
CHANGED
|
@@ -14,9 +14,32 @@ async function gitNullSeparatedPaths(cwd, args) {
|
|
|
14
14
|
.map((entry) => entry.trim())
|
|
15
15
|
.filter((entry) => entry.length > 0);
|
|
16
16
|
}
|
|
17
|
+
async function gitStagedPathsIncludingRenames(cwd) {
|
|
18
|
+
// `--name-only` collapses renames to the destination path only, which allows
|
|
19
|
+
// protected-path deletions via `git mv`. `--name-status -z` includes both
|
|
20
|
+
// sides of renames/copies.
|
|
21
|
+
const parts = await gitNullSeparatedPaths(cwd, ["diff", "--name-status", "--cached", "-z"]);
|
|
22
|
+
const out = [];
|
|
23
|
+
for (let i = 0; i < parts.length;) {
|
|
24
|
+
const status = parts[i] ?? "";
|
|
25
|
+
const pathA = parts[i + 1] ?? "";
|
|
26
|
+
if (!status || !pathA)
|
|
27
|
+
break;
|
|
28
|
+
out.push(pathA);
|
|
29
|
+
i += 2;
|
|
30
|
+
const code = status[0] ?? "";
|
|
31
|
+
if (code === "R" || code === "C") {
|
|
32
|
+
const pathB = parts[i] ?? "";
|
|
33
|
+
if (pathB)
|
|
34
|
+
out.push(pathB);
|
|
35
|
+
i += 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...new Set(out)].toSorted((a, b) => a.localeCompare(b));
|
|
39
|
+
}
|
|
17
40
|
export async function getStagedFiles(opts) {
|
|
18
41
|
const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
|
|
19
|
-
return await
|
|
42
|
+
return await gitStagedPathsIncludingRenames(resolved.gitRoot);
|
|
20
43
|
}
|
|
21
44
|
export async function getUnstagedFiles(opts) {
|
|
22
45
|
const resolved = await resolveProject({ cwd: opts.cwd, rootOverride: opts.rootOverride ?? null });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../../src/project/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,
|
|
1
|
+
{"version":3,"file":"project-root.d.ts","sourceRoot":"","sources":["../../src/project/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,CAW1F"}
|
|
@@ -27,12 +27,13 @@ export async function findGitRoot(startDir) {
|
|
|
27
27
|
}
|
|
28
28
|
export async function resolveProject(opts) {
|
|
29
29
|
const start = opts.rootOverride ? path.resolve(opts.rootOverride) : path.resolve(opts.cwd);
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// Intentionally do not search parent directories. agentplane is scoped to the
|
|
31
|
+
// explicit rootOverride, or to the current working directory.
|
|
32
|
+
if (!(await isGitRoot(start))) {
|
|
32
33
|
throw new Error(`Not a git repository (start: ${start})`);
|
|
33
34
|
}
|
|
34
35
|
return {
|
|
35
|
-
gitRoot,
|
|
36
|
-
agentplaneDir: path.join(
|
|
36
|
+
gitRoot: start,
|
|
37
|
+
agentplaneDir: path.join(start, ".agentplane"),
|
|
37
38
|
};
|
|
38
39
|
}
|