@autopilot-harness/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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/dist/autopilot-ignore.d.ts +23 -0
  4. package/dist/autopilot-ignore.d.ts.map +1 -0
  5. package/dist/autopilot-ignore.js +271 -0
  6. package/dist/autopilot-ignore.js.map +1 -0
  7. package/dist/checklist-md.d.ts +62 -0
  8. package/dist/checklist-md.d.ts.map +1 -0
  9. package/dist/checklist-md.js +217 -0
  10. package/dist/checklist-md.js.map +1 -0
  11. package/dist/code-edit-detector.d.ts +10 -0
  12. package/dist/code-edit-detector.d.ts.map +1 -0
  13. package/dist/code-edit-detector.js +44 -0
  14. package/dist/code-edit-detector.js.map +1 -0
  15. package/dist/i18n-render.d.ts +3 -0
  16. package/dist/i18n-render.d.ts.map +1 -0
  17. package/dist/i18n-render.js +8 -0
  18. package/dist/i18n-render.js.map +1 -0
  19. package/dist/index.d.ts +20 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +20 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/list-tracks.d.ts +29 -0
  24. package/dist/list-tracks.d.ts.map +1 -0
  25. package/dist/list-tracks.js +232 -0
  26. package/dist/list-tracks.js.map +1 -0
  27. package/dist/migrate.d.ts +19 -0
  28. package/dist/migrate.d.ts.map +1 -0
  29. package/dist/migrate.js +101 -0
  30. package/dist/migrate.js.map +1 -0
  31. package/dist/phase-actions.d.ts +35 -0
  32. package/dist/phase-actions.d.ts.map +1 -0
  33. package/dist/phase-actions.js +480 -0
  34. package/dist/phase-actions.js.map +1 -0
  35. package/dist/project-config.d.ts +29 -0
  36. package/dist/project-config.d.ts.map +1 -0
  37. package/dist/project-config.js +301 -0
  38. package/dist/project-config.js.map +1 -0
  39. package/dist/project-path.d.ts +26 -0
  40. package/dist/project-path.d.ts.map +1 -0
  41. package/dist/project-path.js +104 -0
  42. package/dist/project-path.js.map +1 -0
  43. package/dist/review-engine.d.ts +263 -0
  44. package/dist/review-engine.d.ts.map +1 -0
  45. package/dist/review-engine.js +2273 -0
  46. package/dist/review-engine.js.map +1 -0
  47. package/dist/review-i18n.d.ts +29 -0
  48. package/dist/review-i18n.d.ts.map +1 -0
  49. package/dist/review-i18n.js +54 -0
  50. package/dist/review-i18n.js.map +1 -0
  51. package/dist/review-lenses.d.ts +13 -0
  52. package/dist/review-lenses.d.ts.map +1 -0
  53. package/dist/review-lenses.js +43 -0
  54. package/dist/review-lenses.js.map +1 -0
  55. package/dist/review-runtime.d.ts +12 -0
  56. package/dist/review-runtime.d.ts.map +1 -0
  57. package/dist/review-runtime.js +37 -0
  58. package/dist/review-runtime.js.map +1 -0
  59. package/dist/review-scope.d.ts +9 -0
  60. package/dist/review-scope.d.ts.map +1 -0
  61. package/dist/review-scope.js +92 -0
  62. package/dist/review-scope.js.map +1 -0
  63. package/dist/sqlite.d.ts +15 -0
  64. package/dist/sqlite.d.ts.map +1 -0
  65. package/dist/sqlite.js +38 -0
  66. package/dist/sqlite.js.map +1 -0
  67. package/dist/state-store.d.ts +277 -0
  68. package/dist/state-store.d.ts.map +1 -0
  69. package/dist/state-store.js +1091 -0
  70. package/dist/state-store.js.map +1 -0
  71. package/dist/track-slug.d.ts +4 -0
  72. package/dist/track-slug.d.ts.map +1 -0
  73. package/dist/track-slug.js +18 -0
  74. package/dist/track-slug.js.map +1 -0
  75. package/dist/transcript-followup.d.ts +59 -0
  76. package/dist/transcript-followup.d.ts.map +1 -0
  77. package/dist/transcript-followup.js +310 -0
  78. package/dist/transcript-followup.js.map +1 -0
  79. package/dist/trigger-parser.d.ts +54 -0
  80. package/dist/trigger-parser.d.ts.map +1 -0
  81. package/dist/trigger-parser.js +250 -0
  82. package/dist/trigger-parser.js.map +1 -0
  83. package/dist/verify-report.d.ts +52 -0
  84. package/dist/verify-report.d.ts.map +1 -0
  85. package/dist/verify-report.js +195 -0
  86. package/dist/verify-report.js.map +1 -0
  87. package/package.json +41 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Autopilot Harness contributors
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.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # @autopilot-harness/core
2
+
3
+ Internal library for Autopilot: state store, review engine, checklist, and triggers.
4
+
5
+ Most users should install **[`@autopilot-harness/cli`](https://www.npmjs.com/package/@autopilot-harness/cli)** instead of depending on this package directly.
6
+
7
+ - Repo: [mt2007/autopilot-harness](https://github.com/mt2007/autopilot-harness)
8
+ - License: MIT
@@ -0,0 +1,23 @@
1
+ /** Default ignore rules when `.autopilotignore` is missing (matches init template). */
2
+ export declare const DEFAULT_AUTOPILOT_IGNORE_TEXT = "# Autopilot \u2014 paths that do NOT trigger self-review (gitignore syntax).\n#\n# What this file is:\n# - Controls whether an afterFileEdit counts as \"product code\" (opens fix/confirm).\n# - Does NOT change `git diff` / `git status` output (that is `.gitignore`).\n# - Review followups ask the agent to skip these paths when reading diffs (soft).\n#\n# Semantics:\n# - Same glob rules as gitignore; last matching pattern wins.\n# - Use `!` to force-include an exception (e.g. `!docs/feed/**/*.yml`).\n# - Markdown (*.md / *.mdx) is NOT ignored by default \u2014 design docs can be reviewed.\n# - `docs/**` is NOT ignored by default.\n# - Also skip untracked paths ignored by `.gitignore` (tracked files still count).\n#\n# Later (not implemented): hard-filtered review-diff / path ledger \u2014 see\n# docs/autopilot/workflows/autopilot-executing.md (B2 strong).\n\n# Runtime / editor (prefer also listing these in .gitignore)\n.autopilot/**\n.cursor/**\n\n# Planning artifacts\nplans/**\n\n# Common build / vendor trees\nnode_modules/**\ndist/**\nbuild/**\nout/**\ntarget/**\n.target/**\ncoverage/**\n.venv/**\nvenv/**\n__pycache__/**\n\n# Lockfiles / package manager noise\npackage-lock.json\npnpm-lock.yaml\nyarn.lock\nbun.lock\nbun.lockb\nCargo.lock\npoetry.lock\ncomposer.lock\n\n# Media / binary (do not trigger self-review)\n*.png\n*.jpg\n*.jpeg\n*.gif\n*.webp\n*.ico\n*.svg\n*.bmp\n*.mp3\n*.mp4\n*.wav\n*.webm\n*.mov\n*.woff\n*.woff2\n*.ttf\n*.otf\n*.eot\n*.pdf\n*.zip\n*.gz\n*.tgz\n*.7z\n*.rar\n*.jar\n*.class\n*.o\n*.a\n*.so\n*.dylib\n*.dll\n*.exe\n*.wasm\n\n# Prose / data noise\n*.txt\n*.html\n*.htm\n*.csv\n*.tsv\n*.log\n*.map\n*.min.js\n*.min.css\n";
3
+ export interface AutopilotIgnorePattern {
4
+ negated: boolean;
5
+ /** Regex tested against repo-relative posix path. */
6
+ regex: RegExp;
7
+ }
8
+ /** Parse `.autopilotignore` body into ordered patterns (gitignore semantics). */
9
+ export declare function parseAutopilotIgnore(text: string): AutopilotIgnorePattern[];
10
+ export declare const DEFAULT_AUTOPILOT_IGNORE_PATTERNS: AutopilotIgnorePattern[];
11
+ /**
12
+ * True when the relative path is ignored by autopilotignore rules.
13
+ * Last matching pattern wins; `!` negates ignore.
14
+ */
15
+ export declare function isAutopilotIgnoredPath(relativePath: string, patterns: AutopilotIgnorePattern[]): boolean;
16
+ /** Resolve repo-relative posix path from hook file_path + optional project root. */
17
+ export declare function toProjectRelativePath(filePath: string, projectRoot?: string): string;
18
+ export declare function autopilotIgnorePath(projectRoot: string): string;
19
+ /** Load project `.autopilotignore`, or built-in defaults when missing. */
20
+ export declare function loadAutopilotIgnorePatterns(projectRoot: string): AutopilotIgnorePattern[];
21
+ /** Clear in-memory ignore cache (tests). */
22
+ export declare function clearAutopilotIgnoreCache(): void;
23
+ //# sourceMappingURL=autopilot-ignore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-ignore.d.ts","sourceRoot":"","sources":["../src/autopilot-ignore.ts"],"names":[],"mappings":"AAGA,uFAAuF;AACvF,eAAO,MAAM,6BAA6B,spDA2FzC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;CACf;AA6FD,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,sBAAsB,EAAE,CAS3E;AAED,eAAO,MAAM,iCAAiC,0BAE7C,CAAC;AAMF;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,sBAAsB,EAAE,GACjC,OAAO,CAST;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAcR;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,0EAA0E;AAC1E,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,GAClB,sBAAsB,EAAE,CAkC1B;AAED,4CAA4C;AAC5C,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
@@ -0,0 +1,271 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ /** Default ignore rules when `.autopilotignore` is missing (matches init template). */
4
+ export const DEFAULT_AUTOPILOT_IGNORE_TEXT = `# Autopilot — paths that do NOT trigger self-review (gitignore syntax).
5
+ #
6
+ # What this file is:
7
+ # - Controls whether an afterFileEdit counts as "product code" (opens fix/confirm).
8
+ # - Does NOT change \`git diff\` / \`git status\` output (that is \`.gitignore\`).
9
+ # - Review followups ask the agent to skip these paths when reading diffs (soft).
10
+ #
11
+ # Semantics:
12
+ # - Same glob rules as gitignore; last matching pattern wins.
13
+ # - Use \`!\` to force-include an exception (e.g. \`!docs/feed/**/*.yml\`).
14
+ # - Markdown (*.md / *.mdx) is NOT ignored by default — design docs can be reviewed.
15
+ # - \`docs/**\` is NOT ignored by default.
16
+ # - Also skip untracked paths ignored by \`.gitignore\` (tracked files still count).
17
+ #
18
+ # Later (not implemented): hard-filtered review-diff / path ledger — see
19
+ # docs/autopilot/workflows/autopilot-executing.md (B2 strong).
20
+
21
+ # Runtime / editor (prefer also listing these in .gitignore)
22
+ .autopilot/**
23
+ .cursor/**
24
+
25
+ # Planning artifacts
26
+ plans/**
27
+
28
+ # Common build / vendor trees
29
+ node_modules/**
30
+ dist/**
31
+ build/**
32
+ out/**
33
+ target/**
34
+ .target/**
35
+ coverage/**
36
+ .venv/**
37
+ venv/**
38
+ __pycache__/**
39
+
40
+ # Lockfiles / package manager noise
41
+ package-lock.json
42
+ pnpm-lock.yaml
43
+ yarn.lock
44
+ bun.lock
45
+ bun.lockb
46
+ Cargo.lock
47
+ poetry.lock
48
+ composer.lock
49
+
50
+ # Media / binary (do not trigger self-review)
51
+ *.png
52
+ *.jpg
53
+ *.jpeg
54
+ *.gif
55
+ *.webp
56
+ *.ico
57
+ *.svg
58
+ *.bmp
59
+ *.mp3
60
+ *.mp4
61
+ *.wav
62
+ *.webm
63
+ *.mov
64
+ *.woff
65
+ *.woff2
66
+ *.ttf
67
+ *.otf
68
+ *.eot
69
+ *.pdf
70
+ *.zip
71
+ *.gz
72
+ *.tgz
73
+ *.7z
74
+ *.rar
75
+ *.jar
76
+ *.class
77
+ *.o
78
+ *.a
79
+ *.so
80
+ *.dylib
81
+ *.dll
82
+ *.exe
83
+ *.wasm
84
+
85
+ # Prose / data noise
86
+ *.txt
87
+ *.html
88
+ *.htm
89
+ *.csv
90
+ *.tsv
91
+ *.log
92
+ *.map
93
+ *.min.js
94
+ *.min.css
95
+ `;
96
+ /** Max bytes for `.autopilotignore` (untrusted project file). */
97
+ const MAX_AUTOPILOT_IGNORE_BYTES = 1_000_000;
98
+ /** Per-line cap — untrusted globs compile to RegExp on the edit hot path. */
99
+ const MAX_AUTOPILOT_IGNORE_LINE_CHARS = 4_096;
100
+ /** Cap compiled patterns to bound match cost per afterFileEdit. */
101
+ const MAX_AUTOPILOT_IGNORE_PATTERNS = 10_000;
102
+ const ignoreCache = new Map();
103
+ function escapeRegexChar(ch) {
104
+ return ch.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
105
+ }
106
+ /** Convert a gitignore-style glob segment to regex (no anchoring). */
107
+ function globBodyToRegex(glob) {
108
+ let out = "";
109
+ for (let i = 0; i < glob.length;) {
110
+ const ch = glob[i];
111
+ if (ch === "*" && glob[i + 1] === "*") {
112
+ if (glob[i + 2] === "/") {
113
+ out += "(?:.*/)?";
114
+ i += 3;
115
+ continue;
116
+ }
117
+ out += ".*";
118
+ i += 2;
119
+ continue;
120
+ }
121
+ if (ch === "*") {
122
+ out += "[^/]*";
123
+ i += 1;
124
+ continue;
125
+ }
126
+ if (ch === "?") {
127
+ out += "[^/]";
128
+ i += 1;
129
+ continue;
130
+ }
131
+ out += escapeRegexChar(ch);
132
+ i += 1;
133
+ }
134
+ return out;
135
+ }
136
+ function compilePattern(raw) {
137
+ let line = raw.trim();
138
+ if (!line || line.startsWith("#"))
139
+ return null;
140
+ if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS)
141
+ return null;
142
+ let negated = false;
143
+ if (line.startsWith("!")) {
144
+ negated = true;
145
+ line = line.slice(1).trim();
146
+ if (!line || line.startsWith("#"))
147
+ return null;
148
+ }
149
+ const dirOnly = line.endsWith("/");
150
+ if (dirOnly)
151
+ line = line.slice(0, -1);
152
+ if (!line)
153
+ return null;
154
+ if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS)
155
+ return null;
156
+ let anchored = false;
157
+ if (line.startsWith("/")) {
158
+ anchored = true;
159
+ line = line.slice(1);
160
+ }
161
+ const body = globBodyToRegex(line);
162
+ let regexSource;
163
+ if (anchored) {
164
+ regexSource = `^${body}`;
165
+ if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
166
+ regexSource += "(?:/.*)?";
167
+ }
168
+ regexSource += "$";
169
+ }
170
+ else if (line.includes("/")) {
171
+ regexSource = `(?:^|.*/)${body}`;
172
+ if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
173
+ regexSource += "(?:/.*)?";
174
+ }
175
+ regexSource += "$";
176
+ }
177
+ else {
178
+ regexSource = `(?:^|.*/)?${body}$`;
179
+ }
180
+ return { negated, regex: new RegExp(regexSource) };
181
+ }
182
+ /** Parse `.autopilotignore` body into ordered patterns (gitignore semantics). */
183
+ export function parseAutopilotIgnore(text) {
184
+ const patterns = [];
185
+ for (const rawLine of text.split(/\r?\n/)) {
186
+ const compiled = compilePattern(rawLine);
187
+ if (!compiled)
188
+ continue;
189
+ patterns.push(compiled);
190
+ if (patterns.length >= MAX_AUTOPILOT_IGNORE_PATTERNS)
191
+ break;
192
+ }
193
+ return patterns;
194
+ }
195
+ export const DEFAULT_AUTOPILOT_IGNORE_PATTERNS = parseAutopilotIgnore(DEFAULT_AUTOPILOT_IGNORE_TEXT);
196
+ function normalizeRelativePath(filePath) {
197
+ return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
198
+ }
199
+ /**
200
+ * True when the relative path is ignored by autopilotignore rules.
201
+ * Last matching pattern wins; `!` negates ignore.
202
+ */
203
+ export function isAutopilotIgnoredPath(relativePath, patterns) {
204
+ const norm = normalizeRelativePath(relativePath);
205
+ let ignored = false;
206
+ for (const pat of patterns) {
207
+ if (pat.regex.test(norm)) {
208
+ ignored = !pat.negated;
209
+ }
210
+ }
211
+ return ignored;
212
+ }
213
+ /** Resolve repo-relative posix path from hook file_path + optional project root. */
214
+ export function toProjectRelativePath(filePath, projectRoot) {
215
+ const posix = filePath.replace(/\\/g, "/");
216
+ if (!projectRoot?.trim()) {
217
+ return normalizeRelativePath(posix);
218
+ }
219
+ const root = path.resolve(projectRoot);
220
+ const abs = path.isAbsolute(posix)
221
+ ? path.resolve(posix)
222
+ : path.resolve(root, posix);
223
+ const rel = path.relative(root, abs);
224
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
225
+ return "";
226
+ }
227
+ return normalizeRelativePath(rel.replace(/\\/g, "/"));
228
+ }
229
+ export function autopilotIgnorePath(projectRoot) {
230
+ return path.join(path.resolve(projectRoot), ".autopilotignore");
231
+ }
232
+ /** Load project `.autopilotignore`, or built-in defaults when missing. */
233
+ export function loadAutopilotIgnorePatterns(projectRoot) {
234
+ const root = path.resolve(projectRoot);
235
+ const filePath = path.join(root, ".autopilotignore");
236
+ let st;
237
+ try {
238
+ st = fs.lstatSync(filePath);
239
+ }
240
+ catch {
241
+ return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
242
+ }
243
+ if (st.isSymbolicLink() || !st.isFile()) {
244
+ return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
245
+ }
246
+ if (st.size > MAX_AUTOPILOT_IGNORE_BYTES) {
247
+ return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
248
+ }
249
+ const cached = ignoreCache.get(root);
250
+ if (cached && cached.mtimeMs === st.mtimeMs) {
251
+ return cached.patterns;
252
+ }
253
+ let text;
254
+ try {
255
+ text = fs.readFileSync(filePath, "utf8");
256
+ }
257
+ catch {
258
+ return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
259
+ }
260
+ if (text.length > MAX_AUTOPILOT_IGNORE_BYTES) {
261
+ return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
262
+ }
263
+ const patterns = parseAutopilotIgnore(text);
264
+ ignoreCache.set(root, { mtimeMs: st.mtimeMs, patterns });
265
+ return patterns;
266
+ }
267
+ /** Clear in-memory ignore cache (tests). */
268
+ export function clearAutopilotIgnoreCache() {
269
+ ignoreCache.clear();
270
+ }
271
+ //# sourceMappingURL=autopilot-ignore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-ignore.js","sourceRoot":"","sources":["../src/autopilot-ignore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ export interface ChecklistItem {
2
+ id: string;
3
+ title: string;
4
+ checked: boolean;
5
+ line: string;
6
+ lineNumber: number;
7
+ idFromSeparator: boolean;
8
+ }
9
+ export interface ChecklistMd {
10
+ path: string;
11
+ items: ChecklistItem[];
12
+ }
13
+ /** Hard cap — stop-hook / track listing must not OOM on hostile checklist paths. */
14
+ export declare const MAX_CHECKLIST_BYTES = 1048576;
15
+ export declare function slugify(text: string): string;
16
+ /** Parse checklist markdown text (no FS). */
17
+ export declare function parseChecklistMarkdown(content: string, checklistPath: string): ChecklistMd;
18
+ /**
19
+ * Read + parse a checklist file.
20
+ * Refuses NUL paths, symlinks (O_NOFOLLOW / lstat), non-files, and oversized bodies.
21
+ * When projectRoot is set, re-checks realpath containment after open (same TOCTOU
22
+ * class as readVerifyReport: intermediate dir symlink escape).
23
+ */
24
+ export declare function parseChecklist(checklistPath: string, opts?: {
25
+ projectRoot?: string;
26
+ }): ChecklistMd;
27
+ export declare function countUnchecked(checklist: ChecklistMd): number;
28
+ export declare function firstUnchecked(checklist: ChecklistMd): ChecklistItem | null;
29
+ /** Second unchecked item — the one to implement after marking current [x]. */
30
+ export declare function secondUnchecked(checklist: ChecklistMd): ChecklistItem | null;
31
+ /**
32
+ * First unchecked item that appears *after* `afterItemId` in checklist order.
33
+ * Used when advance must follow the item under review (not bare secondUnchecked),
34
+ * so a premature `[x]` on the reviewing item cannot skip the true next row.
35
+ */
36
+ export declare function uncheckedAfter(checklist: ChecklistMd, afterItemId: string): ChecklistItem | null;
37
+ /**
38
+ * Sticky reviewing id is usable only when the row exists and every earlier
39
+ * checklist item is already checked. Otherwise advance may have seeded the
40
+ * *next* id before the agent checked off the completed current item — honoring
41
+ * sticky then would skip the still-open predecessor.
42
+ */
43
+ export declare function effectiveReviewingItemId(checklist: ChecklistMd, reviewingItemId?: string | null): string | null;
44
+ /**
45
+ * Resolve which item advance/done should mark, and which to implement next.
46
+ * When `reviewingItemId` is set (sticky from first product edit), prefer it over
47
+ * firstUnchecked so premature checklist checks cannot rename "current".
48
+ */
49
+ export declare function resolveAdvanceTargets(checklist: ChecklistMd, reviewingItemId?: string | null): {
50
+ current: ChecklistItem | null;
51
+ next: ChecklistItem | null;
52
+ unchecked: number;
53
+ };
54
+ /**
55
+ * Extract the "implement next" item id from an Advance followup body.
56
+ * Used when arming sticky reviewing_item_id so a premature `[x]` before the
57
+ * first product edit cannot retarget via firstUnchecked.
58
+ */
59
+ export declare function parseAdvanceNextItemId(pendingFollowup: string | null | undefined): string | null;
60
+ export declare function isLastUnchecked(checklist: ChecklistMd): boolean;
61
+ export declare function itemsMissingSeparatorId(checklist: ChecklistMd): ChecklistItem[];
62
+ //# sourceMappingURL=checklist-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checklist-md.d.ts","sourceRoot":"","sources":["../src/checklist-md.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,UAAY,CAAC;AAO7C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C;AA8BD,6CAA6C;AAC7C,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,WAAW,CAQb;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,WAAW,CAsDb;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,GAAG,MAAM,CAE7D;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,GAAG,aAAa,GAAG,IAAI,CAE3E;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,GAAG,aAAa,GAAG,IAAI,CAQ5E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,WAAW,EACtB,WAAW,EAAE,MAAM,GAClB,aAAa,GAAG,IAAI,CAYtB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,WAAW,EACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B,MAAM,GAAG,IAAI,CASf;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,WAAW,EACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B;IACD,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB,CAkBA;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,eAAe,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACzC,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,WAAW,GAAG,OAAO,CAE/D;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,WAAW,GAAG,aAAa,EAAE,CAE/E"}
@@ -0,0 +1,217 @@
1
+ import fs from "node:fs";
2
+ import { isRealpathInsideProject, normalizeProjectRoot } from "./project-path.js";
3
+ /** Hard cap — stop-hook / track listing must not OOM on hostile checklist paths. */
4
+ export const MAX_CHECKLIST_BYTES = 1_048_576;
5
+ const ITEM_RE = /^-\s*\[([ xX])\]\s*(.+)$/;
6
+ /** Prefer em/en dash; ASCII hyphen only when surrounded by spaces. */
7
+ const SEPARATOR_RE = /^(.+?)\s*(?:[—–]| - )\s*(.+)$/;
8
+ const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
9
+ export function slugify(text) {
10
+ return text
11
+ .trim()
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, "-")
14
+ .replace(/^-+|-+$/g, "")
15
+ .slice(0, 80);
16
+ }
17
+ function parseItemLine(line, lineNumber) {
18
+ const m = line.match(ITEM_RE);
19
+ if (!m)
20
+ return null;
21
+ const checked = m[1].toLowerCase() === "x";
22
+ const body = m[2].trim();
23
+ const sep = body.match(SEPARATOR_RE);
24
+ if (sep) {
25
+ const id = sep[1].trim();
26
+ const title = sep[2].trim();
27
+ return {
28
+ id: KEBAB_RE.test(id) ? id : slugify(id),
29
+ title,
30
+ checked,
31
+ line,
32
+ lineNumber,
33
+ idFromSeparator: true,
34
+ };
35
+ }
36
+ return {
37
+ id: slugify(body),
38
+ title: body,
39
+ checked,
40
+ line,
41
+ lineNumber,
42
+ idFromSeparator: false,
43
+ };
44
+ }
45
+ /** Parse checklist markdown text (no FS). */
46
+ export function parseChecklistMarkdown(content, checklistPath) {
47
+ const lines = content.split(/\r?\n/);
48
+ const items = [];
49
+ for (let i = 0; i < lines.length; i++) {
50
+ const item = parseItemLine(lines[i], i + 1);
51
+ if (item)
52
+ items.push(item);
53
+ }
54
+ return { path: checklistPath, items };
55
+ }
56
+ /**
57
+ * Read + parse a checklist file.
58
+ * Refuses NUL paths, symlinks (O_NOFOLLOW / lstat), non-files, and oversized bodies.
59
+ * When projectRoot is set, re-checks realpath containment after open (same TOCTOU
60
+ * class as readVerifyReport: intermediate dir symlink escape).
61
+ */
62
+ export function parseChecklist(checklistPath, opts) {
63
+ if (!checklistPath || checklistPath.includes("\0")) {
64
+ throw new Error("Invalid checklist path");
65
+ }
66
+ const projectRoot = opts?.projectRoot;
67
+ if (projectRoot !== undefined && projectRoot !== null) {
68
+ if (typeof projectRoot !== "string" ||
69
+ !normalizeProjectRoot(projectRoot)) {
70
+ throw new Error("Invalid project root");
71
+ }
72
+ }
73
+ const root = typeof projectRoot === "string"
74
+ ? normalizeProjectRoot(projectRoot) ?? undefined
75
+ : undefined;
76
+ const nofollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
77
+ // Platforms without O_NOFOLLOW: refuse symlinks before open (still a small TOCTOU window).
78
+ if (nofollow === 0) {
79
+ const st = fs.lstatSync(checklistPath);
80
+ if (st.isSymbolicLink() || !st.isFile()) {
81
+ throw new Error("Checklist must be a regular file");
82
+ }
83
+ }
84
+ const fd = fs.openSync(checklistPath, fs.constants.O_RDONLY | nofollow);
85
+ try {
86
+ const st = fs.fstatSync(fd);
87
+ if (!st.isFile() || st.size > MAX_CHECKLIST_BYTES) {
88
+ throw new Error("Checklist unreadable or too large");
89
+ }
90
+ // Bind fd to the path's current identity. Always — not only when O_NOFOLLOW
91
+ // is missing (same intermediate-dir swap-back TOCTOU as verify-report).
92
+ const lst = fs.lstatSync(checklistPath);
93
+ if (lst.isSymbolicLink() || !lst.isFile()) {
94
+ throw new Error("Checklist must be a regular file");
95
+ }
96
+ if (lst.ino !== st.ino || lst.dev !== st.dev) {
97
+ throw new Error("Checklist path changed during open");
98
+ }
99
+ if (root && !isRealpathInsideProject(root, checklistPath)) {
100
+ throw new Error("Checklist outside project");
101
+ }
102
+ const buf = Buffer.alloc(st.size);
103
+ const n = fs.readSync(fd, buf, 0, st.size, 0);
104
+ const content = buf.subarray(0, n).toString("utf8");
105
+ if (Buffer.byteLength(content, "utf8") > MAX_CHECKLIST_BYTES) {
106
+ throw new Error("Checklist too large");
107
+ }
108
+ return parseChecklistMarkdown(content, checklistPath);
109
+ }
110
+ finally {
111
+ fs.closeSync(fd);
112
+ }
113
+ }
114
+ export function countUnchecked(checklist) {
115
+ return checklist.items.filter((i) => !i.checked).length;
116
+ }
117
+ export function firstUnchecked(checklist) {
118
+ return checklist.items.find((i) => !i.checked) ?? null;
119
+ }
120
+ /** Second unchecked item — the one to implement after marking current [x]. */
121
+ export function secondUnchecked(checklist) {
122
+ let seen = 0;
123
+ for (const item of checklist.items) {
124
+ if (item.checked)
125
+ continue;
126
+ seen += 1;
127
+ if (seen === 2)
128
+ return item;
129
+ }
130
+ return null;
131
+ }
132
+ /**
133
+ * First unchecked item that appears *after* `afterItemId` in checklist order.
134
+ * Used when advance must follow the item under review (not bare secondUnchecked),
135
+ * so a premature `[x]` on the reviewing item cannot skip the true next row.
136
+ */
137
+ export function uncheckedAfter(checklist, afterItemId) {
138
+ const id = afterItemId.trim();
139
+ if (!id)
140
+ return null;
141
+ let seen = false;
142
+ for (const item of checklist.items) {
143
+ if (!seen) {
144
+ if (item.id === id)
145
+ seen = true;
146
+ continue;
147
+ }
148
+ if (!item.checked)
149
+ return item;
150
+ }
151
+ return null;
152
+ }
153
+ /**
154
+ * Sticky reviewing id is usable only when the row exists and every earlier
155
+ * checklist item is already checked. Otherwise advance may have seeded the
156
+ * *next* id before the agent checked off the completed current item — honoring
157
+ * sticky then would skip the still-open predecessor.
158
+ */
159
+ export function effectiveReviewingItemId(checklist, reviewingItemId) {
160
+ const rid = (reviewingItemId ?? "").trim();
161
+ if (!rid)
162
+ return null;
163
+ const idx = checklist.items.findIndex((i) => i.id === rid);
164
+ if (idx < 0)
165
+ return null;
166
+ for (let i = 0; i < idx; i++) {
167
+ if (!checklist.items[i].checked)
168
+ return null;
169
+ }
170
+ return rid;
171
+ }
172
+ /**
173
+ * Resolve which item advance/done should mark, and which to implement next.
174
+ * When `reviewingItemId` is set (sticky from first product edit), prefer it over
175
+ * firstUnchecked so premature checklist checks cannot rename "current".
176
+ */
177
+ export function resolveAdvanceTargets(checklist, reviewingItemId) {
178
+ const unchecked = countUnchecked(checklist);
179
+ const rid = effectiveReviewingItemId(checklist, reviewingItemId);
180
+ if (rid) {
181
+ const current = checklist.items.find((i) => i.id === rid) ?? null;
182
+ if (current) {
183
+ return {
184
+ current,
185
+ next: uncheckedAfter(checklist, rid),
186
+ unchecked,
187
+ };
188
+ }
189
+ }
190
+ return {
191
+ current: firstUnchecked(checklist),
192
+ next: secondUnchecked(checklist),
193
+ unchecked,
194
+ };
195
+ }
196
+ /**
197
+ * Extract the "implement next" item id from an Advance followup body.
198
+ * Used when arming sticky reviewing_item_id so a premature `[x]` before the
199
+ * first product edit cannot retarget via firstUnchecked.
200
+ */
201
+ export function parseAdvanceNextItemId(pendingFollowup) {
202
+ const text = (pendingFollowup ?? "").trim();
203
+ if (!text || text.includes("\0"))
204
+ return null;
205
+ const m = text.match(/(?:Then implement next|然后实现下一项)\s*[::]\s*([A-Za-z0-9][\w.-]*)/);
206
+ const id = m?.[1]?.trim() ?? "";
207
+ if (!id || id.includes("\0"))
208
+ return null;
209
+ return id;
210
+ }
211
+ export function isLastUnchecked(checklist) {
212
+ return countUnchecked(checklist) === 1;
213
+ }
214
+ export function itemsMissingSeparatorId(checklist) {
215
+ return checklist.items.filter((i) => !i.idFromSeparator);
216
+ }
217
+ //# sourceMappingURL=checklist-md.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checklist-md.js","sourceRoot":"","sources":["../src/checklist-md.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface ProductCodeEditOptions {
2
+ /** Project root — loads `.autopilotignore` and runs `git check-ignore`. */
3
+ projectRoot?: string;
4
+ }
5
+ /**
6
+ * Returns true if the edited path counts as product code (triggers fix review).
7
+ * No extension allowlist — exclusions live in `.autopilotignore` (+ gitignore A).
8
+ */
9
+ export declare function isProductCodeEdit(filePath: string, opts?: ProductCodeEditOptions): boolean;
10
+ //# sourceMappingURL=code-edit-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-edit-detector.d.ts","sourceRoot":"","sources":["../src/code-edit-detector.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA+BD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAcT"}