gsd-pi 2.42.0-dev.eedc83f → 2.43.0-dev.5717b75
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/cli.js +3 -2
- package/dist/loader.js +3 -1
- package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
- package/dist/resources/extensions/async-jobs/index.js +2 -0
- package/dist/resources/extensions/gsd/auto/phases.js +1 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
- package/dist/resources/extensions/gsd/auto-start.js +8 -11
- package/dist/resources/extensions/gsd/git-service.js +3 -69
- package/dist/resources/extensions/gsd/worktree.js +2 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
- package/src/resources/extensions/async-jobs/index.ts +1 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
- package/src/resources/extensions/gsd/auto/phases.ts +1 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
- package/src/resources/extensions/gsd/auto-start.ts +7 -10
- package/src/resources/extensions/gsd/git-service.ts +2 -72
- package/src/resources/extensions/gsd/gitignore.ts +1 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +9 -14
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
- package/src/resources/extensions/gsd/worktree-resolver.ts +0 -1
- package/src/resources/extensions/gsd/worktree.ts +2 -2
- /package/dist/web/standalone/.next/static/{JUBX5FUR73jiViQU5a-Cx → 5ULZcR9XhHFzlAZFiSKRl}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{JUBX5FUR73jiViQU5a-Cx → 5ULZcR9XhHFzlAZFiSKRl}/_ssgManifest.js +0 -0
|
@@ -42,6 +42,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
42
42
|
|
|
43
43
|
manager = new AsyncJobManager({
|
|
44
44
|
onJobComplete: (job) => {
|
|
45
|
+
if (job.awaited) return;
|
|
45
46
|
const statusEmoji = job.status === "completed" ? "done" : "error";
|
|
46
47
|
const elapsed = ((Date.now() - job.startTime) / 1000).toFixed(1);
|
|
47
48
|
const output = job.status === "completed"
|
|
@@ -261,9 +261,7 @@ export async function runPreDispatch(
|
|
|
261
261
|
|
|
262
262
|
if (mid) {
|
|
263
263
|
if (deps.getIsolationMode() !== "none") {
|
|
264
|
-
deps.captureIntegrationBranch(s.basePath, mid
|
|
265
|
-
commitDocs: prefs?.git?.commit_docs,
|
|
266
|
-
});
|
|
264
|
+
deps.captureIntegrationBranch(s.basePath, mid);
|
|
267
265
|
}
|
|
268
266
|
deps.resolver.enterMilestone(mid, ctx.ui);
|
|
269
267
|
} else {
|
|
@@ -420,8 +420,6 @@ export function buildSkillActivationBlock(params: {
|
|
|
420
420
|
params.sliceTitle,
|
|
421
421
|
params.taskId,
|
|
422
422
|
params.taskTitle,
|
|
423
|
-
...(params.extraContext ?? []),
|
|
424
|
-
params.taskPlanContent ?? undefined,
|
|
425
423
|
);
|
|
426
424
|
|
|
427
425
|
const visibleSkills = (typeof getLoadedSkills === 'function' ? getLoadedSkills() : []).filter(skill => !skill.disableModelInvocation);
|
|
@@ -452,12 +450,6 @@ export function buildSkillActivationBlock(params: {
|
|
|
452
450
|
}
|
|
453
451
|
}
|
|
454
452
|
|
|
455
|
-
for (const skill of visibleSkills) {
|
|
456
|
-
if (skillMatchesContext(skill, contextTokens)) {
|
|
457
|
-
matched.add(normalizeSkillReference(skill.name));
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
453
|
const ordered = [...matched]
|
|
462
454
|
.filter(name => installedNames.has(name) && !avoided.has(name))
|
|
463
455
|
.sort();
|
|
@@ -983,11 +975,7 @@ export async function buildPlanSlicePrompt(
|
|
|
983
975
|
const executorContextConstraints = formatExecutorConstraints();
|
|
984
976
|
|
|
985
977
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
986
|
-
const
|
|
987
|
-
const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
|
|
988
|
-
const commitInstruction = commitDocsEnabled
|
|
989
|
-
? `Commit the plan files only: \`git add --force ${relSlicePath(base, mid, sid)}/ .gsd/DECISIONS.md .gitignore && git commit -m "docs(${sid}): add slice plan"\`. Do not stage .gsd/STATE.md or other runtime files — the system manages those.`
|
|
990
|
-
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
978
|
+
const commitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
|
|
991
979
|
return loadPrompt("plan-slice", {
|
|
992
980
|
workingDirectory: base,
|
|
993
981
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
@@ -1485,11 +1473,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1485
1473
|
// Non-fatal — captures module may not be available
|
|
1486
1474
|
}
|
|
1487
1475
|
|
|
1488
|
-
const
|
|
1489
|
-
const reassessCommitDocsEnabled = reassessPrefs?.preferences?.git?.commit_docs !== false;
|
|
1490
|
-
const reassessCommitInstruction = reassessCommitDocsEnabled
|
|
1491
|
-
? `Commit: \`docs(${mid}): reassess roadmap after ${completedSliceId}\`. Stage only the .gsd/milestones/ files you changed — do not stage .gsd/STATE.md or other runtime files.`
|
|
1492
|
-
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
1476
|
+
const reassessCommitInstruction = "Do not commit — .gsd/ planning docs are managed externally and not tracked in git.";
|
|
1493
1477
|
|
|
1494
1478
|
return loadPrompt("reassess-roadmap", {
|
|
1495
1479
|
workingDirectory: base,
|
|
@@ -167,22 +167,19 @@ export async function bootstrapAutoSession(
|
|
|
167
167
|
// ensureGitignore checks for git-tracked .gsd/ files and skips the
|
|
168
168
|
// ".gsd" pattern if the project intentionally tracks .gsd/ in git.
|
|
169
169
|
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
170
|
-
const commitDocs = gitPrefs?.commit_docs;
|
|
171
170
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
172
|
-
ensureGitignore(base, {
|
|
171
|
+
ensureGitignore(base, { manageGitignore });
|
|
173
172
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
174
173
|
|
|
175
174
|
// Bootstrap .gsd/ if it doesn't exist
|
|
176
175
|
const gsdDir = join(base, ".gsd");
|
|
177
176
|
if (!existsSync(gsdDir)) {
|
|
178
177
|
mkdirSync(join(gsdDir, "milestones"), { recursive: true });
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/* nothing to commit */
|
|
185
|
-
}
|
|
178
|
+
try {
|
|
179
|
+
nativeAddAll(base);
|
|
180
|
+
nativeCommit(base, "chore: init gsd");
|
|
181
|
+
} catch {
|
|
182
|
+
/* nothing to commit */
|
|
186
183
|
}
|
|
187
184
|
}
|
|
188
185
|
|
|
@@ -487,7 +484,7 @@ export async function bootstrapAutoSession(
|
|
|
487
484
|
// Capture integration branch
|
|
488
485
|
if (s.currentMilestoneId) {
|
|
489
486
|
if (getIsolationMode() !== "none") {
|
|
490
|
-
captureIntegrationBranch(base, s.currentMilestoneId
|
|
487
|
+
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
491
488
|
}
|
|
492
489
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
493
490
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { execFileSync, execSync } from "node:child_process";
|
|
12
|
-
import { existsSync,
|
|
13
|
-
import { join
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
14
|
import { gsdRoot } from "./paths.js";
|
|
15
15
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
16
16
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -245,7 +245,6 @@ export function writeIntegrationBranch(
|
|
|
245
245
|
basePath: string,
|
|
246
246
|
milestoneId: string,
|
|
247
247
|
branch: string,
|
|
248
|
-
_options?: { commitDocs?: boolean },
|
|
249
248
|
): void {
|
|
250
249
|
// Don't record slice branches as the integration target
|
|
251
250
|
if (SLICE_BRANCH_RE.test(branch)) return;
|
|
@@ -486,80 +485,11 @@ export class GitServiceImpl {
|
|
|
486
485
|
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
487
486
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
488
487
|
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
489
|
-
|
|
490
|
-
// Force-add .gsd/milestones/ when .gsd is a symlink (#2104).
|
|
491
|
-
// When .gsd is a symlink (external state projects), ensureGitignore adds
|
|
492
|
-
// `.gsd` to .gitignore. The nativeAddAllWithExclusions call above falls
|
|
493
|
-
// back to plain `git add -A` (symlink pathspec rejection), which respects
|
|
494
|
-
// .gitignore and silently skips new .gsd/milestones/ files.
|
|
495
|
-
//
|
|
496
|
-
// `git add -f` also fails with "beyond a symbolic link", so we use
|
|
497
|
-
// `git hash-object -w` + `git update-index --add --cacheinfo` to bypass
|
|
498
|
-
// the symlink restriction entirely. This stages each milestone artifact
|
|
499
|
-
// individually by hashing the file content and updating the index directly.
|
|
500
|
-
const gsdPath = join(this.basePath, ".gsd");
|
|
501
|
-
const milestonesDir = join(gsdPath, "milestones");
|
|
502
|
-
try {
|
|
503
|
-
if (
|
|
504
|
-
existsSync(gsdPath) &&
|
|
505
|
-
lstatSync(gsdPath).isSymbolicLink() &&
|
|
506
|
-
existsSync(milestonesDir)
|
|
507
|
-
) {
|
|
508
|
-
this._forceAddMilestoneArtifacts(milestonesDir);
|
|
509
|
-
}
|
|
510
|
-
} catch {
|
|
511
|
-
// Non-fatal: if force-add fails, the commit proceeds without these files.
|
|
512
|
-
// This matches existing behavior where milestone artifacts were silently
|
|
513
|
-
// omitted — but now we at least attempt to include them.
|
|
514
|
-
}
|
|
515
488
|
}
|
|
516
489
|
|
|
517
490
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
518
491
|
private _runtimeFilesCleanedUp = false;
|
|
519
492
|
|
|
520
|
-
/**
|
|
521
|
-
* Recursively collect all files under a directory.
|
|
522
|
-
* Returns paths relative to `basePath` (e.g. ".gsd/milestones/M009/SUMMARY.md").
|
|
523
|
-
*/
|
|
524
|
-
private _collectFiles(dir: string): string[] {
|
|
525
|
-
const files: string[] = [];
|
|
526
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
527
|
-
const full = join(dir, entry.name);
|
|
528
|
-
if (entry.isDirectory()) {
|
|
529
|
-
files.push(...this._collectFiles(full));
|
|
530
|
-
} else if (entry.isFile()) {
|
|
531
|
-
files.push(relative(this.basePath, full));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return files;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Stage milestone artifacts through a symlinked .gsd directory (#2104).
|
|
539
|
-
*
|
|
540
|
-
* `git add` (even with `-f`) refuses to stage files "beyond a symbolic link".
|
|
541
|
-
* This method bypasses that restriction by hashing each file with
|
|
542
|
-
* `git hash-object -w` and inserting the blob into the index with
|
|
543
|
-
* `git update-index --add --cacheinfo 100644 <hash> <path>`.
|
|
544
|
-
*/
|
|
545
|
-
private _forceAddMilestoneArtifacts(milestonesDir: string): void {
|
|
546
|
-
const files = this._collectFiles(milestonesDir);
|
|
547
|
-
for (const filePath of files) {
|
|
548
|
-
const hash = execFileSync("git", ["hash-object", "-w", filePath], {
|
|
549
|
-
cwd: this.basePath,
|
|
550
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
551
|
-
encoding: "utf-8",
|
|
552
|
-
env: GIT_NO_PROMPT_ENV,
|
|
553
|
-
}).trim();
|
|
554
|
-
execFileSync("git", ["update-index", "--add", "--cacheinfo", "100644", hash, filePath], {
|
|
555
|
-
cwd: this.basePath,
|
|
556
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
557
|
-
encoding: "utf-8",
|
|
558
|
-
env: GIT_NO_PROMPT_ENV,
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
493
|
/**
|
|
564
494
|
* Stage files (smart staging) and commit.
|
|
565
495
|
* Returns the commit message string on success, or null if nothing to commit.
|
|
@@ -137,7 +137,7 @@ export function hasGitTrackedGsdFiles(basePath: string): boolean {
|
|
|
137
137
|
*/
|
|
138
138
|
export function ensureGitignore(
|
|
139
139
|
basePath: string,
|
|
140
|
-
options?: { manageGitignore?: boolean
|
|
140
|
+
options?: { manageGitignore?: boolean },
|
|
141
141
|
): boolean {
|
|
142
142
|
// If manage_gitignore is explicitly false, do not touch .gitignore at all
|
|
143
143
|
if (options?.manageGitignore === false) return false;
|
|
@@ -1411,16 +1411,14 @@ async function main(): Promise<void> {
|
|
|
1411
1411
|
rmSync(repo, { recursive: true, force: true });
|
|
1412
1412
|
}
|
|
1413
1413
|
|
|
1414
|
-
// ─── autoCommit: symlinked .gsd
|
|
1414
|
+
// ─── autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247) ──
|
|
1415
1415
|
|
|
1416
|
-
console.log("\n=== autoCommit: symlinked .gsd
|
|
1416
|
+
console.log("\n=== autoCommit: symlinked .gsd does NOT stage milestone artifacts (#2247) ===");
|
|
1417
1417
|
|
|
1418
1418
|
{
|
|
1419
|
-
//
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1422
|
-
// 2. `.gsd` is in .gitignore → new .gsd/ files are invisible to `git add`
|
|
1423
|
-
// The fix: smartStage() force-adds .gsd/milestones/ after the normal staging.
|
|
1419
|
+
// When .gsd is a symlink (external state project), .gsd/ files live outside
|
|
1420
|
+
// the repo by design. smartStage() must NOT force-stage them into git — the
|
|
1421
|
+
// .gitignore exclusion is correct and intentional.
|
|
1424
1422
|
const repo = initTempRepo();
|
|
1425
1423
|
|
|
1426
1424
|
// Create an external .gsd directory and symlink it into the repo
|
|
@@ -1433,7 +1431,8 @@ async function main(): Promise<void> {
|
|
|
1433
1431
|
|
|
1434
1432
|
// .gitignore blocks .gsd (as ensureGitignore would do for symlink projects)
|
|
1435
1433
|
writeFileSync(join(repo, ".gitignore"), ".gsd\n");
|
|
1436
|
-
run(
|
|
1434
|
+
run('git add .gitignore', repo);
|
|
1435
|
+
run('git commit -m "add gitignore"', repo);
|
|
1437
1436
|
|
|
1438
1437
|
// Simulate new milestone artifacts created during execution
|
|
1439
1438
|
writeFileSync(join(externalGsd, "milestones", "M009", "M009-SUMMARY.md"), "# M009 Summary");
|
|
@@ -1449,12 +1448,8 @@ async function main(): Promise<void> {
|
|
|
1449
1448
|
|
|
1450
1449
|
const committed = run("git show --name-only HEAD", repo);
|
|
1451
1450
|
assertTrue(committed.includes("src/feature.ts"), "symlink autoCommit: source file committed");
|
|
1452
|
-
assertTrue(committed.includes(".gsd/milestones/
|
|
1453
|
-
"symlink autoCommit:
|
|
1454
|
-
assertTrue(committed.includes(".gsd/milestones/M009/S01-SUMMARY.md"),
|
|
1455
|
-
"symlink autoCommit: new S01-SUMMARY.md is committed");
|
|
1456
|
-
assertTrue(committed.includes(".gsd/milestones/M009/T01-VERIFY.json"),
|
|
1457
|
-
"symlink autoCommit: new T01-VERIFY.json is committed");
|
|
1451
|
+
assertTrue(!committed.includes(".gsd/milestones/"),
|
|
1452
|
+
"symlink autoCommit: .gsd/milestones/ files are NOT staged (external state stays external)");
|
|
1458
1453
|
|
|
1459
1454
|
try { rmSync(repo, { recursive: true, force: true }); } catch {}
|
|
1460
1455
|
try { rmSync(externalGsd, { recursive: true, force: true }); } catch {}
|
|
@@ -39,7 +39,7 @@ function buildBlock(
|
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
test("buildSkillActivationBlock
|
|
42
|
+
test("buildSkillActivationBlock does not auto-activate skills via broad context heuristic", () => {
|
|
43
43
|
const base = makeTempBase();
|
|
44
44
|
try {
|
|
45
45
|
writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
|
|
@@ -52,7 +52,29 @@ test("buildSkillActivationBlock matches installed skills from task context", ()
|
|
|
52
52
|
taskTitle: "Implement React settings panel",
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
// Skills should not be activated just because their name appears in task context.
|
|
56
|
+
// Activation requires explicit preference sources (always_use, skill_rules, prefer_skills, skills_used).
|
|
57
|
+
assert.equal(result, "");
|
|
58
|
+
} finally {
|
|
59
|
+
cleanup(base);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("buildSkillActivationBlock activates skills via prefer_skills when context matches", () => {
|
|
64
|
+
const base = makeTempBase();
|
|
65
|
+
try {
|
|
66
|
+
writeSkill(base, "react", "Use for React components, hooks, JSX, and frontend UI work.");
|
|
67
|
+
writeSkill(base, "swiftui", "Use for SwiftUI views, iOS layout, and Apple platform UI work.");
|
|
68
|
+
loadOnlyTestSkills(base);
|
|
69
|
+
|
|
70
|
+
const result = buildBlock(base, {
|
|
71
|
+
sliceTitle: "Build React dashboard",
|
|
72
|
+
taskId: "T01",
|
|
73
|
+
taskTitle: "Implement React settings panel",
|
|
74
|
+
}, {
|
|
75
|
+
prefer_skills: ["react"],
|
|
76
|
+
});
|
|
77
|
+
|
|
56
78
|
assert.match(result, /Call Skill\('react'\)/);
|
|
57
79
|
assert.doesNotMatch(result, /swiftui/);
|
|
58
80
|
} finally {
|
|
@@ -105,7 +127,7 @@ test("buildSkillActivationBlock includes skill_rules matches and task-plan skill
|
|
|
105
127
|
}
|
|
106
128
|
});
|
|
107
129
|
|
|
108
|
-
test("buildSkillActivationBlock honors avoid_skills", () => {
|
|
130
|
+
test("buildSkillActivationBlock honors avoid_skills against always_use_skills", () => {
|
|
109
131
|
const base = makeTempBase();
|
|
110
132
|
try {
|
|
111
133
|
writeSkill(base, "react", "Use for React components and frontend UI work.");
|
|
@@ -114,6 +136,7 @@ test("buildSkillActivationBlock honors avoid_skills", () => {
|
|
|
114
136
|
const result = buildBlock(base, {
|
|
115
137
|
taskTitle: "Implement React settings panel",
|
|
116
138
|
}, {
|
|
139
|
+
always_use_skills: ["react"],
|
|
117
140
|
avoid_skills: ["react"],
|
|
118
141
|
});
|
|
119
142
|
|
|
@@ -138,3 +161,33 @@ test("buildSkillActivationBlock falls back cleanly when nothing matches", () =>
|
|
|
138
161
|
cleanup(base);
|
|
139
162
|
}
|
|
140
163
|
});
|
|
164
|
+
|
|
165
|
+
test("buildSkillActivationBlock does not activate skills from extraContext or taskPlanContent body", () => {
|
|
166
|
+
const base = makeTempBase();
|
|
167
|
+
try {
|
|
168
|
+
writeSkill(base, "xcode-build", "Use for Xcode build workflows and iOS compilation.");
|
|
169
|
+
writeSkill(base, "ableton-lom", "Use for Ableton Live Object Model scripting.");
|
|
170
|
+
writeSkill(base, "frontend-design", "Use for frontend design systems and UI components.");
|
|
171
|
+
loadOnlyTestSkills(base);
|
|
172
|
+
|
|
173
|
+
const taskPlan = [
|
|
174
|
+
"---",
|
|
175
|
+
"skills_used: []",
|
|
176
|
+
"---",
|
|
177
|
+
"# T01: Build the API endpoint",
|
|
178
|
+
"Use xcode-build patterns and frontend-design tokens.",
|
|
179
|
+
].join("\n");
|
|
180
|
+
|
|
181
|
+
const result = buildBlock(base, {
|
|
182
|
+
taskTitle: "Build REST API",
|
|
183
|
+
extraContext: ["Build workflow for iOS and Ableton integration testing"],
|
|
184
|
+
taskPlanContent: taskPlan,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// None of these skills should activate — extraContext and taskPlanContent body
|
|
188
|
+
// must not be used for heuristic matching.
|
|
189
|
+
assert.equal(result, "");
|
|
190
|
+
} finally {
|
|
191
|
+
cleanup(base);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
@@ -139,11 +139,10 @@ function makeDeps(
|
|
|
139
139
|
captureIntegrationBranch: (
|
|
140
140
|
basePath: string,
|
|
141
141
|
mid: string | undefined,
|
|
142
|
-
opts?: { commitDocs?: boolean },
|
|
143
142
|
) => {
|
|
144
143
|
calls.push({
|
|
145
144
|
fn: "captureIntegrationBranch",
|
|
146
|
-
args: [basePath, mid
|
|
145
|
+
args: [basePath, mid],
|
|
147
146
|
});
|
|
148
147
|
},
|
|
149
148
|
...overrides,
|
|
@@ -57,13 +57,13 @@ export function setActiveMilestoneId(basePath: string, milestoneId: string | nul
|
|
|
57
57
|
* record when the user starts from a different branch (#300). Always a no-op
|
|
58
58
|
* if on a GSD slice branch.
|
|
59
59
|
*/
|
|
60
|
-
export function captureIntegrationBranch(basePath: string, milestoneId: string
|
|
60
|
+
export function captureIntegrationBranch(basePath: string, milestoneId: string): void {
|
|
61
61
|
// In a worktree, the base branch is implicit (worktree/<name>).
|
|
62
62
|
// Writing it to META.json would leave stale metadata after merge back to main.
|
|
63
63
|
if (detectWorktreeName(basePath)) return;
|
|
64
64
|
const svc = getService(basePath);
|
|
65
65
|
const current = svc.getCurrentBranch();
|
|
66
|
-
writeIntegrationBranch(basePath, milestoneId, current
|
|
66
|
+
writeIntegrationBranch(basePath, milestoneId, current);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// ─── Pure Utility Functions (unchanged) ────────────────────────────────────
|
|
File without changes
|
|
File without changes
|