@indigoai-us/hq-cloud 6.11.11 → 6.11.13
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/bin/sync-runner-company.d.ts +35 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.js +290 -0
- package/dist/bin/sync-runner-company.js.map +1 -0
- package/dist/bin/sync-runner-events.d.ts +12 -0
- package/dist/bin/sync-runner-events.d.ts.map +1 -0
- package/dist/bin/sync-runner-events.js +12 -0
- package/dist/bin/sync-runner-events.js.map +1 -0
- package/dist/bin/sync-runner-planning.d.ts +53 -0
- package/dist/bin/sync-runner-planning.d.ts.map +1 -0
- package/dist/bin/sync-runner-planning.js +59 -0
- package/dist/bin/sync-runner-planning.js.map +1 -0
- package/dist/bin/sync-runner-rollup.d.ts +24 -0
- package/dist/bin/sync-runner-rollup.d.ts.map +1 -0
- package/dist/bin/sync-runner-rollup.js +46 -0
- package/dist/bin/sync-runner-rollup.js.map +1 -0
- package/dist/bin/sync-runner-telemetry.d.ts +5 -0
- package/dist/bin/sync-runner-telemetry.d.ts.map +1 -0
- package/dist/bin/sync-runner-telemetry.js +5 -0
- package/dist/bin/sync-runner-telemetry.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts +17 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-loop.js +372 -0
- package/dist/bin/sync-runner-watch-loop.js.map +1 -0
- package/dist/bin/sync-runner-watch-routes.d.ts +25 -0
- package/dist/bin/sync-runner-watch-routes.d.ts.map +1 -0
- package/dist/bin/sync-runner-watch-routes.js +74 -0
- package/dist/bin/sync-runner-watch-routes.js.map +1 -0
- package/dist/bin/sync-runner.d.ts +5 -54
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +76 -978
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +265 -11
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +34 -17
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +39 -5
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/rescue-classify-ordering.test.js +75 -0
- package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
- package/dist/cli/rescue-core.d.ts +45 -0
- package/dist/cli/rescue-core.d.ts.map +1 -1
- package/dist/cli/rescue-core.js +320 -170
- package/dist/cli/rescue-core.js.map +1 -1
- package/dist/cli/share.d.ts +2 -1
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +276 -660
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +30 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +28 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +541 -748
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +382 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.d.ts.map +1 -1
- package/dist/cognito-auth.js +55 -10
- package/dist/cognito-auth.js.map +1 -1
- package/dist/cognito-auth.test.js +61 -0
- package/dist/cognito-auth.test.js.map +1 -1
- package/dist/daemon-worker.d.ts +2 -2
- package/dist/daemon-worker.js +3 -3
- package/dist/daemon-worker.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +93 -6
- package/dist/journal.js.map +1 -1
- package/dist/journal.test.js +59 -0
- package/dist/journal.test.js.map +1 -1
- package/dist/machine-auth.test.js +60 -2
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/object-io.d.ts +37 -1
- package/dist/object-io.d.ts.map +1 -1
- package/dist/object-io.js +149 -30
- package/dist/object-io.js.map +1 -1
- package/dist/object-io.test.js +121 -0
- package/dist/object-io.test.js.map +1 -1
- package/dist/operation-lock.d.ts +8 -8
- package/dist/operation-lock.d.ts.map +1 -1
- package/dist/operation-lock.js +99 -32
- package/dist/operation-lock.js.map +1 -1
- package/dist/operation-lock.test.js +51 -4
- package/dist/operation-lock.test.js.map +1 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +8 -2
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +34 -0
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/prefix-coalesce.d.ts +20 -9
- package/dist/prefix-coalesce.d.ts.map +1 -1
- package/dist/prefix-coalesce.js +124 -28
- package/dist/prefix-coalesce.js.map +1 -1
- package/dist/prefix-coalesce.test.js +57 -2
- package/dist/prefix-coalesce.test.js.map +1 -1
- package/dist/remote-pull.d.ts +8 -3
- package/dist/remote-pull.d.ts.map +1 -1
- package/dist/remote-pull.js +85 -16
- package/dist/remote-pull.js.map +1 -1
- package/dist/remote-pull.test.js +213 -2
- package/dist/remote-pull.test.js.map +1 -1
- package/dist/s3.d.ts +2 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +197 -116
- package/dist/s3.js.map +1 -1
- package/dist/s3.test.js +109 -0
- package/dist/s3.test.js.map +1 -1
- package/dist/scope-shrink.d.ts +3 -2
- package/dist/scope-shrink.d.ts.map +1 -1
- package/dist/scope-shrink.js +1 -1
- package/dist/scope-shrink.js.map +1 -1
- package/dist/skill-telemetry.d.ts +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +69 -9
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +86 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/event-sync.d.ts +6 -0
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +34 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +73 -0
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/metrics.d.ts +17 -1
- package/dist/sync/metrics.d.ts.map +1 -1
- package/dist/sync/metrics.js +32 -1
- package/dist/sync/metrics.js.map +1 -1
- package/dist/sync/metrics.test.js +74 -1
- package/dist/sync/metrics.test.js.map +1 -1
- package/dist/sync/pull-scope.d.ts.map +1 -1
- package/dist/sync/pull-scope.js +15 -7
- package/dist/sync/pull-scope.js.map +1 -1
- package/dist/sync/push-receiver.d.ts +12 -5
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +45 -17
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +67 -1
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync-core.d.ts +27 -0
- package/dist/sync-core.d.ts.map +1 -0
- package/dist/sync-core.js +54 -0
- package/dist/sync-core.js.map +1 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +59 -6
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +74 -0
- package/dist/telemetry.test.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/vault-client.d.ts.map +1 -1
- package/dist/vault-client.js +284 -36
- package/dist/vault-client.js.map +1 -1
- package/dist/vault-client.test.js +59 -0
- package/dist/vault-client.test.js.map +1 -1
- package/dist/watcher.d.ts +38 -20
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +155 -143
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +103 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +350 -0
- package/src/bin/sync-runner-events.ts +25 -0
- package/src/bin/sync-runner-planning.ts +121 -0
- package/src/bin/sync-runner-rollup.ts +72 -0
- package/src/bin/sync-runner-telemetry.ts +8 -0
- package/src/bin/sync-runner-watch-loop.ts +443 -0
- package/src/bin/sync-runner-watch-routes.ts +86 -0
- package/src/bin/sync-runner.test.ts +298 -11
- package/src/bin/sync-runner.ts +99 -1054
- package/src/cli/reindex.test.ts +41 -3
- package/src/cli/reindex.ts +35 -19
- package/src/cli/rescue-classify-ordering.test.ts +81 -0
- package/src/cli/rescue-core.ts +400 -165
- package/src/cli/share.test.ts +38 -0
- package/src/cli/share.ts +420 -693
- package/src/cli/sync.test.ts +460 -1
- package/src/cli/sync.ts +788 -825
- package/src/cognito-auth.test.ts +77 -0
- package/src/cognito-auth.ts +73 -11
- package/src/daemon-worker.ts +3 -3
- package/src/index.ts +8 -0
- package/src/journal.test.ts +72 -0
- package/src/journal.ts +95 -8
- package/src/machine-auth.test.ts +64 -2
- package/src/object-io.test.ts +142 -0
- package/src/object-io.ts +183 -31
- package/src/operation-lock.test.ts +63 -4
- package/src/operation-lock.ts +99 -31
- package/src/personal-vault.test.ts +42 -0
- package/src/personal-vault.ts +8 -2
- package/src/prefix-coalesce.test.ts +71 -1
- package/src/prefix-coalesce.ts +155 -30
- package/src/remote-pull.test.ts +235 -1
- package/src/remote-pull.ts +106 -18
- package/src/s3.test.ts +126 -0
- package/src/s3.ts +237 -122
- package/src/scope-shrink.ts +6 -3
- package/src/skill-telemetry.test.ts +109 -0
- package/src/skill-telemetry.ts +82 -14
- package/src/sync/event-sync.test.ts +75 -0
- package/src/sync/event-sync.ts +54 -1
- package/src/sync/metrics.test.ts +81 -0
- package/src/sync/metrics.ts +59 -4
- package/src/sync/pull-scope.ts +23 -7
- package/src/sync/push-receiver.test.ts +73 -1
- package/src/sync/push-receiver.ts +56 -20
- package/src/sync-core.ts +58 -0
- package/src/telemetry.test.ts +85 -0
- package/src/telemetry.ts +69 -6
- package/src/types.ts +8 -0
- package/src/vault-client.test.ts +74 -0
- package/src/vault-client.ts +395 -43
- package/src/watcher.test.ts +117 -0
- package/src/watcher.ts +215 -174
package/src/cli/reindex.test.ts
CHANGED
|
@@ -18,14 +18,20 @@ import { lockPathFor, OPERATION_LOCKED_EXIT } from "../operation-lock.js";
|
|
|
18
18
|
// HQ-B0: simulate the Windows filesystem rejecting a ':' in a path segment.
|
|
19
19
|
// The flag is off by default, so every other test sees the real `mkdirSync`;
|
|
20
20
|
// the regression test below flips it on only for its own run.
|
|
21
|
-
const hoisted = vi.hoisted(() => ({
|
|
21
|
+
const hoisted = vi.hoisted(() => ({
|
|
22
|
+
failNamespacedMkdir: false,
|
|
23
|
+
// When set, any mkdir whose path contains this substring throws ENOENT —
|
|
24
|
+
// lets a test simulate a mkdir failure at a NON-wrapper site (e.g. the
|
|
25
|
+
// `.claude/skills` parent or a `core/<type>` mirror dir).
|
|
26
|
+
failMkdirContaining: "" as string,
|
|
27
|
+
}));
|
|
22
28
|
vi.mock("fs", async (importOriginal) => {
|
|
23
29
|
const actual = await importOriginal<typeof import("fs")>();
|
|
24
30
|
const mkdirSync = ((p: unknown, opts: unknown) => {
|
|
25
31
|
if (
|
|
26
|
-
hoisted.failNamespacedMkdir &&
|
|
27
32
|
typeof p === "string" &&
|
|
28
|
-
/[:][^/\\]*$/.test(p) //
|
|
33
|
+
((hoisted.failNamespacedMkdir && /[:][^/\\]*$/.test(p)) || // colon in final segment
|
|
34
|
+
(hoisted.failMkdirContaining !== "" && p.includes(hoisted.failMkdirContaining)))
|
|
29
35
|
) {
|
|
30
36
|
throw Object.assign(
|
|
31
37
|
new Error(`ENOENT: no such file or directory, mkdir '${p}'`),
|
|
@@ -269,4 +275,36 @@ describe("reindex", () => {
|
|
|
269
275
|
expect(fs.existsSync(path.join(root, ".claude/skills/core:demo"))).toBe(false);
|
|
270
276
|
expect(fs.existsSync(path.join(root, ".claude/skills/acme:widget"))).toBe(false);
|
|
271
277
|
});
|
|
278
|
+
|
|
279
|
+
// ── HQ-B0 (residual): PR #98 guarded only the wrapper mkdir, leaving the
|
|
280
|
+
// `.claude/skills` parent and the `core/<type>` mirror mkdirs unguarded.
|
|
281
|
+
// A failure at EITHER must also degrade gracefully, not abort reindex.
|
|
282
|
+
it("does not abort reindex when the .claude/skills parent mkdir fails", () => {
|
|
283
|
+
writeSkill("core/skills/demo");
|
|
284
|
+
|
|
285
|
+
hoisted.failMkdirContaining = path.join(".claude", "skills");
|
|
286
|
+
try {
|
|
287
|
+
// The parent mkdir fails AND every recursive wrapper mkdir under it fails,
|
|
288
|
+
// so nothing is surfaced — but reindex must still complete cleanly.
|
|
289
|
+
expect(reindex({ repoRoot: root }).status).toBe(0);
|
|
290
|
+
} finally {
|
|
291
|
+
hoisted.failMkdirContaining = "";
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("does not abort reindex when a core/<type> mirror mkdir fails", () => {
|
|
296
|
+
writeSkill("core/skills/demo");
|
|
297
|
+
fs.mkdirSync(path.join(root, "personal/knowledge/my-kb"), { recursive: true });
|
|
298
|
+
fs.writeFileSync(path.join(root, "personal/knowledge/my-kb/note.md"), "n\n");
|
|
299
|
+
|
|
300
|
+
hoisted.failMkdirContaining = path.join("core", "knowledge");
|
|
301
|
+
try {
|
|
302
|
+
expect(reindex({ repoRoot: root }).status).toBe(0);
|
|
303
|
+
} finally {
|
|
304
|
+
hoisted.failMkdirContaining = "";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// The mirror for the failed type is skipped; skill surfacing still happened.
|
|
308
|
+
expect(fs.existsSync(path.join(root, ".claude/skills/core:demo"))).toBe(true);
|
|
309
|
+
});
|
|
272
310
|
});
|
package/src/cli/reindex.ts
CHANGED
|
@@ -129,6 +129,31 @@ function warn(msg: string): void {
|
|
|
129
129
|
process.stderr.write(`${msg}\n`);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// Create a directory (recursively), tolerating platform-specific mkdir
|
|
133
|
+
// failures instead of aborting the whole reindex. The classic case (HQ-B0) is
|
|
134
|
+
// a `<ns>:<skill>` wrapper whose ':' is a reserved drive/ADS separator on
|
|
135
|
+
// Windows, where mkdirSync throws ENOENT — but any single mkdir failure (a
|
|
136
|
+
// stray file in the slot, a permission hiccup) should degrade gracefully and
|
|
137
|
+
// let the rest of the reindex proceed. Returns true on success; on failure it
|
|
138
|
+
// logs a clear, actionable warning and returns false so the caller can skip
|
|
139
|
+
// just that piece. PR #98 guarded only the wrapper mkdir; this guards every
|
|
140
|
+
// mkdir site so no single one can crash `hq reindex`.
|
|
141
|
+
function safeMkdir(dir: string, label: string): boolean {
|
|
142
|
+
try {
|
|
143
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
return true;
|
|
145
|
+
} catch (err) {
|
|
146
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
147
|
+
warn(
|
|
148
|
+
`reindex: could not create ${label} '${dir}' ` +
|
|
149
|
+
`(${code ?? "error"}: ${(err as Error).message}); skipping. On Windows ` +
|
|
150
|
+
`this is usually an illegal path character (e.g. a ':' in a '<ns>:<skill>' ` +
|
|
151
|
+
`wrapper name).`,
|
|
152
|
+
);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
132
157
|
// --- legacy `.claude/commands/<ns>/<skill>.md` symlink matcher --------------
|
|
133
158
|
// Mirrors the bash `case` patterns; `*` matches any chars (including `/`).
|
|
134
159
|
function isLegacyCommandTarget(t: string): boolean {
|
|
@@ -173,7 +198,10 @@ export function reindex(opts: ReindexOptions = {}): ReindexResult {
|
|
|
173
198
|
}
|
|
174
199
|
try {
|
|
175
200
|
|
|
176
|
-
|
|
201
|
+
// Best-effort: each wrapper mkdir below is recursive and re-creates this
|
|
202
|
+
// parent as needed, so a failure here is non-fatal — warn and carry on so
|
|
203
|
+
// personal-overlay mirroring and registry regeneration still run.
|
|
204
|
+
safeMkdir(path.join(root, ".claude", "skills"), ".claude/skills directory");
|
|
177
205
|
|
|
178
206
|
// --- Build (namespace, src_rel) pairs -------------------------------------
|
|
179
207
|
const pairs: { ns: string; srcRel: string }[] = [];
|
|
@@ -270,23 +298,11 @@ export function reindex(opts: ReindexOptions = {}): ReindexResult {
|
|
|
270
298
|
/* best-effort */
|
|
271
299
|
}
|
|
272
300
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// but a reserved drive/ADS separator on Windows, so mkdir there fails
|
|
279
|
-
// with ENOENT. Skip this one wrapper with a clear message instead of
|
|
280
|
-
// aborting the whole reindex (the skill's source folder is untouched).
|
|
281
|
-
const code = (err as NodeJS.ErrnoException).code;
|
|
282
|
-
warn(
|
|
283
|
-
`reindex: could not create skill wrapper '${wrapperName}' ` +
|
|
284
|
-
`(${code ?? "error"}: ${(err as Error).message}). Namespaced wrappers ` +
|
|
285
|
-
`use a ':' in the directory name, which is not a legal filename ` +
|
|
286
|
-
`character on this platform (e.g. Windows); skipping this skill.`,
|
|
287
|
-
);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
301
|
+
// Namespaced wrappers embed a ':' in the directory name (`<ns>:<skill>`),
|
|
302
|
+
// which is a reserved drive/ADS separator on Windows where mkdir fails
|
|
303
|
+
// with ENOENT (HQ-B0). Skip just this wrapper instead of aborting the
|
|
304
|
+
// whole reindex; the skill's source folder is untouched.
|
|
305
|
+
if (!safeMkdir(wrapper, `skill wrapper '${wrapperName}'`)) continue;
|
|
290
306
|
|
|
291
307
|
// Symlink every (non-hidden) entry in the source skill folder. The
|
|
292
308
|
// wrapper lives three levels below REPO_ROOT.
|
|
@@ -386,7 +402,7 @@ export function reindex(opts: ReindexOptions = {}): ReindexResult {
|
|
|
386
402
|
const coreDir = path.join(root, "core", type);
|
|
387
403
|
|
|
388
404
|
if (!isDir(personalDir)) continue;
|
|
389
|
-
|
|
405
|
+
if (!safeMkdir(coreDir, `core/${type} directory`)) continue;
|
|
390
406
|
|
|
391
407
|
for (const entry of globEntries(personalDir)) {
|
|
392
408
|
const entryPath = path.join(personalDir, entry);
|
|
@@ -74,6 +74,41 @@ const lexists = (p: string) => {
|
|
|
74
74
|
}
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
+
function hasRecoveryManifestFor(root: string, rel: string): boolean {
|
|
78
|
+
let found = false;
|
|
79
|
+
const interesting = /(recover|recovery|rollback|transaction|manifest|resume|rescue)/i;
|
|
80
|
+
const walk = (dir: string) => {
|
|
81
|
+
if (found) return;
|
|
82
|
+
let entries: fs.Dirent[];
|
|
83
|
+
try {
|
|
84
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
} catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
89
|
+
for (const ent of entries) {
|
|
90
|
+
if (found) return;
|
|
91
|
+
const abs = path.join(dir, ent.name);
|
|
92
|
+
if (ent.isDirectory()) {
|
|
93
|
+
walk(abs);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!ent.isFile()) continue;
|
|
97
|
+
const relPath = abs.slice(root.length + 1);
|
|
98
|
+
if (!interesting.test(relPath)) continue;
|
|
99
|
+
let body = "";
|
|
100
|
+
try {
|
|
101
|
+
body = fs.readFileSync(abs, "utf-8");
|
|
102
|
+
} catch {
|
|
103
|
+
body = "";
|
|
104
|
+
}
|
|
105
|
+
found = body.includes(rel) && interesting.test(`${relPath}\n${body}`);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
walk(root);
|
|
109
|
+
return found;
|
|
110
|
+
}
|
|
111
|
+
|
|
77
112
|
describe.skipIf(!gitAvailable)("rescue classify-before-delete + dir-symlink handling", () => {
|
|
78
113
|
let workDir: string;
|
|
79
114
|
let upstream: string;
|
|
@@ -108,6 +143,8 @@ describe.skipIf(!gitAvailable)("rescue classify-before-delete + dir-symlink hand
|
|
|
108
143
|
git(workDir, "init", "-b", "main", "upstream");
|
|
109
144
|
fs.writeFileSync(path.join(upstream, "core/a.md"), "v1\n");
|
|
110
145
|
fs.writeFileSync(path.join(upstream, "core/b.md"), "beta\n");
|
|
146
|
+
fs.mkdirSync(path.join(upstream, "core/blocker"), { recursive: true });
|
|
147
|
+
fs.writeFileSync(path.join(upstream, "core/blocker/second.md"), "upstream\n");
|
|
111
148
|
git(upstream, "add", "-A");
|
|
112
149
|
git(upstream, "commit", "-m", "floor");
|
|
113
150
|
floorSha = git(upstream, "rev-parse", "HEAD");
|
|
@@ -267,6 +304,50 @@ exec ${JSON.stringify(realGit)} "$@"
|
|
|
267
304
|
expect(fs.existsSync(path.join(hqRoot, "core/zzz.md"))).toBe(true);
|
|
268
305
|
});
|
|
269
306
|
|
|
307
|
+
it("dry-run emits prior plan lines before a classifier fault aborts the walk", () => {
|
|
308
|
+
const hqRoot = makeHqRoot();
|
|
309
|
+
fs.writeFileSync(path.join(hqRoot, "core/zzz.md"), "trigger\n");
|
|
310
|
+
|
|
311
|
+
const r = runRescueCapture(liveArgs(hqRoot, ["--dry-run"]), {
|
|
312
|
+
...baseEnv,
|
|
313
|
+
HQ_RESCUE_FAULT_AT_REL: "core/zzz.md",
|
|
314
|
+
});
|
|
315
|
+
const out = `${r.stdout}\n${r.stderr}`;
|
|
316
|
+
|
|
317
|
+
expect(r.threw, out).toBeDefined();
|
|
318
|
+
expect(String(r.threw)).toMatch(/fault injected at core\/zzz\.md/);
|
|
319
|
+
expect(out).toContain("unchanged (delete + replace): core/a.md");
|
|
320
|
+
expect(out).toContain("unchanged (preserved in place): core/b.md");
|
|
321
|
+
expect(out).not.toMatch(/DRY RUN classification summary/);
|
|
322
|
+
expect(fs.existsSync(path.join(hqRoot, "core/a.md"))).toBe(true);
|
|
323
|
+
expect(fs.readFileSync(path.join(hqRoot, "core/a.md"), "utf-8")).toBe("v1\n");
|
|
324
|
+
expect(fs.existsSync(path.join(hqRoot, "core/zzz.md"))).toBe(true);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it("F12: rolls back or records recovery when an apply action fails after earlier destructive actions", () => {
|
|
328
|
+
const hqRoot = makeHqRoot();
|
|
329
|
+
fs.mkdirSync(path.join(hqRoot, "core/blocker"), { recursive: true });
|
|
330
|
+
fs.writeFileSync(path.join(hqRoot, "core/blocker/second.md"), "local edit\n");
|
|
331
|
+
fs.writeFileSync(path.join(hqRoot, "personal/blocker"), "not a directory\n");
|
|
332
|
+
|
|
333
|
+
const firstDeletedByApply = path.join(hqRoot, "core/a.md");
|
|
334
|
+
const firstOriginal = fs.readFileSync(firstDeletedByApply, "utf-8");
|
|
335
|
+
const r = runRescueCapture(liveArgs(hqRoot), baseEnv);
|
|
336
|
+
const out = `${r.stdout}\n${r.stderr}\n${String(r.threw ?? "")}`;
|
|
337
|
+
|
|
338
|
+
const applyFailed = r.threw !== undefined || (r.status !== undefined && r.status !== 0);
|
|
339
|
+
expect(applyFailed, out).toBe(true);
|
|
340
|
+
expect(fs.existsSync(path.join(hqRoot, "core/blocker/second.md")), out).toBe(true);
|
|
341
|
+
|
|
342
|
+
const firstActionRolledBack =
|
|
343
|
+
fs.existsSync(firstDeletedByApply) &&
|
|
344
|
+
fs.readFileSync(firstDeletedByApply, "utf-8") === firstOriginal;
|
|
345
|
+
expect(
|
|
346
|
+
firstActionRolledBack || hasRecoveryManifestFor(hqRoot, "core/a.md"),
|
|
347
|
+
`${out}\ncore/a.md was deleted without rollback or a recovery/transaction manifest`,
|
|
348
|
+
).toBe(true);
|
|
349
|
+
});
|
|
350
|
+
|
|
270
351
|
it("dry-run classifies the dir-symlink identically to the live run and changes nothing", () => {
|
|
271
352
|
const hqRoot = makeHqRoot();
|
|
272
353
|
const target = path.join(hqRoot, "personal/knowledge/ad-creative-engine");
|