iterate-plugin 3.5.6 → 3.6.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/README.md +64 -20
- package/README.zh-CN.md +53 -17
- package/dist/approval-gate.js +143 -23
- package/dist/atomic-fs.js +148 -17
- package/dist/config-loader.js +509 -34
- package/dist/config-write.js +83 -22
- package/dist/evidence.js +141 -87
- package/dist/file-lock.js +165 -0
- package/dist/index.js +54 -28
- package/dist/jobs.js +56 -11
- package/dist/live.js +52 -13
- package/dist/meta-review.js +132 -28
- package/dist/method-scope.js +31 -8
- package/dist/review-scope.js +32 -9
- package/dist/review.js +96 -17
- package/dist/session-hooks.js +55 -13
- package/dist/skill-prompt.js +417 -67
- package/dist/tools/checkpoint.js +85 -19
- package/dist/tools/config.js +44 -2
- package/dist/tools/context.js +22 -5
- package/dist/tools/decision-log.js +122 -27
- package/dist/tools/defense-events.js +90 -31
- package/dist/tools/defense-store.js +28 -3
- package/dist/tools/experience-bank.js +108 -43
- package/dist/tools/experience-store.js +54 -4
- package/dist/tools/fix.js +347 -119
- package/dist/tools/history.js +68 -14
- package/dist/tools/present.js +80 -0
- package/dist/tools/prune.js +156 -53
- package/dist/tools/quality-gate.js +52 -2
- package/dist/tools/quality-store.js +17 -1
- package/dist/tools/review.js +158 -7
- package/dist/tools/transcript.js +173 -36
- package/dist/tools/triage.js +67 -12
- package/dist/tools/validate.js +115 -24
- package/dist/transcript.js +267 -4
- package/lib/client.js +945 -181
- package/lib/parse.js +760 -57
- package/package.json +15 -12
- package/src/approval-gate.ts +160 -23
- package/src/atomic-fs.ts +152 -17
- package/src/client/index.ts +600 -129
- package/src/config-loader.ts +542 -35
- package/src/config-write.ts +108 -26
- package/src/evidence.ts +194 -92
- package/src/file-lock.ts +184 -0
- package/src/index.ts +53 -29
- package/src/jobs.ts +63 -12
- package/src/live.ts +48 -11
- package/src/meta-review.ts +151 -34
- package/src/method-scope.ts +31 -8
- package/src/review-scope.ts +33 -10
- package/src/review.ts +96 -17
- package/src/session-hooks.ts +54 -12
- package/src/skill-prompt.ts +417 -67
- package/src/tools/checkpoint.ts +81 -19
- package/src/tools/config.ts +44 -2
- package/src/tools/context.ts +25 -5
- package/src/tools/decision-log.ts +127 -22
- package/src/tools/defense-events.ts +87 -31
- package/src/tools/defense-store.ts +30 -3
- package/src/tools/experience-bank.ts +112 -44
- package/src/tools/experience-store.ts +54 -5
- package/src/tools/fix.ts +258 -20
- package/src/tools/history.ts +74 -15
- package/src/tools/present.ts +89 -0
- package/src/tools/prune.ts +160 -51
- package/src/tools/quality-gate.ts +57 -2
- package/src/tools/quality-store.ts +18 -1
- package/src/tools/review.ts +155 -6
- package/src/tools/transcript.ts +178 -36
- package/src/tools/triage.ts +70 -13
- package/src/tools/validate.ts +138 -32
- package/src/transcript.ts +279 -4
- package/src/types.ts +29 -1
package/dist/atomic-fs.js
CHANGED
|
@@ -4,15 +4,24 @@
|
|
|
4
4
|
* Every JSON/YAML artifact the plugin persists (.iterate/*.json,
|
|
5
5
|
* iterate.config.yaml) is written through here: content goes to a
|
|
6
6
|
* uniquely-named temp file in the SAME directory as the target (rename is
|
|
7
|
-
* only atomic within one filesystem),
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* only atomic within one filesystem), is fsynced to plating, then is
|
|
8
|
+
* `renameSync`ed over the destination, followed by a best-effort directory
|
|
9
|
+
* fsync so the rename itself is durable. A crash mid-write can therefore
|
|
10
|
+
* leave a stale temp file behind, but never a truncated / half-written state
|
|
11
|
+
* file — readers (which all tolerate missing/corrupt files) see either the
|
|
12
|
+
* previous or the new content, never a mixture.
|
|
13
|
+
*
|
|
14
|
+
* Two properties travel with the rename (which REPLACES the target inode):
|
|
15
|
+
* - permissions: the temp is given the existing target's mode before the
|
|
16
|
+
* swap, so rewriting a 0755 script stays 0755 and a 0600 secret never
|
|
17
|
+
* widens to the umask default (0644).
|
|
18
|
+
* - symlinks: when the target is a symlink, the atomic replace runs on the
|
|
19
|
+
* link's REAL target instead — a fix written through a linked source file
|
|
20
|
+
* must reach the real file, and the link must survive as a link.
|
|
12
21
|
*/
|
|
13
|
-
import { dirname, join } from 'node:path';
|
|
14
|
-
import { renameSync, rmSync,
|
|
15
|
-
import { rename, rm,
|
|
22
|
+
import { basename, dirname, join } from 'node:path';
|
|
23
|
+
import { closeSync, fchmodSync, fsyncSync, lstatSync, openSync, realpathSync, renameSync, rmSync, statSync, writeSync, } from 'node:fs';
|
|
24
|
+
import { lstat, open, realpath, rename, rm, stat } from 'node:fs/promises';
|
|
16
25
|
/** Best-effort unlink of a leftover temp file (never throws). */
|
|
17
26
|
function cleanupTemp(tmp) {
|
|
18
27
|
try {
|
|
@@ -35,21 +44,128 @@ async function cleanupTempAsync(tmp) {
|
|
|
35
44
|
* Unique temp file path for `filePath`, in the same directory, following the
|
|
36
45
|
* prunable `.<basename>.tmp-<pid>-<random>` convention (see prune.ts
|
|
37
46
|
* `isPrunableTemp`). The pid+random suffix makes concurrent writers safe.
|
|
47
|
+
* `basename` from node:path handles the platform separator (a hand-rolled
|
|
48
|
+
* `split('/')` breaks on Windows paths).
|
|
38
49
|
*/
|
|
39
50
|
function tempPathFor(filePath) {
|
|
40
|
-
return join(dirname(filePath), `.${filePath
|
|
51
|
+
return join(dirname(filePath), `.${basename(filePath)}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 8)}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the path the atomic replace must actually run on. A symlinked
|
|
55
|
+
* target is resolved to its real path: `renameSync(tmp, link)` would swap the
|
|
56
|
+
* LINK for a regular file and the linked-to file would silently keep its old
|
|
57
|
+
* content (a fix that never reaches the real source). When lstat/realpath
|
|
58
|
+
* cannot resolve (missing target, dangling or unreadable link), fall back to
|
|
59
|
+
* writing `filePath` itself — the pre-symlink-aware behavior.
|
|
60
|
+
*/
|
|
61
|
+
function resolveAtomicTarget(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
if (lstatSync(filePath).isSymbolicLink())
|
|
64
|
+
return realpathSync(filePath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Missing target or unresolvable link — write `filePath` itself.
|
|
68
|
+
}
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
71
|
+
/** Async variant of {@link resolveAtomicTarget}. */
|
|
72
|
+
async function resolveAtomicTargetAsync(filePath) {
|
|
73
|
+
try {
|
|
74
|
+
if ((await lstat(filePath)).isSymbolicLink())
|
|
75
|
+
return await realpath(filePath);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Missing target or unresolvable link — write `filePath` itself.
|
|
79
|
+
}
|
|
80
|
+
return filePath;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The permission bits to give the temp file: the existing target's mode (so
|
|
84
|
+
* the rename-over swap preserves it), or `null` for a brand-new file — then
|
|
85
|
+
* the default open mode (0o666 & ~umask) applies, exactly like a plain
|
|
86
|
+
* `writeFileSync` of a new file.
|
|
87
|
+
*/
|
|
88
|
+
function existingMode(target) {
|
|
89
|
+
try {
|
|
90
|
+
return statSync(target).mode & 0o777;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** Async variant of {@link existingMode}. */
|
|
97
|
+
async function existingModeAsync(target) {
|
|
98
|
+
try {
|
|
99
|
+
return (await stat(target)).mode & 0o777;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Best-effort fsync of the directory holding the final file, so the rename
|
|
107
|
+
* itself survives a crash. Unsupported on some platforms/filesystems — the
|
|
108
|
+
* data is already durable (fsynced on the temp), this only covers the
|
|
109
|
+
* directory entry, so failure is ignored by design.
|
|
110
|
+
*/
|
|
111
|
+
function fsyncDirBestEffort(dir) {
|
|
112
|
+
try {
|
|
113
|
+
const fd = openSync(dir, 'r');
|
|
114
|
+
try {
|
|
115
|
+
fsyncSync(fd);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
closeSync(fd);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Directory fsync is unavailable (Windows, some FUSE mounts) — durability
|
|
123
|
+
// of the content does not depend on it.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Async variant of {@link fsyncDirBestEffort}. */
|
|
127
|
+
async function fsyncDirBestEffortAsync(dir) {
|
|
128
|
+
let dh;
|
|
129
|
+
try {
|
|
130
|
+
dh = await open(dir, 'r');
|
|
131
|
+
await dh.sync();
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Directory fsync is unavailable — see fsyncDirBestEffort.
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
if (dh) {
|
|
138
|
+
await dh.close().catch(() => { });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
41
141
|
}
|
|
42
142
|
/**
|
|
43
|
-
* Write `data` to `filePath` atomically (temp file + rename, same dir).
|
|
143
|
+
* Write `data` to `filePath` atomically (temp file + fsync + rename, same dir).
|
|
44
144
|
* Temp files follow the `<name>.tmp-<pid>-<random>` convention also used by
|
|
45
|
-
* prune.ts / checkpoint.ts so they are recognizable and prunable.
|
|
145
|
+
* prune.ts / checkpoint.ts so they are recognizable and prunable. The existing
|
|
146
|
+
* target's mode is preserved across the swap and symlinked targets are
|
|
147
|
+
* resolved first (see the header).
|
|
46
148
|
* Throws on failure (callers own error reporting — they map failures to
|
|
47
149
|
* structured tool results).
|
|
48
150
|
*/
|
|
49
151
|
export function writeTextAtomic(filePath, data) {
|
|
50
|
-
const
|
|
152
|
+
const target = resolveAtomicTarget(filePath);
|
|
153
|
+
const tmp = tempPathFor(target);
|
|
154
|
+
const mode = existingMode(target);
|
|
51
155
|
try {
|
|
52
|
-
|
|
156
|
+
// fd-based write so the mode can be pinned and the CONTENT fsynced before
|
|
157
|
+
// the rename — a crash after rename but before the data hit the disk would
|
|
158
|
+
// otherwise leave a zero-length / torn state file.
|
|
159
|
+
const fd = openSync(tmp, 'w');
|
|
160
|
+
try {
|
|
161
|
+
writeSync(fd, data, null, 'utf-8');
|
|
162
|
+
if (mode !== null)
|
|
163
|
+
fchmodSync(fd, mode);
|
|
164
|
+
fsyncSync(fd);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
closeSync(fd);
|
|
168
|
+
}
|
|
53
169
|
}
|
|
54
170
|
catch (err) {
|
|
55
171
|
// The write itself failed (disk full, permission, …): the temp file may
|
|
@@ -59,19 +175,33 @@ export function writeTextAtomic(filePath, data) {
|
|
|
59
175
|
throw err;
|
|
60
176
|
}
|
|
61
177
|
try {
|
|
62
|
-
renameSync(tmp,
|
|
178
|
+
renameSync(tmp, target);
|
|
63
179
|
}
|
|
64
180
|
catch (err) {
|
|
65
181
|
// Best-effort temp cleanup so a failed rename never litters the directory.
|
|
66
182
|
cleanupTemp(tmp);
|
|
67
183
|
throw err;
|
|
68
184
|
}
|
|
185
|
+
fsyncDirBestEffort(dirname(target));
|
|
69
186
|
}
|
|
70
187
|
/** Async variant of {@link writeTextAtomic} (same temp naming convention). */
|
|
71
188
|
export async function writeTextAtomicAsync(filePath, data) {
|
|
72
|
-
const
|
|
189
|
+
const target = await resolveAtomicTargetAsync(filePath);
|
|
190
|
+
const tmp = tempPathFor(target);
|
|
191
|
+
const mode = await existingModeAsync(target);
|
|
73
192
|
try {
|
|
74
|
-
|
|
193
|
+
// fd-based write: pin the target's mode and fsync the content before the
|
|
194
|
+
// rename (mirrors the sync variant — see writeTextAtomic).
|
|
195
|
+
const fh = await open(tmp, 'w');
|
|
196
|
+
try {
|
|
197
|
+
await fh.writeFile(data, 'utf-8');
|
|
198
|
+
if (mode !== null)
|
|
199
|
+
await fh.chmod(mode);
|
|
200
|
+
await fh.sync();
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
await fh.close();
|
|
204
|
+
}
|
|
75
205
|
}
|
|
76
206
|
catch (err) {
|
|
77
207
|
// The write itself failed (disk full, permission, …): the temp file may
|
|
@@ -81,13 +211,14 @@ export async function writeTextAtomicAsync(filePath, data) {
|
|
|
81
211
|
throw err;
|
|
82
212
|
}
|
|
83
213
|
try {
|
|
84
|
-
await rename(tmp,
|
|
214
|
+
await rename(tmp, target);
|
|
85
215
|
}
|
|
86
216
|
catch (err) {
|
|
87
217
|
// Best-effort temp cleanup so a failed rename never litters the directory.
|
|
88
218
|
await cleanupTempAsync(tmp);
|
|
89
219
|
throw err;
|
|
90
220
|
}
|
|
221
|
+
await fsyncDirBestEffortAsync(dirname(target));
|
|
91
222
|
}
|
|
92
223
|
/** JSON.stringify (2-space indent) + atomic write in one call. */
|
|
93
224
|
export function writeJsonAtomic(filePath, value) {
|