infinity-harness 2.8.9 → 2.8.11
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/CHANGELOG.md +18 -0
- package/dist/extensions/infinity-harness/index.js +3614 -0
- package/dist/extensions/infinity-harness/index.js.map +1 -0
- package/dist/src/approval.js +154 -0
- package/dist/src/approval.js.map +1 -0
- package/dist/src/core/brief.js +309 -0
- package/dist/src/core/brief.js.map +1 -0
- package/dist/src/core/config.js +539 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/exec.js +106 -0
- package/dist/src/core/exec.js.map +1 -0
- package/dist/src/core/featureList.js +365 -0
- package/dist/src/core/featureList.js.map +1 -0
- package/dist/src/core/fsx.js +123 -0
- package/dist/src/core/fsx.js.map +1 -0
- package/dist/src/core/gates.js +490 -0
- package/dist/src/core/gates.js.map +1 -0
- package/dist/src/core/init.js +480 -0
- package/dist/src/core/init.js.map +1 -0
- package/dist/src/core/lock.js +183 -0
- package/dist/src/core/lock.js.map +1 -0
- package/dist/src/core/modelRouter.js +185 -0
- package/dist/src/core/modelRouter.js.map +1 -0
- package/dist/src/core/paths.js +129 -0
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/phases.js +254 -0
- package/dist/src/core/phases.js.map +1 -0
- package/dist/src/core/plan.js +15 -0
- package/dist/src/core/plan.js.map +1 -0
- package/dist/src/core/runState.js +126 -0
- package/dist/src/core/runState.js.map +1 -0
- package/dist/src/core/settings.js +877 -0
- package/dist/src/core/settings.js.map +1 -0
- package/dist/src/core/skills.js +319 -0
- package/dist/src/core/skills.js.map +1 -0
- package/dist/src/core/skillsAudit.js +194 -0
- package/dist/src/core/skillsAudit.js.map +1 -0
- package/dist/src/core/types.js +77 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/daemon/budget.js +85 -0
- package/dist/src/daemon/budget.js.map +1 -0
- package/dist/src/daemon/guard.js +122 -0
- package/dist/src/daemon/guard.js.map +1 -0
- package/dist/src/daemon/index.js +461 -0
- package/dist/src/daemon/index.js.map +1 -0
- package/dist/src/daemon/isolation.js +84 -0
- package/dist/src/daemon/isolation.js.map +1 -0
- package/dist/src/daemon/preflight.js +131 -0
- package/dist/src/daemon/preflight.js.map +1 -0
- package/dist/src/daemon/server.js +174 -0
- package/dist/src/daemon/server.js.map +1 -0
- package/dist/src/daemon/supervisorState.js +42 -0
- package/dist/src/daemon/supervisorState.js.map +1 -0
- package/dist/src/daemon/worker.js +239 -0
- package/dist/src/daemon/worker.js.map +1 -0
- package/dist/src/daemon/worktree.js +102 -0
- package/dist/src/daemon/worktree.js.map +1 -0
- package/dist/src/escalate.js +312 -0
- package/dist/src/escalate.js.map +1 -0
- package/dist/src/exec/piWorker.js +630 -0
- package/dist/src/exec/piWorker.js.map +1 -0
- package/dist/src/goal.js +317 -0
- package/dist/src/goal.js.map +1 -0
- package/dist/src/goalLoop.js +374 -0
- package/dist/src/goalLoop.js.map +1 -0
- package/dist/src/goalSpec.js +397 -0
- package/dist/src/goalSpec.js.map +1 -0
- package/dist/src/goalState.js +255 -0
- package/dist/src/goalState.js.map +1 -0
- package/dist/src/handoff.js +205 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/intake.js +203 -0
- package/dist/src/intake.js.map +1 -0
- package/dist/src/loop.js +592 -0
- package/dist/src/loop.js.map +1 -0
- package/dist/src/modelRouter.js +216 -0
- package/dist/src/modelRouter.js.map +1 -0
- package/dist/src/remote.js +246 -0
- package/dist/src/remote.js.map +1 -0
- package/dist/src/replan.js +262 -0
- package/dist/src/replan.js.map +1 -0
- package/dist/src/review.js +54 -0
- package/dist/src/review.js.map +1 -0
- package/dist/src/rework.js +241 -0
- package/dist/src/rework.js.map +1 -0
- package/dist/src/runState.js +4 -0
- package/dist/src/runState.js.map +1 -0
- package/dist/src/scheduler.js +270 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/supervisor.js +787 -0
- package/dist/src/supervisor.js.map +1 -0
- package/dist/src/taskList.js +430 -0
- package/dist/src/taskList.js.map +1 -0
- package/dist/src/ui/config.js +268 -0
- package/dist/src/ui/config.js.map +1 -0
- package/dist/src/ui/dashboard.js +1288 -0
- package/dist/src/ui/dashboard.js.map +1 -0
- package/dist/src/ui/display.js +229 -0
- package/dist/src/ui/display.js.map +1 -0
- package/dist/src/ui/planTree.js +219 -0
- package/dist/src/ui/planTree.js.map +1 -0
- package/dist/src/ui/theme.js +298 -0
- package/dist/src/ui/theme.js.map +1 -0
- package/dist/src/ui/viewState.js +65 -0
- package/dist/src/ui/viewState.js.map +1 -0
- package/dist/src/ui/widget.js +721 -0
- package/dist/src/ui/widget.js.map +1 -0
- package/dist/src/ui/wizard.js +540 -0
- package/dist/src/ui/wizard.js.map +1 -0
- package/dist/src/unstuck.js +210 -0
- package/dist/src/unstuck.js.map +1 -0
- package/dist/src/worker.js +267 -0
- package/dist/src/worker.js.map +1 -0
- package/dist/src/workflow.js +250 -0
- package/dist/src/workflow.js.map +1 -0
- package/dist/tests/approval.test.js +144 -0
- package/dist/tests/approval.test.js.map +1 -0
- package/dist/tests/brief.test.js +271 -0
- package/dist/tests/brief.test.js.map +1 -0
- package/dist/tests/config.test.js +320 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/dashboard.test.js +277 -0
- package/dist/tests/dashboard.test.js.map +1 -0
- package/dist/tests/display.test.js +229 -0
- package/dist/tests/display.test.js.map +1 -0
- package/dist/tests/escalate.test.js +272 -0
- package/dist/tests/escalate.test.js.map +1 -0
- package/dist/tests/featureList.test.js +429 -0
- package/dist/tests/featureList.test.js.map +1 -0
- package/dist/tests/gates.test.js +333 -0
- package/dist/tests/gates.test.js.map +1 -0
- package/dist/tests/goal.test.js +229 -0
- package/dist/tests/goal.test.js.map +1 -0
- package/dist/tests/goalLoop.test.js +170 -0
- package/dist/tests/goalLoop.test.js.map +1 -0
- package/dist/tests/goalSpec.test.js +146 -0
- package/dist/tests/goalSpec.test.js.map +1 -0
- package/dist/tests/goalState.test.js +202 -0
- package/dist/tests/goalState.test.js.map +1 -0
- package/dist/tests/handoff.test.js +127 -0
- package/dist/tests/handoff.test.js.map +1 -0
- package/dist/tests/init.test.js +264 -0
- package/dist/tests/init.test.js.map +1 -0
- package/dist/tests/intake.test.js +288 -0
- package/dist/tests/intake.test.js.map +1 -0
- package/dist/tests/loop.test.js +453 -0
- package/dist/tests/loop.test.js.map +1 -0
- package/dist/tests/modelRouter.test.js +131 -0
- package/dist/tests/modelRouter.test.js.map +1 -0
- package/dist/tests/phases.test.js +244 -0
- package/dist/tests/phases.test.js.map +1 -0
- package/dist/tests/planTree.test.js +190 -0
- package/dist/tests/planTree.test.js.map +1 -0
- package/dist/tests/remote.test.js +380 -0
- package/dist/tests/remote.test.js.map +1 -0
- package/dist/tests/replan.test.js +113 -0
- package/dist/tests/replan.test.js.map +1 -0
- package/dist/tests/review.test.js +77 -0
- package/dist/tests/review.test.js.map +1 -0
- package/dist/tests/rework.test.js +109 -0
- package/dist/tests/rework.test.js.map +1 -0
- package/dist/tests/routing-live.test.js +121 -0
- package/dist/tests/routing-live.test.js.map +1 -0
- package/dist/tests/runState.test.js +73 -0
- package/dist/tests/runState.test.js.map +1 -0
- package/dist/tests/settings.test.js +311 -0
- package/dist/tests/settings.test.js.map +1 -0
- package/dist/tests/skills.test.js +387 -0
- package/dist/tests/skills.test.js.map +1 -0
- package/dist/tests/supervisor.test.js +437 -0
- package/dist/tests/supervisor.test.js.map +1 -0
- package/dist/tests/surface.test.js +203 -0
- package/dist/tests/surface.test.js.map +1 -0
- package/dist/tests/taskList.test.js +376 -0
- package/dist/tests/taskList.test.js.map +1 -0
- package/dist/tests/theme.test.js +149 -0
- package/dist/tests/theme.test.js.map +1 -0
- package/dist/tests/unstuck.test.js +268 -0
- package/dist/tests/unstuck.test.js.map +1 -0
- package/dist/tests/widget.test.js +424 -0
- package/dist/tests/widget.test.js.map +1 -0
- package/dist/tests/worker.test.js +306 -0
- package/dist/tests/worker.test.js.map +1 -0
- package/dist/tests/workflow.test.js +187 -0
- package/dist/tests/workflow.test.js.map +1 -0
- package/extensions/infinity-harness/index.ts +39 -12
- package/package.json +5 -2
- package/src/core/modelRouter.ts +23 -1
- package/src/daemon/index.ts +3 -3
- package/src/daemon/worker.ts +28 -5
- package/src/ui/config.ts +25 -1
- package/src/ui/wizard.ts +10 -5
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { buildApiPayload, buildHtml, buildRemoteState, createRemoteServer, escapeHtml, } from "../src/remote.js";
|
|
6
|
+
import { defaultConfig } from "../src/core/config.js";
|
|
7
|
+
/**
|
|
8
|
+
* A keep-alive socket pooled by undici from a previous server on the same
|
|
9
|
+
* host:port can already be dead when the next server binds it; the first
|
|
10
|
+
* request over it fails with "other side closed". Retry rather than let
|
|
11
|
+
* connection pooling decide whether the suite passes.
|
|
12
|
+
*/
|
|
13
|
+
async function get(url, init) {
|
|
14
|
+
let lastError;
|
|
15
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
16
|
+
try {
|
|
17
|
+
return await fetch(url, init);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
lastError = e;
|
|
21
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw lastError;
|
|
25
|
+
}
|
|
26
|
+
function tmpProject() {
|
|
27
|
+
const d = mkdtempSync(join(tmpdir(), "pi-remote-"));
|
|
28
|
+
mkdirSync(join(d, "harness", "features"), { recursive: true });
|
|
29
|
+
return d;
|
|
30
|
+
}
|
|
31
|
+
function writeFeatureList(dir, fl) {
|
|
32
|
+
writeFileSync(join(dir, "harness", "features", "feature-list.json"), JSON.stringify(fl, null, 2), "utf-8");
|
|
33
|
+
}
|
|
34
|
+
function writeConfig(dir, mutate = () => { }) {
|
|
35
|
+
const c = defaultConfig();
|
|
36
|
+
mutate(c);
|
|
37
|
+
writeFileSync(join(dir, "harness", "config.json"), JSON.stringify(c, null, 2), "utf-8");
|
|
38
|
+
}
|
|
39
|
+
function baseFeatureList(overrides) {
|
|
40
|
+
return {
|
|
41
|
+
version: "2.0",
|
|
42
|
+
baseRevision: 7,
|
|
43
|
+
goals: [{ id: "goal-001", title: "infinity-harness v1.0 — superset visual harness", description: "test" }],
|
|
44
|
+
sprints: [{ id: "sprint-005", name: "F5 — Remote", goalId: "goal-001" }],
|
|
45
|
+
features: [
|
|
46
|
+
{
|
|
47
|
+
id: "feature-005",
|
|
48
|
+
name: "Remote Read-Only Web View → 1.0.0",
|
|
49
|
+
sprintId: "sprint-005",
|
|
50
|
+
goalId: "goal-001",
|
|
51
|
+
passes: false,
|
|
52
|
+
tasks: [
|
|
53
|
+
{ id: "task-010", key: "remote-core", description: "Build src/remote.ts", status: "complete", dependsOn: [], subtasks: [] },
|
|
54
|
+
{ id: "task-011", key: "remote-wire", description: "Wire enforcer & bump", status: "pending", dependsOn: ["remote-core"], subtasks: [] },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
...(overrides ?? {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ── escapeHtml (re-exported for callers that build their own fragments) ─────
|
|
62
|
+
{
|
|
63
|
+
assert.equal(escapeHtml(`a & b`), "a & b");
|
|
64
|
+
assert.equal(escapeHtml(`<x>"y"`), "<x>"y"");
|
|
65
|
+
assert.equal(escapeHtml(`it's`), "it's");
|
|
66
|
+
console.log("✓ escapeHtml");
|
|
67
|
+
}
|
|
68
|
+
// ── buildRemoteState: the snapshot the dashboard renders ───────────────────
|
|
69
|
+
{
|
|
70
|
+
const dir = tmpProject();
|
|
71
|
+
try {
|
|
72
|
+
writeFeatureList(dir, baseFeatureList());
|
|
73
|
+
writeConfig(dir, (c) => {
|
|
74
|
+
c.currentPhase = "build";
|
|
75
|
+
c.taskRetryCount = 3;
|
|
76
|
+
c.maxRetries = 9;
|
|
77
|
+
});
|
|
78
|
+
const st = buildRemoteState(dir);
|
|
79
|
+
assert.equal(st.baseRevision, 7, "baseRevision matches fixture");
|
|
80
|
+
assert.ok(Array.isArray(st.features) && st.features.length === 1);
|
|
81
|
+
assert.equal(st.features[0].id, "feature-005");
|
|
82
|
+
assert.ok(Array.isArray(st.goals) && st.goals.length === 1);
|
|
83
|
+
assert.equal(st.list.baseRevision, 7, "the whole plan comes along for the renderer");
|
|
84
|
+
assert.equal(st.list.features[0].tasks.length, 2);
|
|
85
|
+
// Progress is computed here so every consumer sees the same numbers.
|
|
86
|
+
assert.equal(st.progress.tasksDone, 1);
|
|
87
|
+
assert.equal(st.progress.tasksTotal, 2);
|
|
88
|
+
assert.equal(st.progress.percent, 50);
|
|
89
|
+
assert.equal(st.phase, "build", "the phase comes from the config, not the plan");
|
|
90
|
+
assert.equal(st.paused, false);
|
|
91
|
+
assert.deepEqual(st.enabledPhases, defaultConfig().phases.enabled);
|
|
92
|
+
assert.deepEqual(st.retries, { task: 3, max: 9 });
|
|
93
|
+
assert.equal(st.gate, null, "no gate has been recorded yet");
|
|
94
|
+
assert.equal(st.router, null, "no model-router.json in this project");
|
|
95
|
+
assert.equal(st.rework, null);
|
|
96
|
+
assert.ok(typeof st.timestamp === "string" && new Date(st.timestamp).toISOString() === st.timestamp);
|
|
97
|
+
console.log("✓ buildRemoteState shape: baseRevision, plan, progress, phase, retries, timestamp");
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rmSync(dir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ── buildRemoteState: the last recorded gate, never a fresh run ────────────
|
|
104
|
+
{
|
|
105
|
+
const dir = tmpProject();
|
|
106
|
+
try {
|
|
107
|
+
writeFeatureList(dir, baseFeatureList());
|
|
108
|
+
writeConfig(dir, (c) => {
|
|
109
|
+
c.currentPhase = "build";
|
|
110
|
+
c.commands.test = "exit 1"; // would fail if the gate were actually run
|
|
111
|
+
c.gateHistory = [
|
|
112
|
+
{ phase: "define", result: "pass", timestamp: "2026-08-01T00:00:00.000Z" },
|
|
113
|
+
{ phase: "build", result: "fail", timestamp: "2026-08-02T00:00:00.000Z", feature: "feature-005", task: "remote-wire" },
|
|
114
|
+
];
|
|
115
|
+
});
|
|
116
|
+
const st = buildRemoteState(dir);
|
|
117
|
+
assert.ok(st.gate, "the most recent verdict is reported");
|
|
118
|
+
assert.equal(st.gate.phase, "build", "the last entry wins, not the first");
|
|
119
|
+
assert.equal(st.gate.overall, false);
|
|
120
|
+
assert.deepEqual(st.gate.failures, ["see terminal for detail"]);
|
|
121
|
+
assert.equal(st.gate.feature, "feature-005");
|
|
122
|
+
assert.equal(st.gate.task, "remote-wire");
|
|
123
|
+
assert.equal(st.gate.checks.length, 1);
|
|
124
|
+
assert.match(st.gate.checks[0].name, /last recorded gate \(2026-08-02T00:00:00.000Z\)/);
|
|
125
|
+
assert.match(st.gate.checks[0].detail, /failed on build/);
|
|
126
|
+
// Opening the page must not perturb the run: no gate history is appended.
|
|
127
|
+
assert.equal(buildRemoteState(dir).gate.checks.length, 1);
|
|
128
|
+
const cfg = JSON.parse(readFileSync(join(dir, "harness", "config.json"), "utf-8"));
|
|
129
|
+
assert.equal(cfg.gateHistory.length, 2, "reading the state records nothing");
|
|
130
|
+
console.log("✓ buildRemoteState reports the last recorded gate without running one");
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
rmSync(dir, { recursive: true, force: true });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ── buildRemoteState: missing plan -> empty state ──────────────────────────
|
|
137
|
+
{
|
|
138
|
+
const dir = tmpProject();
|
|
139
|
+
try {
|
|
140
|
+
const st = buildRemoteState(dir);
|
|
141
|
+
assert.equal(st.baseRevision, 0);
|
|
142
|
+
assert.equal(st.features.length, 0);
|
|
143
|
+
assert.deepEqual(st.goals, []);
|
|
144
|
+
assert.equal(st.progress.tasksTotal, 0);
|
|
145
|
+
assert.equal(st.phase, null, "an uninitialised project has no phase");
|
|
146
|
+
assert.equal(st.paused, false);
|
|
147
|
+
assert.ok(buildHtml(st).startsWith("<!doctype html>"), "an empty project still renders a page");
|
|
148
|
+
console.log("✓ buildRemoteState missing file returns empty");
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
rmSync(dir, { recursive: true, force: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ── router / rework side files are surfaced verbatim ───────────────────────
|
|
155
|
+
{
|
|
156
|
+
const dir = tmpProject();
|
|
157
|
+
try {
|
|
158
|
+
writeFeatureList(dir, baseFeatureList());
|
|
159
|
+
writeConfig(dir);
|
|
160
|
+
writeFileSync(join(dir, "harness", "model-router.json"), JSON.stringify({ enabled: true, default: "m-1" }), "utf-8");
|
|
161
|
+
writeFileSync(join(dir, "harness", "rework.json"), JSON.stringify({ active: true, impactedCount: 4 }), "utf-8");
|
|
162
|
+
const st = buildRemoteState(dir);
|
|
163
|
+
assert.deepEqual(st.router, { enabled: true, default: "m-1" });
|
|
164
|
+
assert.deepEqual(st.rework, { active: true, impactedCount: 4 });
|
|
165
|
+
const html = buildHtml(st);
|
|
166
|
+
assert.match(html, /router/, "the router badge is rendered");
|
|
167
|
+
assert.match(html, /4 impacted/, "the rework badge carries its one detail");
|
|
168
|
+
// A corrupt side file must not take the page down.
|
|
169
|
+
writeFileSync(join(dir, "harness", "rework.json"), "{ broken", "utf-8");
|
|
170
|
+
assert.equal(buildRemoteState(dir).rework, null);
|
|
171
|
+
console.log("✓ router/rework side files are surfaced and degrade safely");
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
rmSync(dir, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ── buildHtml: a self-contained page that escapes everything ───────────────
|
|
178
|
+
{
|
|
179
|
+
const dir = tmpProject();
|
|
180
|
+
try {
|
|
181
|
+
const dangerous = baseFeatureList({
|
|
182
|
+
features: [
|
|
183
|
+
{
|
|
184
|
+
id: "feature-005",
|
|
185
|
+
name: "XSS <test>",
|
|
186
|
+
sprintId: "sprint-005",
|
|
187
|
+
tasks: [
|
|
188
|
+
{ id: "t1", key: "k1", description: `a & b <script>"x"</script>`, status: "pending", dependsOn: [], subtasks: [] },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
writeFeatureList(dir, dangerous);
|
|
194
|
+
writeConfig(dir, (c) => {
|
|
195
|
+
c.currentPhase = "build";
|
|
196
|
+
});
|
|
197
|
+
const st = buildRemoteState(dir);
|
|
198
|
+
const html = buildHtml(st);
|
|
199
|
+
assert.ok(html.includes("infinity-harness"), "html identifies the harness");
|
|
200
|
+
assert.match(html, /rev 7/, "html shows the plan revision");
|
|
201
|
+
assert.ok(!html.includes(`<script>"x"</script>`), "html escaped script");
|
|
202
|
+
assert.ok(html.includes("&") && html.includes("<script>"), "html escaped entities");
|
|
203
|
+
assert.ok(html.includes("XSS <test>"), "the feature name is escaped, not stripped");
|
|
204
|
+
assert.match(html, /fetch\(/, "the page polls itself");
|
|
205
|
+
assert.ok(!html.includes("http://") && !html.includes("https://"), "no external references");
|
|
206
|
+
console.log("✓ buildHtml escaping + self-contained page");
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
rmSync(dir, { recursive: true, force: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── buildApiPayload: compact, and no full plan ─────────────────────────────
|
|
213
|
+
{
|
|
214
|
+
const dir = tmpProject();
|
|
215
|
+
try {
|
|
216
|
+
writeFeatureList(dir, baseFeatureList());
|
|
217
|
+
writeConfig(dir, (c) => {
|
|
218
|
+
c.currentPhase = "build";
|
|
219
|
+
});
|
|
220
|
+
const payload = buildApiPayload(buildRemoteState(dir));
|
|
221
|
+
assert.equal(payload.baseRevision, 7);
|
|
222
|
+
assert.equal(payload.phase, "build");
|
|
223
|
+
assert.equal(payload.paused, false);
|
|
224
|
+
assert.ok(!("list" in payload), "the full plan is left out to keep the payload small");
|
|
225
|
+
const features = payload.features;
|
|
226
|
+
assert.equal(features.length, 1);
|
|
227
|
+
assert.equal(features[0].tasks.length, 2);
|
|
228
|
+
assert.equal(features[0].tasks[0].key, "remote-core");
|
|
229
|
+
assert.deepEqual(features[0].tasks[1].dependsOn, ["remote-core"]);
|
|
230
|
+
assert.equal(JSON.stringify(payload).length > 0, true);
|
|
231
|
+
console.log("✓ buildApiPayload");
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
rmSync(dir, { recursive: true, force: true });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// ── createRemoteServer: ephemeral port shape + HTML + health ────────────────
|
|
238
|
+
{
|
|
239
|
+
const dir = tmpProject();
|
|
240
|
+
try {
|
|
241
|
+
writeFeatureList(dir, baseFeatureList());
|
|
242
|
+
writeConfig(dir, (c) => {
|
|
243
|
+
c.currentPhase = "build";
|
|
244
|
+
});
|
|
245
|
+
const srv = await createRemoteServer({ projectDir: dir, host: "127.0.0.1", port: 0 });
|
|
246
|
+
try {
|
|
247
|
+
assert.ok(srv.url.startsWith("http://127.0.0.1:"), `url ${srv.url}`);
|
|
248
|
+
assert.ok(srv.port > 0 && srv.port < 65536);
|
|
249
|
+
// GET /api/harness shape
|
|
250
|
+
const r1 = await get(`${srv.url}/api/harness`);
|
|
251
|
+
assert.equal(r1.status, 200);
|
|
252
|
+
const j1 = await r1.json();
|
|
253
|
+
assert.equal(j1.baseRevision, 7);
|
|
254
|
+
assert.ok(Array.isArray(j1.features) && j1.features.length === 1);
|
|
255
|
+
assert.equal(j1.progress.tasksTotal, 2);
|
|
256
|
+
assert.equal(j1.phase, "build");
|
|
257
|
+
assert.ok(typeof j1.timestamp === "string");
|
|
258
|
+
const ct1 = r1.headers.get("content-type") ?? "";
|
|
259
|
+
assert.ok(ct1.includes("application/json"));
|
|
260
|
+
assert.equal(r1.headers.get("cache-control"), "no-store", "a dashboard must never be cached");
|
|
261
|
+
// GET / serves the dashboard
|
|
262
|
+
const r2 = await get(`${srv.url}/`);
|
|
263
|
+
assert.equal(r2.status, 200);
|
|
264
|
+
const html = await r2.text();
|
|
265
|
+
assert.ok(html.includes("infinity-harness"), "GET / html identifies the harness");
|
|
266
|
+
assert.ok(html.includes("Remote Read-Only Web View"), "GET / html shows the plan");
|
|
267
|
+
assert.ok(html.includes(`rev ${j1.baseRevision}`), "GET / html mentions the revision");
|
|
268
|
+
const ct2 = r2.headers.get("content-type") ?? "";
|
|
269
|
+
assert.ok(ct2.includes("text/html"));
|
|
270
|
+
// The page renders untrusted model output; the CSP is the second line of
|
|
271
|
+
// defence behind escaping.
|
|
272
|
+
const csp = r2.headers.get("content-security-policy") ?? "";
|
|
273
|
+
assert.match(csp, /default-src 'none'/);
|
|
274
|
+
assert.ok(!csp.includes("script-src 'self'"), "no remote script origin is allowed");
|
|
275
|
+
assert.equal(r2.headers.get("x-content-type-options"), "nosniff");
|
|
276
|
+
// GET /api/health
|
|
277
|
+
const r3 = await get(`${srv.url}/api/health`);
|
|
278
|
+
assert.equal(r3.status, 200);
|
|
279
|
+
const j3 = await r3.json();
|
|
280
|
+
assert.equal(j3.ok, true);
|
|
281
|
+
assert.ok(typeof j3.timestamp === "string");
|
|
282
|
+
// HEAD is allowed; the surface is read-only, not GET-only.
|
|
283
|
+
const rh = await get(`${srv.url}/api/health`, { method: "HEAD" });
|
|
284
|
+
assert.equal(rh.status, 200);
|
|
285
|
+
// 404 unknown
|
|
286
|
+
const r4 = await get(`${srv.url}/not-here`);
|
|
287
|
+
assert.equal(r4.status, 404);
|
|
288
|
+
// 405 POST rejected — nothing here writes.
|
|
289
|
+
const r5 = await get(`${srv.url}/api/harness`, { method: "POST" });
|
|
290
|
+
assert.equal(r5.status, 405);
|
|
291
|
+
assert.equal(r5.headers.get("allow"), "GET, HEAD");
|
|
292
|
+
const r6 = await get(`${srv.url}/`, { method: "DELETE" });
|
|
293
|
+
assert.equal(r6.status, 405);
|
|
294
|
+
console.log("✓ createRemoteServer ephemeral port shape + HTML + health + 404/405");
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
await srv.close();
|
|
298
|
+
await srv.close(); // second close no throw
|
|
299
|
+
console.log("✓ close frees port (idempotent)");
|
|
300
|
+
// verify port freed: can bind same port again quickly
|
|
301
|
+
const srv2 = await createRemoteServer({ projectDir: dir, host: "127.0.0.1", port: srv.port });
|
|
302
|
+
const r = await get(`${srv2.url}/api/harness`);
|
|
303
|
+
assert.equal(r.status, 200);
|
|
304
|
+
await srv2.close();
|
|
305
|
+
console.log("✓ close frees port -> re-bind same port works");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
rmSync(dir, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// ── a non-loopback bind has to be asked for explicitly ─────────────────────
|
|
313
|
+
{
|
|
314
|
+
const dir = tmpProject();
|
|
315
|
+
const prior = process.env.INFINITY_HARNESS_ALLOW_REMOTE;
|
|
316
|
+
try {
|
|
317
|
+
delete process.env.INFINITY_HARNESS_ALLOW_REMOTE;
|
|
318
|
+
await assert.rejects(() => createRemoteServer({ projectDir: dir, host: "0.0.0.0", port: 0 }), /refusing to bind the dashboard to 0.0.0.0/, "the page exposes source paths and task text");
|
|
319
|
+
console.log("✓ non-loopback binds are refused by default");
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
if (prior === undefined)
|
|
323
|
+
delete process.env.INFINITY_HARNESS_ALLOW_REMOTE;
|
|
324
|
+
else
|
|
325
|
+
process.env.INFINITY_HARNESS_ALLOW_REMOTE = prior;
|
|
326
|
+
rmSync(dir, { recursive: true, force: true });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// ── concurrent fetches x5 serialized ────────────────────────────────────────
|
|
330
|
+
{
|
|
331
|
+
const dir = tmpProject();
|
|
332
|
+
try {
|
|
333
|
+
writeFeatureList(dir, baseFeatureList());
|
|
334
|
+
const srv = await createRemoteServer({ projectDir: dir, host: "127.0.0.1", port: 0 });
|
|
335
|
+
try {
|
|
336
|
+
const results = await Promise.all(Array.from({ length: 5 }, () => get(`${srv.url}/api/harness`).then((r) => r.json())));
|
|
337
|
+
for (const j of results)
|
|
338
|
+
assert.equal(j.baseRevision, 7);
|
|
339
|
+
console.log("✓ concurrent fetches x5 serialized (all same baseRevision)");
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
await srv.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
rmSync(dir, { recursive: true, force: true });
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ── read-only: repeated reads do not mutate baseRevision / file ─────────────
|
|
350
|
+
{
|
|
351
|
+
const dir = tmpProject();
|
|
352
|
+
try {
|
|
353
|
+
const fl = baseFeatureList();
|
|
354
|
+
writeFeatureList(dir, fl);
|
|
355
|
+
writeConfig(dir, (c) => {
|
|
356
|
+
c.currentPhase = "build";
|
|
357
|
+
});
|
|
358
|
+
const planBefore = readFileSync(join(dir, "harness", "features", "feature-list.json"), "utf-8");
|
|
359
|
+
const configBefore = readFileSync(join(dir, "harness", "config.json"), "utf-8");
|
|
360
|
+
const srv = await createRemoteServer({ projectDir: dir, host: "127.0.0.1", port: 0 });
|
|
361
|
+
try {
|
|
362
|
+
for (let i = 0; i < 3; i++) {
|
|
363
|
+
const j = await get(`${srv.url}/api/harness`).then((r) => r.json());
|
|
364
|
+
assert.equal(j.baseRevision, 7);
|
|
365
|
+
await get(`${srv.url}/`).then((r) => r.text());
|
|
366
|
+
}
|
|
367
|
+
assert.equal(readFileSync(join(dir, "harness", "features", "feature-list.json"), "utf-8"), planBefore, "the plan file is byte-identical after serving the dashboard");
|
|
368
|
+
assert.equal(readFileSync(join(dir, "harness", "config.json"), "utf-8"), configBefore, "the config file is byte-identical too — no gate history, no counters");
|
|
369
|
+
console.log("✓ read-only: GETs do not mutate any harness file");
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
await srv.close();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
rmSync(dir, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
console.log("All remote tests PASS");
|
|
380
|
+
//# sourceMappingURL=remote.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.test.js","sourceRoot":"","sources":["../../tests/remote.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync, readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { amendPlan, loadReplanHistory, clearReplanHistory } from "../src/replan.js";
|
|
6
|
+
function tmpProject() {
|
|
7
|
+
const d = mkdtempSync(join(tmpdir(), "pi-replan-"));
|
|
8
|
+
mkdirSync(join(d, "harness", "features"), { recursive: true });
|
|
9
|
+
return d;
|
|
10
|
+
}
|
|
11
|
+
function writeFeatureList(dir, tasks, baseRevision = 0) {
|
|
12
|
+
const features = [{ id: "feature-006", name: "F", passes: false, tasks }];
|
|
13
|
+
writeFileSync(join(dir, "harness", "features", "feature-list.json"), JSON.stringify({ version: "0.1", baseRevision, features, goals: [], sprints: [] }, null, 2));
|
|
14
|
+
writeFileSync(join(dir, "harness", "config.json"), JSON.stringify({ replan: { maxReplans: 10 } }, null, 2));
|
|
15
|
+
}
|
|
16
|
+
// --- amendPlan adds task, bumps, guards cycle/missing, maxReplans ---
|
|
17
|
+
{
|
|
18
|
+
const proj = tmpProject();
|
|
19
|
+
try {
|
|
20
|
+
writeFeatureList(proj, [
|
|
21
|
+
{ id: "task-012", key: "schema-rework", description: "A", status: "complete", dependsOn: [] },
|
|
22
|
+
], 2);
|
|
23
|
+
// add task
|
|
24
|
+
let res = await amendPlan({ projectDir: proj, reason: "add feature", addFeatures: [{ id: "feature-007", name: "NewF", tasks: [{ id: "task-014", key: "router-core", description: "C", status: "pending", dependsOn: [] }] }] });
|
|
25
|
+
assert.equal(res.added.features, 1);
|
|
26
|
+
assert.equal(res.baseRevision, 3);
|
|
27
|
+
// add task to existing feature
|
|
28
|
+
res = await amendPlan({ projectDir: proj, reason: "add task", addTasks: [{ featureId: "feature-006", task: { id: "task-013", key: "rework-replan", description: "B", status: "pending", dependsOn: ["schema-rework"] } }] });
|
|
29
|
+
assert.equal(res.added.tasks, 1);
|
|
30
|
+
assert.equal(res.baseRevision, 4);
|
|
31
|
+
// cycle rejected - add tasks that create cycle
|
|
32
|
+
try {
|
|
33
|
+
await amendPlan({ projectDir: proj, addTasks: [
|
|
34
|
+
{ featureId: "feature-006", task: { id: "task-015", key: "cyc-a", description: "Cyc", status: "pending", dependsOn: ["cyc-b"] } },
|
|
35
|
+
{ featureId: "feature-006", task: { id: "task-016", key: "cyc-b", description: "CycB", status: "pending", dependsOn: ["cyc-a"] } },
|
|
36
|
+
] });
|
|
37
|
+
assert.fail("expected cycle");
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
assert.match(e.message, /cycle/);
|
|
41
|
+
}
|
|
42
|
+
// missing dep rejected
|
|
43
|
+
try {
|
|
44
|
+
await amendPlan({ projectDir: proj, addTasks: [{ featureId: "feature-006", task: { id: "task-017", key: "bad", description: "Bad", status: "pending", dependsOn: ["nonexistent"] } }] });
|
|
45
|
+
assert.fail("expected missing");
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
assert.match(e.message, /missing|references/);
|
|
49
|
+
}
|
|
50
|
+
// maxReplans guard - separate project with max 2
|
|
51
|
+
{
|
|
52
|
+
const proj2 = tmpProject();
|
|
53
|
+
try {
|
|
54
|
+
writeFileSync(join(proj2, "harness", "config.json"), JSON.stringify({ replan: { maxReplans: 2 } }, null, 2));
|
|
55
|
+
writeFileSync(join(proj2, "harness", "features", "feature-list.json"), JSON.stringify({ version: "0.1", baseRevision: 0, features: [{ id: "feature-006", name: "F", passes: false, tasks: [{ id: "task-012", key: "schema-rework", description: "A", status: "complete", dependsOn: [] }] }], goals: [], sprints: [] }, null, 2));
|
|
56
|
+
await amendPlan({ projectDir: proj2, addTasks: [{ featureId: "feature-006", task: { id: "task-013", key: "rework-replan", description: "B2", status: "pending", dependsOn: ["schema-rework"] } }] });
|
|
57
|
+
await amendPlan({ projectDir: proj2, addTasks: [{ featureId: "feature-006", task: { id: "task-014", key: "router-core", description: "C", status: "pending" } }] });
|
|
58
|
+
try {
|
|
59
|
+
await amendPlan({ projectDir: proj2, addTasks: [{ featureId: "feature-006", task: { id: "task-018", key: "extra", description: "Extra", status: "pending" } }] });
|
|
60
|
+
assert.fail("expected maxReplans");
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
assert.match(e.message, /maxReplans/);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
rmSync(proj2, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// history
|
|
71
|
+
const hist = loadReplanHistory(proj);
|
|
72
|
+
assert.equal(hist.length, 2);
|
|
73
|
+
await clearReplanHistory(proj);
|
|
74
|
+
assert.ok(!existsSync(join(proj, "harness/replan.json")));
|
|
75
|
+
assert.deepEqual(loadReplanHistory(proj), []);
|
|
76
|
+
console.log("✓ amendPlan add, bump, cycle/missing, maxReplans, history");
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
rmSync(proj, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// --- a status alias is not an unresolved dependency ---
|
|
83
|
+
// This module used to parse feature-list.json raw, so a task stored as "done"
|
|
84
|
+
// never compared equal to "complete" and validateDeps rejected every amendment
|
|
85
|
+
// to a plan that used an alias — with the nonsense message that a task in
|
|
86
|
+
// flight had unresolved dependencies. Plan reads now go through the core
|
|
87
|
+
// loader, which normalises aliases on the way in.
|
|
88
|
+
{
|
|
89
|
+
const proj = tmpProject();
|
|
90
|
+
try {
|
|
91
|
+
writeFeatureList(proj, [
|
|
92
|
+
{ id: "task-012", key: "schema-rework", description: "A", status: "done", dependsOn: [] },
|
|
93
|
+
{ id: "task-013", key: "rework-replan", description: "B", status: "in_progress", dependsOn: ["schema-rework"] },
|
|
94
|
+
], 1);
|
|
95
|
+
const res = await amendPlan({
|
|
96
|
+
projectDir: proj,
|
|
97
|
+
reason: "alias",
|
|
98
|
+
addTasks: [{ featureId: "feature-006", task: { id: "task-014", key: "router-core", description: "C", status: "pending" } }],
|
|
99
|
+
});
|
|
100
|
+
assert.equal(res.added.tasks, 1, "an aliased status must not read as an unresolved dependency");
|
|
101
|
+
assert.equal(res.baseRevision, 2);
|
|
102
|
+
const file = JSON.parse(readFileSync(join(proj, "harness", "features", "feature-list.json"), "utf-8"));
|
|
103
|
+
const statuses = new Map(file.features[0].tasks.map((t) => [t.key, t.status]));
|
|
104
|
+
assert.equal(statuses.get("schema-rework"), "complete", '"done" is normalised to the canonical status on write');
|
|
105
|
+
assert.equal(statuses.get("rework-replan"), "in_progress", "the dependent is left where it was");
|
|
106
|
+
console.log("✓ status aliases normalise instead of failing dependency validation");
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
rmSync(proj, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
console.log("All replan tests PASS");
|
|
113
|
+
//# sourceMappingURL=replan.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replan.test.js","sourceRoot":"","sources":["../../tests/replan.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { shouldBounceToRework } from "../src/review.js";
|
|
6
|
+
function tmpProject(cfg) {
|
|
7
|
+
const d = mkdtempSync(join(tmpdir(), "pi-review-"));
|
|
8
|
+
mkdirSync(join(d, "harness"), { recursive: true });
|
|
9
|
+
writeFileSync(join(d, "harness/config.json"), JSON.stringify(cfg, null, 2));
|
|
10
|
+
return d;
|
|
11
|
+
}
|
|
12
|
+
{
|
|
13
|
+
const proj = tmpProject({ review: { allowBackward: true, maxBounces: 2, bounceRequiresDelta: true } });
|
|
14
|
+
try {
|
|
15
|
+
let r = shouldBounceToRework({ projectDir: proj, fileDelta: true, bounceCount: 0 });
|
|
16
|
+
assert.equal(r.shouldBounce, true, "fileDelta true -> bounce");
|
|
17
|
+
r = shouldBounceToRework({ projectDir: proj, fileDelta: false, bounceCount: 0 });
|
|
18
|
+
assert.equal(r.shouldBounce, false, "fileDelta false ignored when bounceRequiresDelta");
|
|
19
|
+
assert.match(r.reason, /bounceRequiresDelta/);
|
|
20
|
+
console.log("✓ bounceRequiresDelta fileDelta");
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
rmSync(proj, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
{
|
|
27
|
+
const proj = tmpProject({ review: { allowBackward: true, maxBounces: 2, bounceRequiresDelta: false } });
|
|
28
|
+
try {
|
|
29
|
+
const r = shouldBounceToRework({ projectDir: proj, fileDelta: false, bounceCount: 0 });
|
|
30
|
+
assert.equal(r.shouldBounce, true, "bounceRequiresDelta false -> fileDelta false still bounces");
|
|
31
|
+
console.log("✓ bounceRequiresDelta false ignores fileDelta");
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
rmSync(proj, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
{
|
|
38
|
+
const proj = tmpProject({ review: { allowBackward: true, maxBounces: 2, bounceRequiresDelta: true } });
|
|
39
|
+
try {
|
|
40
|
+
const r = shouldBounceToRework({ projectDir: proj, fileDelta: true, bounceCount: 2 });
|
|
41
|
+
assert.equal(r.shouldBounce, false, "maxBounces guard");
|
|
42
|
+
assert.match(r.reason, /maxBounces/);
|
|
43
|
+
console.log("✓ maxBounces guard");
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
rmSync(proj, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
{
|
|
50
|
+
const proj = tmpProject({ review: { allowBackward: false, maxBounces: 2, bounceRequiresDelta: true } });
|
|
51
|
+
try {
|
|
52
|
+
const r = shouldBounceToRework({ projectDir: proj, fileDelta: true, bounceCount: 0 });
|
|
53
|
+
assert.equal(r.shouldBounce, false);
|
|
54
|
+
assert.match(r.reason, /allowBackward/);
|
|
55
|
+
console.log("✓ allowBackward false");
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
rmSync(proj, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
{
|
|
62
|
+
// fresh-read: toggle config between calls
|
|
63
|
+
const proj = tmpProject({ review: { allowBackward: true, maxBounces: 2, bounceRequiresDelta: true } });
|
|
64
|
+
try {
|
|
65
|
+
let r = shouldBounceToRework({ projectDir: proj, fileDelta: false, bounceCount: 0 });
|
|
66
|
+
assert.equal(r.shouldBounce, false);
|
|
67
|
+
writeFileSync(join(proj, "harness/config.json"), JSON.stringify({ review: { allowBackward: true, maxBounces: 2, bounceRequiresDelta: false } }, null, 2));
|
|
68
|
+
r = shouldBounceToRework({ projectDir: proj, fileDelta: false, bounceCount: 0 });
|
|
69
|
+
assert.equal(r.shouldBounce, true, "fresh-read toggle");
|
|
70
|
+
console.log("✓ fresh-read toggle");
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
rmSync(proj, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
console.log("All review tests PASS");
|
|
77
|
+
//# sourceMappingURL=review.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.test.js","sourceRoot":"","sources":["../../tests/review.test.ts"],"names":[],"mappings":"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"}
|