@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.
@@ -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: string;
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;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"}
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 parseSubjectTemplate(subject) {
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 suffix = extractTaskSuffix(opts.taskId);
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");
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 if (template.suffix.toLowerCase() !== suffix.toLowerCase()) {
54
- errors.push("commit subject must include task suffix as the second token");
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 normalizedSummary = stripPunctuation(template.summary).toLowerCase().trim();
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":"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;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"}
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"}
@@ -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 gitNullSeparatedPaths(resolved.gitRoot, ["diff", "--name-only", "--cached", "-z"]);
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,CAU1F"}
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
- const gitRoot = await findGitRoot(start);
31
- if (!gitRoot) {
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(gitRoot, ".agentplane"),
36
+ gitRoot: start,
37
+ agentplaneDir: path.join(start, ".agentplane"),
37
38
  };
38
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentplaneorg/core",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Core utilities and models for the Agent Plane CLI.",
5
5
  "keywords": [
6
6
  "agentplane",