gsd-pi 2.37.0 → 2.37.1-dev.3bbb0a9
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 +21 -20
- package/dist/onboarding.js +1 -0
- package/dist/resources/extensions/cmux/package.json +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +67 -1
- package/dist/resources/extensions/gsd/auto-loop.js +18 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +14 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +91 -2
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto.js +42 -5
- package/dist/resources/extensions/gsd/commands.js +80 -33
- package/dist/resources/extensions/gsd/doctor-providers.js +35 -1
- package/dist/resources/extensions/gsd/files.js +41 -0
- package/dist/resources/extensions/gsd/git-service.js +9 -1
- package/dist/resources/extensions/gsd/history.js +2 -1
- package/dist/resources/extensions/gsd/metrics.js +4 -2
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/preferences-types.js +2 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +42 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/session-lock.js +26 -6
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/shared/format-utils.js +5 -41
- package/dist/resources/extensions/shared/layout-utils.js +46 -0
- package/dist/resources/extensions/shared/mod.js +2 -1
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -4
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +8 -4
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/package.json +7 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +93 -0
- package/src/resources/extensions/gsd/auto-loop.ts +24 -6
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +125 -3
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto.ts +56 -5
- package/src/resources/extensions/gsd/commands.ts +85 -31
- package/src/resources/extensions/gsd/doctor-providers.ts +38 -1
- package/src/resources/extensions/gsd/files.ts +45 -0
- package/src/resources/extensions/gsd/git-service.ts +12 -1
- package/src/resources/extensions/gsd/history.ts +2 -1
- package/src/resources/extensions/gsd/metrics.ts +4 -2
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-types.ts +5 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +41 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +41 -0
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/session-lock.ts +41 -6
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +37 -1
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +19 -0
- package/src/resources/extensions/gsd/tests/cmux.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +108 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +45 -0
- package/src/resources/extensions/gsd/types.ts +43 -0
- package/src/resources/extensions/shared/format-utils.ts +5 -44
- package/src/resources/extensions/shared/layout-utils.ts +49 -0
- package/src/resources/extensions/shared/mod.ts +7 -4
- package/src/resources/extensions/shared/tests/format-utils.test.ts +5 -3
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type AgentEndEvent,
|
|
15
15
|
type LoopDeps,
|
|
16
16
|
} from "../auto-loop.js";
|
|
17
|
+
import type { SessionLockStatus } from "../session-lock.js";
|
|
17
18
|
|
|
18
19
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
@@ -341,7 +342,7 @@ function makeMockDeps(
|
|
|
341
342
|
preDispatchHealthGate: async () => ({ proceed: true, fixesApplied: [] }),
|
|
342
343
|
syncProjectRootToWorktree: () => {},
|
|
343
344
|
checkResourcesStale: () => null,
|
|
344
|
-
validateSessionLock: () => true,
|
|
345
|
+
validateSessionLock: () => ({ valid: true } as SessionLockStatus),
|
|
345
346
|
updateSessionLock: () => {
|
|
346
347
|
callLog.push("updateSessionLock");
|
|
347
348
|
},
|
|
@@ -532,6 +533,41 @@ test("autoLoop exits on terminal complete state", async (t) => {
|
|
|
532
533
|
);
|
|
533
534
|
});
|
|
534
535
|
|
|
536
|
+
test("autoLoop passes structured session-lock failure details to the handler", async () => {
|
|
537
|
+
_resetPendingResolve();
|
|
538
|
+
|
|
539
|
+
const ctx = makeMockCtx();
|
|
540
|
+
ctx.ui.setStatus = () => {};
|
|
541
|
+
const pi = makeMockPi();
|
|
542
|
+
const s = makeLoopSession();
|
|
543
|
+
let observedLockStatus: SessionLockStatus | undefined;
|
|
544
|
+
|
|
545
|
+
const deps = makeMockDeps({
|
|
546
|
+
validateSessionLock: () =>
|
|
547
|
+
({
|
|
548
|
+
valid: false,
|
|
549
|
+
failureReason: "compromised",
|
|
550
|
+
expectedPid: process.pid,
|
|
551
|
+
}) as SessionLockStatus,
|
|
552
|
+
handleLostSessionLock: (_ctx, lockStatus) => {
|
|
553
|
+
observedLockStatus = lockStatus;
|
|
554
|
+
deps.callLog.push("handleLostSessionLock");
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await autoLoop(ctx, pi, s, deps);
|
|
559
|
+
|
|
560
|
+
assert.deepEqual(observedLockStatus, {
|
|
561
|
+
valid: false,
|
|
562
|
+
failureReason: "compromised",
|
|
563
|
+
expectedPid: process.pid,
|
|
564
|
+
});
|
|
565
|
+
assert.ok(
|
|
566
|
+
!deps.callLog.includes("resolveDispatch"),
|
|
567
|
+
"should stop before dispatch after lock validation fails",
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
535
571
|
test("autoLoop exits on terminal blocked state", async (t) => {
|
|
536
572
|
_resetPendingResolve();
|
|
537
573
|
|
|
@@ -153,6 +153,25 @@ async function main(): Promise<void> {
|
|
|
153
153
|
// After teardown, originalBase should be null
|
|
154
154
|
assertEq(getAutoWorktreeOriginalBase(), null, "no split-brain: originalBase cleared");
|
|
155
155
|
|
|
156
|
+
// ─── #1526: getMainBranch returns milestone branch in auto-worktree ──
|
|
157
|
+
console.log("\n=== #1526: getMainBranch() returns milestone/<MID> in auto-worktree ===");
|
|
158
|
+
{
|
|
159
|
+
const { GitServiceImpl } = await import("../git-service.ts");
|
|
160
|
+
|
|
161
|
+
// Create worktree
|
|
162
|
+
const wtPath = createAutoWorktree(tempDir, "M005");
|
|
163
|
+
// Don't set main_branch pref so getMainBranch falls through to worktree detection
|
|
164
|
+
const gitService = new GitServiceImpl(wtPath);
|
|
165
|
+
gitService.setMilestoneId("M005");
|
|
166
|
+
|
|
167
|
+
// Verify getMainBranch returns the milestone branch
|
|
168
|
+
const mainBranch = gitService.getMainBranch();
|
|
169
|
+
assertEq(mainBranch, "milestone/M005", "getMainBranch returns milestone/<MID> in auto-worktree");
|
|
170
|
+
|
|
171
|
+
// Cleanup
|
|
172
|
+
teardownAutoWorktree(tempDir, "M005");
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
// ─── #778: reconcile plan checkboxes on re-attach ─────────────────
|
|
157
176
|
console.log("\n=== #778: reconcile plan checkboxes on re-attach ===");
|
|
158
177
|
{
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import test from "node:test";
|
|
1
|
+
import test, { describe } from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import {
|
|
4
7
|
buildCmuxProgress,
|
|
5
8
|
buildCmuxStatusLabel,
|
|
@@ -96,3 +99,24 @@ test("buildCmuxStatusLabel and progress prefer deepest active unit", () => {
|
|
|
96
99
|
assert.equal(buildCmuxStatusLabel(state), "M001 S02/T03 · executing");
|
|
97
100
|
assert.deepEqual(buildCmuxProgress(state), { value: 0.4, label: "2/5 tasks" });
|
|
98
101
|
});
|
|
102
|
+
|
|
103
|
+
describe("cmux extension discovery opt-out", () => {
|
|
104
|
+
test("cmux directory has package.json with pi manifest to prevent auto-discovery as extension", () => {
|
|
105
|
+
const cmuxDir = path.resolve(
|
|
106
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
107
|
+
"../../cmux",
|
|
108
|
+
);
|
|
109
|
+
const pkgPath = path.join(cmuxDir, "package.json");
|
|
110
|
+
assert.ok(fs.existsSync(pkgPath), `${pkgPath} must exist`);
|
|
111
|
+
|
|
112
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
113
|
+
assert.ok(
|
|
114
|
+
pkg.pi !== undefined && typeof pkg.pi === "object",
|
|
115
|
+
'package.json must have a "pi" field to opt out of extension auto-discovery',
|
|
116
|
+
);
|
|
117
|
+
assert.ok(
|
|
118
|
+
!pkg.pi.extensions?.length,
|
|
119
|
+
"pi.extensions must be empty or absent — cmux is a library, not an extension",
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -184,7 +184,7 @@ test("runProviderChecks detects Anthropic key from ANTHROPIC_API_KEY env var", (
|
|
|
184
184
|
// Isolate from real HOME so loadEffectiveGSDPreferences returns null (default → anthropic)
|
|
185
185
|
// and auth.json lookups hit an empty directory.
|
|
186
186
|
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-env-test-")));
|
|
187
|
-
withEnv({ ANTHROPIC_API_KEY: "sk-ant-test-key", HOME: tmpHome }, () => {
|
|
187
|
+
withEnv({ ANTHROPIC_API_KEY: "sk-ant-test-key", ANTHROPIC_OAUTH_TOKEN: undefined, HOME: tmpHome }, () => {
|
|
188
188
|
try {
|
|
189
189
|
const results = runProviderChecks();
|
|
190
190
|
const anthropic = results.find(r => r.name === "anthropic");
|
|
@@ -199,7 +199,15 @@ test("runProviderChecks detects Anthropic key from ANTHROPIC_API_KEY env var", (
|
|
|
199
199
|
|
|
200
200
|
test("runProviderChecks returns error for Anthropic when no key present", () => {
|
|
201
201
|
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-test-")));
|
|
202
|
-
withEnv({
|
|
202
|
+
withEnv({
|
|
203
|
+
ANTHROPIC_API_KEY: undefined,
|
|
204
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
205
|
+
// Clear cross-provider routing env vars (GitHub Copilot can serve Claude models)
|
|
206
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
207
|
+
GH_TOKEN: undefined,
|
|
208
|
+
GITHUB_TOKEN: undefined,
|
|
209
|
+
HOME: tmpHome,
|
|
210
|
+
}, () => {
|
|
203
211
|
try {
|
|
204
212
|
const results = runProviderChecks();
|
|
205
213
|
const anthropic = results.find(r => r.name === "anthropic");
|
|
@@ -275,7 +283,7 @@ test("runProviderChecks detects key from auth.json", () => {
|
|
|
275
283
|
});
|
|
276
284
|
|
|
277
285
|
test("runProviderChecks ignores empty placeholder keys in auth.json", () => {
|
|
278
|
-
withEnv({ ANTHROPIC_API_KEY: undefined }, () => {
|
|
286
|
+
withEnv({ ANTHROPIC_API_KEY: undefined, ANTHROPIC_OAUTH_TOKEN: undefined, COPILOT_GITHUB_TOKEN: undefined, GH_TOKEN: undefined, GITHUB_TOKEN: undefined }, () => {
|
|
279
287
|
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-test-")));
|
|
280
288
|
const agentDir = join(tmpHome, ".gsd", "agent");
|
|
281
289
|
mkdirSync(agentDir, { recursive: true });
|
|
@@ -296,3 +304,100 @@ test("runProviderChecks ignores empty placeholder keys in auth.json", () => {
|
|
|
296
304
|
rmSync(tmpHome, { recursive: true, force: true });
|
|
297
305
|
});
|
|
298
306
|
});
|
|
307
|
+
|
|
308
|
+
// ─── runProviderChecks — cross-provider routing ──────────────────────────────
|
|
309
|
+
|
|
310
|
+
test("runProviderChecks reports ok for Anthropic when GitHub Copilot env var is set", () => {
|
|
311
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-copilot-test-")));
|
|
312
|
+
withEnv({
|
|
313
|
+
ANTHROPIC_API_KEY: undefined,
|
|
314
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
315
|
+
COPILOT_GITHUB_TOKEN: "ghu_copilot-token",
|
|
316
|
+
GH_TOKEN: undefined,
|
|
317
|
+
GITHUB_TOKEN: undefined,
|
|
318
|
+
HOME: tmpHome,
|
|
319
|
+
}, () => {
|
|
320
|
+
try {
|
|
321
|
+
const results = runProviderChecks();
|
|
322
|
+
const anthropic = results.find(r => r.name === "anthropic");
|
|
323
|
+
assert.ok(anthropic, "anthropic result should exist");
|
|
324
|
+
assert.equal(anthropic!.status, "ok", "should be ok when Copilot auth is available");
|
|
325
|
+
assert.ok(anthropic!.message.includes("GitHub Copilot"), "should mention cross-provider source");
|
|
326
|
+
} finally {
|
|
327
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("runProviderChecks reports ok for Anthropic via GITHUB_TOKEN cross-provider routing", () => {
|
|
333
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-ghtoken-test-")));
|
|
334
|
+
withEnv({
|
|
335
|
+
ANTHROPIC_API_KEY: undefined,
|
|
336
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
337
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
338
|
+
GH_TOKEN: undefined,
|
|
339
|
+
GITHUB_TOKEN: "ghp_github-token",
|
|
340
|
+
HOME: tmpHome,
|
|
341
|
+
}, () => {
|
|
342
|
+
try {
|
|
343
|
+
const results = runProviderChecks();
|
|
344
|
+
const anthropic = results.find(r => r.name === "anthropic");
|
|
345
|
+
assert.ok(anthropic, "anthropic result should exist");
|
|
346
|
+
assert.equal(anthropic!.status, "ok", "should be ok when GITHUB_TOKEN provides Copilot access");
|
|
347
|
+
} finally {
|
|
348
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("runProviderChecks detects ANTHROPIC_OAUTH_TOKEN as valid Anthropic auth", () => {
|
|
354
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-oauth-test-")));
|
|
355
|
+
withEnv({
|
|
356
|
+
ANTHROPIC_API_KEY: undefined,
|
|
357
|
+
ANTHROPIC_OAUTH_TOKEN: "oauth-token-test",
|
|
358
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
359
|
+
GH_TOKEN: undefined,
|
|
360
|
+
GITHUB_TOKEN: undefined,
|
|
361
|
+
HOME: tmpHome,
|
|
362
|
+
}, () => {
|
|
363
|
+
try {
|
|
364
|
+
const results = runProviderChecks();
|
|
365
|
+
const anthropic = results.find(r => r.name === "anthropic");
|
|
366
|
+
assert.ok(anthropic, "anthropic result should exist");
|
|
367
|
+
assert.equal(anthropic!.status, "ok", "should be ok when ANTHROPIC_OAUTH_TOKEN is set");
|
|
368
|
+
assert.ok(anthropic!.message.includes("env"), "should report env source");
|
|
369
|
+
} finally {
|
|
370
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("runProviderChecks reports ok via Copilot auth.json for Anthropic", () => {
|
|
376
|
+
withEnv({
|
|
377
|
+
ANTHROPIC_API_KEY: undefined,
|
|
378
|
+
ANTHROPIC_OAUTH_TOKEN: undefined,
|
|
379
|
+
COPILOT_GITHUB_TOKEN: undefined,
|
|
380
|
+
GH_TOKEN: undefined,
|
|
381
|
+
GITHUB_TOKEN: undefined,
|
|
382
|
+
}, () => {
|
|
383
|
+
const tmpHome = realpathSync(mkdtempSync(join(tmpdir(), "gsd-providers-copilot-auth-test-")));
|
|
384
|
+
const agentDir = join(tmpHome, ".gsd", "agent");
|
|
385
|
+
mkdirSync(agentDir, { recursive: true });
|
|
386
|
+
|
|
387
|
+
// GitHub Copilot OAuth in auth.json
|
|
388
|
+
const authData = {
|
|
389
|
+
"github-copilot": { type: "oauth", apiKey: "ghu_copilot-key", expires: Date.now() + 3_600_000 },
|
|
390
|
+
};
|
|
391
|
+
writeFileSync(join(agentDir, "auth.json"), JSON.stringify(authData));
|
|
392
|
+
|
|
393
|
+
withEnv({ HOME: tmpHome }, () => {
|
|
394
|
+
const results = runProviderChecks();
|
|
395
|
+
const anthropic = results.find(r => r.name === "anthropic");
|
|
396
|
+
assert.ok(anthropic, "anthropic result should exist");
|
|
397
|
+
assert.equal(anthropic!.status, "ok", "should be ok when Copilot is authenticated in auth.json");
|
|
398
|
+
assert.ok(anthropic!.message.includes("GitHub Copilot"), "should mention Copilot as source");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
rmSync(tmpHome, { recursive: true, force: true });
|
|
402
|
+
});
|
|
403
|
+
});
|
|
@@ -360,4 +360,115 @@ console.log('\n=== Clean slice plan: no plan-quality issues ===');
|
|
|
360
360
|
assertEq(planQualityIssues.length, 0, 'clean slice plan produces no empty_task_entry issues');
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
364
|
+
// validateTaskPlanContent — missing output file paths
|
|
365
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
366
|
+
|
|
367
|
+
console.log('\n=== validateTaskPlanContent: missing output file paths ===');
|
|
368
|
+
{
|
|
369
|
+
const content = `# T01: Some Task
|
|
370
|
+
|
|
371
|
+
## Description
|
|
372
|
+
|
|
373
|
+
Do something.
|
|
374
|
+
|
|
375
|
+
## Steps
|
|
376
|
+
|
|
377
|
+
1. Do the thing
|
|
378
|
+
|
|
379
|
+
## Verification
|
|
380
|
+
|
|
381
|
+
- Check it works
|
|
382
|
+
|
|
383
|
+
## Expected Output
|
|
384
|
+
|
|
385
|
+
This task produces the main output.
|
|
386
|
+
`;
|
|
387
|
+
|
|
388
|
+
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
|
389
|
+
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
|
390
|
+
assertTrue(outputIssues.length >= 1, 'Expected Output without file paths triggers missing_output_file_paths');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
console.log('\n=== validateTaskPlanContent: valid output file paths ===');
|
|
394
|
+
{
|
|
395
|
+
const content = `# T01: Some Task
|
|
396
|
+
|
|
397
|
+
## Description
|
|
398
|
+
|
|
399
|
+
Do something.
|
|
400
|
+
|
|
401
|
+
## Steps
|
|
402
|
+
|
|
403
|
+
1. Do the thing
|
|
404
|
+
|
|
405
|
+
## Verification
|
|
406
|
+
|
|
407
|
+
- Check it works
|
|
408
|
+
|
|
409
|
+
## Expected Output
|
|
410
|
+
|
|
411
|
+
- \`src/types.ts\` — New type definitions
|
|
412
|
+
`;
|
|
413
|
+
|
|
414
|
+
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
|
415
|
+
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
|
416
|
+
assertEq(outputIssues.length, 0, 'Expected Output with file paths does not trigger warning');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.log('\n=== validateTaskPlanContent: missing input file paths (info severity) ===');
|
|
420
|
+
{
|
|
421
|
+
const content = `# T01: Some Task
|
|
422
|
+
|
|
423
|
+
## Description
|
|
424
|
+
|
|
425
|
+
Do something.
|
|
426
|
+
|
|
427
|
+
## Steps
|
|
428
|
+
|
|
429
|
+
1. Do the thing
|
|
430
|
+
|
|
431
|
+
## Verification
|
|
432
|
+
|
|
433
|
+
- Check it works
|
|
434
|
+
|
|
435
|
+
## Inputs
|
|
436
|
+
|
|
437
|
+
Prior task summary insights about the architecture.
|
|
438
|
+
|
|
439
|
+
## Expected Output
|
|
440
|
+
|
|
441
|
+
- \`src/output.ts\` — Output file
|
|
442
|
+
`;
|
|
443
|
+
|
|
444
|
+
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
|
445
|
+
const inputIssues = issues.filter(i => i.ruleId === 'missing_input_file_paths');
|
|
446
|
+
assertTrue(inputIssues.length >= 1, 'Inputs without file paths triggers missing_input_file_paths');
|
|
447
|
+
if (inputIssues.length > 0) {
|
|
448
|
+
assertEq(inputIssues[0].severity, 'info', 'missing_input_file_paths is info severity (not warning)');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log('\n=== validateTaskPlanContent: no Expected Output section at all ===');
|
|
453
|
+
{
|
|
454
|
+
const content = `# T01: Some Task
|
|
455
|
+
|
|
456
|
+
## Description
|
|
457
|
+
|
|
458
|
+
Do something.
|
|
459
|
+
|
|
460
|
+
## Steps
|
|
461
|
+
|
|
462
|
+
1. Do the thing
|
|
463
|
+
|
|
464
|
+
## Verification
|
|
465
|
+
|
|
466
|
+
- Check it works
|
|
467
|
+
`;
|
|
468
|
+
|
|
469
|
+
const issues = validateTaskPlanContent('T01-PLAN.md', content);
|
|
470
|
+
const outputIssues = issues.filter(i => i.ruleId === 'missing_output_file_paths');
|
|
471
|
+
assertTrue(outputIssues.length >= 1, 'Missing Expected Output section triggers missing_output_file_paths');
|
|
472
|
+
}
|
|
473
|
+
|
|
363
474
|
report();
|