guardian-framework 0.1.36 → 0.1.37

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 CHANGED
@@ -1335,7 +1335,7 @@ __export(exports_package, {
1335
1335
  bin: () => bin,
1336
1336
  author: () => author
1337
1337
  });
1338
- var name = "guardian-framework", version = "0.1.36", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1338
+ var name = "guardian-framework", version = "0.1.37", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1339
1339
  var init_package = __esm(() => {
1340
1340
  exports = {
1341
1341
  ".": {
package/dist/exports.js CHANGED
@@ -995,7 +995,7 @@ __export(exports_package, {
995
995
  bin: () => bin,
996
996
  author: () => author
997
997
  });
998
- var name = "guardian-framework", version = "0.1.36", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
998
+ var name = "guardian-framework", version = "0.1.37", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
999
999
  var init_package = __esm(() => {
1000
1000
  exports = {
1001
1001
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -1,4 +1,7 @@
1
1
  /**
2
+ * Canonical Reference: .pi/architecture/modules/core-libraries.md
3
+ * Last Sync: 2026-05-31
4
+
2
5
  * Pipeline Extension for pi
3
6
  *
4
7
  * Multi-step workflow engine that iterates over items (issues, tasks, etc.)
@@ -18,7 +21,7 @@
18
21
  */
19
22
 
20
23
  import { execSync } from "node:child_process";
21
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
24
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
22
25
  import { dirname, join } from "node:path";
23
26
 
24
27
  // ── Validator Scripts ──
@@ -332,19 +335,199 @@ class PipelineManager {
332
335
  this.state = loadPipelineState(cwd);
333
336
  }
334
337
 
335
- getState(): PipelineState | null {
336
- return this.state;
338
+ /**
339
+ * Reconcile pipeline state against ground truth (git, GitHub, validators).
340
+ * Also updates module markdown docs when items complete.
341
+ * Call this on session_start and before any state-dependent operation.
342
+ */
343
+ reconcile(): void {
344
+ if (!this.state) return;
345
+ let changed = false;
346
+
347
+ for (let i = 0; i < this.state.items.length; i++) {
348
+ const item = this.state.items[i];
349
+ const slug = item.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
350
+ const branch = `feat/${slug}`;
351
+ const result = this.state.results.find((r) => r.item === item);
352
+
353
+ // Check ground truth
354
+ const branchExists = this.gitBranchExists(branch);
355
+ const prMerged = this.ghPrMerged(branch);
356
+ const prExists = this.ghPrExists(branch);
357
+ const hasCommits = branchExists && this.branchHasCommits(branch);
358
+
359
+ if (prMerged) {
360
+ // All steps done — mark item complete
361
+ if (!result || result.status !== "done") {
362
+ const itemResult: ItemResult = {
363
+ item,
364
+ status: "done",
365
+ stepResults: this.state.steps.map((s) => ({ step: s.name, status: "passed", reason: "reconciled" })),
366
+ };
367
+ // Replace or add
368
+ const idx = this.state.results.findIndex((r) => r.item === item);
369
+ if (idx >= 0) this.state.results[idx] = itemResult;
370
+ else this.state.results.push(itemResult);
371
+ changed = true;
372
+ this.updateModuleDocStatus(item, "implemented");
373
+ }
374
+ } else if (prExists) {
375
+ // PR open — implement+validate+create-mr done, waiting on merge
376
+ if (!result || result.status === "skipped") {
377
+ const steps = this.state.steps;
378
+ const stepResults = steps.map((s, idx) => ({
379
+ step: s.name,
380
+ status: idx < steps.length - 1 ? "passed" as const : "skipped" as const,
381
+ reason: idx < steps.length - 1 ? "reconciled" : "waiting for merge",
382
+ }));
383
+ const itemResult: ItemResult = { item, status: "in-progress", stepResults };
384
+ const idx = this.state.results.findIndex((r) => r.item === item);
385
+ if (idx >= 0) this.state.results[idx] = itemResult;
386
+ else this.state.results.push(itemResult);
387
+ changed = true;
388
+ }
389
+ } else if (hasCommits) {
390
+ // Branch with commits — implement done, rest pending
391
+ if (!result || result.status === "skipped") {
392
+ const stepResults = [{ step: "implement", status: "passed" as const, reason: "reconciled" }];
393
+ const itemResult: ItemResult = { item, status: "in-progress", stepResults };
394
+ const idx = this.state.results.findIndex((r) => r.item === item);
395
+ if (idx >= 0) this.state.results[idx] = itemResult;
396
+ else this.state.results.push(itemResult);
397
+ changed = true;
398
+ }
399
+ }
400
+ }
401
+
402
+ // If all items done, update overall status
403
+ if (this.state.results.every((r) => r.status === "done") && this.state.items.length > 0) {
404
+ this.state.status = "done";
405
+ this.state.currentItemIndex = this.state.items.length;
406
+ this.state.currentStepIndex = 0;
407
+ changed = true;
408
+ }
409
+
410
+ // Reposition currentItemIndex to first non-done item
411
+ const firstUndone = this.state.items.findIndex((item, idx) => {
412
+ const r = this.state!.results.find((res) => res.item === item);
413
+ return !r || r.status !== "done";
414
+ });
415
+ if (firstUndone >= 0) {
416
+ this.state.currentItemIndex = firstUndone;
417
+ this.state.currentStepIndex = 0;
418
+ } else if (this.state.items.length > 0) {
419
+ // All done
420
+ this.state.currentItemIndex = this.state.items.length;
421
+ this.state.currentStepIndex = 0;
422
+ }
423
+
424
+ if (changed) {
425
+ this.state.updatedAt = new Date().toISOString();
426
+ savePipelineState(this.cwd, this.state);
427
+ }
428
+ }
429
+
430
+ private gitBranchExists(branch: string): boolean {
431
+ try {
432
+ const result = execSync(`git branch -a --list "${branch}" 2>/dev/null`, { cwd: this.cwd, encoding: "utf-8" });
433
+ return result.trim().length > 0;
434
+ } catch { return false; }
435
+ }
436
+
437
+ private branchHasCommits(branch: string): boolean {
438
+ try {
439
+ const result = execSync(`git log --oneline "${branch}" 2>/dev/null | head -1`, { cwd: this.cwd, encoding: "utf-8" });
440
+ return result.trim().length > 0;
441
+ } catch { return false; }
442
+ }
443
+
444
+ private ghPrExists(branch: string): boolean {
445
+ try {
446
+ const result = execSync(`gh pr list --head "${branch}" --json number --jq "length" 2>/dev/null`, { cwd: this.cwd, encoding: "utf-8" });
447
+ return parseInt(result.trim()) > 0;
448
+ } catch { return false; }
449
+ }
450
+
451
+ private ghPrMerged(branch: string): boolean {
452
+ try {
453
+ const result = execSync(`gh pr list --head "${branch}" --json state,mergedAt --jq ".[] | select(.state==\"MERGED\") | .mergedAt" 2>/dev/null`, { cwd: this.cwd, encoding: "utf-8" });
454
+ return result.trim().length > 0;
455
+ } catch { return false; }
337
456
  }
338
457
 
339
- reload(): void {
340
- const raw = loadPipelineState(this.cwd);
341
- if (raw) {
342
- // Migrate old string-step format to StepConfig objects
343
- if (raw.steps.length > 0 && typeof raw.steps[0] === "string") {
344
- raw.steps = buildSteps(raw.steps as unknown as string[]);
458
+ /**
459
+ * Update module markdown doc status when an item is confirmed done.
460
+ * Looks for .pi/architecture/modules/<item>.md and replaces `status: planned` with `status: implemented`.
461
+ */
462
+ private updateModuleDocStatus(item: string, newStatus: string): void {
463
+ const modulesDir = join(this.cwd, ".pi", "architecture", "modules");
464
+ try {
465
+ const files = readdirSync(modulesDir).filter((f) => f.endsWith(".md") && f !== "module-template.md");
466
+ // Match by module name (kebab-case from item name)
467
+ const slug = item.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
468
+ for (const file of files) {
469
+ if (file.replace(".md", "") === slug || file.includes(slug)) {
470
+ const filePath = join(modulesDir, file);
471
+ let content = readFileSync(filePath, "utf-8");
472
+ const oldStatus = content.match(/\*\*Status:\*\*\s*(\w+)/);
473
+ if (oldStatus && oldStatus[1] !== newStatus) {
474
+ content = content.replace(/\*\*Status:\*\*\s*\w+/, `**Status:** ${newStatus}`);
475
+ // Also update status: planned → status: implemented markers
476
+ content = content.replace(/^status: planned$/gm, `status: ${newStatus}`);
477
+ writeFileSync(filePath, content, "utf-8");
478
+ }
479
+ break;
480
+ }
345
481
  }
482
+ } catch { /* modules dir may not exist */ }
483
+ }
484
+
485
+ /**
486
+ * Verify the current step's work actually happened before advancing.
487
+ * For "implement" step: check git branch has commits.
488
+ * For "validate" step: run acceptance gates.
489
+ * For "create-mr" step: check PR exists.
490
+ * For "merge" step: check PR is merged.
491
+ */
492
+ verifyCurrentStep(): { verified: boolean; reason: string } {
493
+ if (!this.state) return { verified: false, reason: "No pipeline state" };
494
+ const item = this.state.items[this.state.currentItemIndex];
495
+ const step = this.state.steps[this.state.currentStepIndex];
496
+ if (!step) return { verified: true, reason: "No current step" };
497
+
498
+ const slug = item.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
499
+ const branch = `feat/${slug}`;
500
+
501
+ switch (step.name) {
502
+ case "implement": {
503
+ if (!this.gitBranchExists(branch) || !this.branchHasCommits(branch)) {
504
+ return { verified: false, reason: `No commits found on branch ${branch}. Implement the feature first.` };
505
+ }
506
+ return { verified: true, reason: "Commits found on branch" };
507
+ }
508
+ case "create-mr": {
509
+ if (!this.ghPrExists(branch)) {
510
+ return { verified: false, reason: `No PR found for branch ${branch}. Create a PR first.` };
511
+ }
512
+ return { verified: true, reason: "PR exists" };
513
+ }
514
+ case "merge": {
515
+ if (!this.ghPrMerged(branch)) {
516
+ return { verified: false, reason: `PR for ${branch} is not merged yet.` };
517
+ }
518
+ return { verified: true, reason: "PR merged" };
519
+ }
520
+ default:
521
+ return { verified: true, reason: "Unknown step — skipping verification" };
346
522
  }
347
- this.state = raw;
523
+ }
524
+
525
+ getState(): PipelineState | null {
526
+ return this.state;
527
+ }
528
+
529
+ getState(): PipelineState | null {
530
+ return this.state;
348
531
  }
349
532
 
350
533
  create(
@@ -424,6 +607,28 @@ class PipelineManager {
424
607
 
425
608
  advanceStep(): void {
426
609
  if (!this.state) return;
610
+
611
+ // Before advancing, verify the current step's work actually happened
612
+ if (this.state.currentStepIndex < this.state.steps.length) {
613
+ const { verified, reason } = this.verifyCurrentStep();
614
+ if (!verified) {
615
+ // Reality disagrees with state — reconcile from git and refuse to advance
616
+ this.reconcile();
617
+ return; // don't advance, state was wrong
618
+ }
619
+
620
+ // Auto-run acceptance gates for validate steps
621
+ const step = this.state.steps[this.state.currentStepIndex];
622
+ if (step.name === "validate" && step.acceptance.type !== "none") {
623
+ const { allPassed, errors } = this.runAcceptanceGates(step);
624
+ if (!allPassed) {
625
+ const item = this.state.items[this.state.currentItemIndex];
626
+ this.markStepFailed(step.name, `Acceptance failed: ${errors.join("; ")}`);
627
+ return; // don't advance, acceptance failed
628
+ }
629
+ }
630
+ }
631
+
427
632
  this.state.currentStepIndex++;
428
633
  this.state.updatedAt = new Date().toISOString();
429
634
 
@@ -432,8 +637,6 @@ class PipelineManager {
432
637
  const item = this.state.items[this.state.currentItemIndex];
433
638
  let result = this.state.results.find((r) => r.item === item);
434
639
 
435
- // If no result entry exists (e.g. advanceStep called before any steps ran),
436
- // create one so the item is tracked.
437
640
  if (!result) {
438
641
  result = { item, status: "skipped", stepResults: [] };
439
642
  this.state.results.push(result);
@@ -444,6 +647,8 @@ class PipelineManager {
444
647
  result.status = "skipped";
445
648
  } else {
446
649
  result.status = "done";
650
+ // Update module doc to implemented
651
+ this.updateModuleDocStatus(item, "implemented");
447
652
  }
448
653
  } else {
449
654
  result.status = "failed";
@@ -456,39 +661,47 @@ class PipelineManager {
456
661
 
457
662
  if (this.state.currentItemIndex >= this.state.items.length) {
458
663
  this.state.status = "done";
664
+ }
665
+ }
459
666
 
460
- // Auto-advance roadmap when module epic completes
461
- const roadmapFile = join(this.cwd, ".pi/.guardian-roadmap-state.json");
462
- if (existsSync(roadmapFile)) {
463
- try {
464
- const roadmap = JSON.parse(readFileSync(roadmapFile, "utf-8"));
465
- const epicName = this.state.name;
466
- const currentPhase = roadmap.phases?.[roadmap.currentPhaseIndex];
467
- if (currentPhase && epicName) {
468
- // Mark module as completed
469
- const completed = new Set(currentPhase.completedModules || []);
470
- completed.add(epicName);
471
- currentPhase.completedModules = Array.from(completed);
472
-
473
- // Check if all modules done → mark phase done
474
- const allDone = currentPhase.modules.every(
475
- (m: { name: string }) => completed.has(m.name),
476
- );
477
- if (allDone) {
478
- currentPhase.status = "done";
479
- }
480
-
481
- roadmap.updatedAt = new Date().toISOString();
482
- writeFileSync(roadmapFile, JSON.stringify(roadmap, null, 2));
483
- }
484
- } catch (err) {
485
- // Silently fail roadmap update is best-effort
486
- }
667
+ savePipelineState(this.cwd, this.state);
668
+ }
669
+
670
+ /**
671
+ * Run acceptance gates for a step and return pass/fail.
672
+ */
673
+ private runAcceptanceGates(step: StepConfig): { allPassed: boolean; errors: string[] } {
674
+ const errors: string[] = [];
675
+ const acceptance = step.acceptance;
676
+
677
+ if (acceptance.type === "none") return { allPassed: true, errors: [] };
678
+
679
+ if (acceptance.type === "shell") {
680
+ try {
681
+ execSync(`bash -c "${acceptance.command}"`, { cwd: this.cwd, timeout: 300_000, encoding: "utf-8" });
682
+ return { allPassed: true, errors: [] };
683
+ } catch (e: unknown) {
684
+ const err = e as { stdout?: string };
685
+ return { allPassed: false, errors: [err.stdout?.slice(0, 200) || "shell failed"] };
686
+ }
687
+ }
688
+
689
+ if (acceptance.type === "validator") {
690
+ for (const validator of acceptance.validators) {
691
+ const scriptPath = VALIDATOR_SCRIPTS[validator];
692
+ if (!scriptPath) { errors.push(`Unknown validator: ${validator}`); continue; }
693
+ try {
694
+ execSync(`bash -c "${scriptPath}"`, { cwd: this.cwd, timeout: 120_000, encoding: "utf-8" });
695
+ } catch (e: unknown) {
696
+ const err = e as { stdout?: string };
697
+ errors.push(`${validator}: ${(err.stdout || "").slice(0, 200)}`);
487
698
  }
488
699
  }
700
+ return { allPassed: errors.length === 0, errors };
489
701
  }
490
702
 
491
- savePipelineState(this.cwd, this.state);
703
+ // LLM gates — can't auto-run, skip verification
704
+ return { allPassed: true, errors: [] };
492
705
  }
493
706
 
494
707
  markStepFailed(stepName: string, reason: string): void {
@@ -519,88 +732,6 @@ class PipelineManager {
519
732
  }
520
733
  }
521
734
 
522
- // ── Step-specific instruction builders ──
523
-
524
- function getDefaultBranch(cwd: string): string {
525
- try {
526
- const symRef = execSync("git symbolic-ref refs/remotes/origin/HEAD", {
527
- cwd, encoding: "utf-8"
528
- }).trim();
529
- return symRef.replace(/^refs\/remotes\/origin\//, "");
530
- } catch {
531
- return "main";
532
- }
533
- }
534
-
535
- function buildImplementInstructions(issueId: string, branchExists: boolean): string {
536
- const slug = issueId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
537
- const branch = `feat/${slug}`;
538
-
539
- if (branchExists) {
540
- return [
541
- "## MANDATORY GIT WORKFLOW",
542
- "",
543
- `Branch \`${branch}\` already exists. Continue working on it.`,
544
- "",
545
- "1. **CHECKOUT:** `git checkout " + branch + "`",
546
- "2. Implement the issue requirements",
547
- "3. **COMMIT every logical chunk:** `git add <files> && git commit -m \"feat: description\"`",
548
- "4. **PUSH regularly:** `git push origin " + branch + "`",
549
- "5. When implementation complete, commit+push final changes then call `pipeline_run_acceptance`",
550
- "",
551
- "⛔ DO NOT skip git. Every commit must be pushed. This is MANDATORY.",
552
- ].join("\n");
553
- }
554
-
555
- return [
556
- "## MANDATORY GIT WORKFLOW — DO NOT SKIP",
557
- "",
558
- "1. **CREATE BRANCH:** `git checkout -b " + branch + "`",
559
- "2. Implement the issue requirements",
560
- "3. **COMMIT every logical chunk:** `git add <files> && git commit -m \"feat: description\"`",
561
- "4. **PUSH regularly:** `git push origin " + branch + "`",
562
- "5. When implementation complete, commit+push final changes then call `pipeline_run_acceptance`",
563
- "",
564
- "⛔ **CRITICAL:** You MUST create a branch, commit, and push. The pipeline validates git history.",
565
- "Do NOT implement directly on main. Do NOT skip git operations.",
566
- ].join("\n");
567
- }
568
-
569
- function buildCreateMRInstructions(issueId: string, repo: string, defaultBranch: string): string {
570
- const slug = issueId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
571
- const branch = `feat/${slug}`;
572
-
573
- return [
574
- "## MANDATORY: CREATE PULL REQUEST",
575
- "",
576
- "1. **ENSURE pushed:** `git push origin " + branch + "`",
577
- "2. **CREATE PR:** `gh pr create --base " + defaultBranch + " --head " + branch + " --title \"" + issueId + ": <description>\" --body \"Closes #<issue-number>\"`",
578
- "3. Wait for CI checks to pass on the PR",
579
- "4. If checks fail, fix, commit+push, re-check",
580
- "5. When PR is created and CI passes, call `pipeline_advance`",
581
- "",
582
- "⛔ **CRITICAL:** You MUST create a PR via `gh pr create`. Do NOT skip this.",
583
- "The repository is: " + (repo || "check guardian-manifest.json"),
584
- ].join("\n");
585
- }
586
-
587
- function buildMergeInstructions(issueId: string, defaultBranch: string): string {
588
- const slug = issueId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
589
- const branch = `feat/${slug}`;
590
-
591
- return [
592
- "## MANDATORY: MERGE PULL REQUEST",
593
- "",
594
- "1. Find the PR for branch `" + branch + "`: `gh pr list --head " + branch + " --json number --jq '.[0].number'`",
595
- "2. **MERGE PR:** `gh pr merge <PR_NUMBER> --squash --delete-branch`",
596
- "3. **CHECKOUT main:** `git checkout " + defaultBranch + "`",
597
- "4. **PULL latest:** `git pull origin " + defaultBranch + "`",
598
- "5. Call `pipeline_advance`",
599
- "",
600
- "⛔ **CRITICAL:** You MUST merge the PR via `gh pr merge`. Do NOT skip this.",
601
- ].join("\n");
602
- }
603
-
604
735
  // ── Extension ──
605
736
 
606
737
  export default function (pi: ExtensionAPI) {
@@ -608,6 +739,8 @@ export default function (pi: ExtensionAPI) {
608
739
 
609
740
  pi.on("session_start", async (_event, ctx) => {
610
741
  manager = new PipelineManager(ctx.cwd);
742
+ // Always reconcile pipeline state against ground truth on session start
743
+ manager.reconcile();
611
744
  const state = manager.getState();
612
745
  if (state && state.status !== "done" && state.status !== "aborted") {
613
746
  ctx.ui.setStatus("pipeline", statusLine(state));
@@ -619,7 +752,6 @@ export default function (pi: ExtensionAPI) {
619
752
  description: "Manage multi-step pipeline workflows",
620
753
  handler: async (args, ctx) => {
621
754
  if (!manager) manager = new PipelineManager(ctx.cwd);
622
- manager.reload();
623
755
  const state = manager.getState();
624
756
 
625
757
  // pi passes args as a string. Split into tokens.
@@ -754,7 +886,6 @@ export default function (pi: ExtensionAPI) {
754
886
  parameters: { type: "object", properties: {} },
755
887
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
756
888
  if (!manager) manager = new PipelineManager(ctx.cwd);
757
- manager.reload();
758
889
  const state = manager.getState();
759
890
  if (!state) {
760
891
  return { content: [{ type: "text" as const, text: "No active pipeline." }] };
@@ -776,7 +907,6 @@ export default function (pi: ExtensionAPI) {
776
907
  },
777
908
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
778
909
  if (!manager) manager = new PipelineManager(ctx.cwd);
779
- manager.reload();
780
910
  const state = manager.getState();
781
911
  if (!state || state.status !== "running") {
782
912
  return { content: [{ type: "text" as const, text: "No running pipeline." }] };
@@ -839,20 +969,12 @@ export default function (pi: ExtensionAPI) {
839
969
  `**Next task:** Item "${currentItem}" → Step: implement`,
840
970
  "",
841
971
  "**Instructions:**",
842
- "1. Create branch: `feat/${currentItem}`",
843
- "2. Review the issue context below",
844
- "3. Implement the component according to the issue spec",
845
- "4. Run `pipeline_run_acceptance` to validate",
846
- "5. Call `pipeline_advance` when done",
972
+ "1. Review the issue context below",
973
+ "2. Implement the component according to the issue spec",
974
+ "3. Run `pipeline_run_acceptance` to validate",
975
+ "4. Call `pipeline_advance` when done",
847
976
  "",
848
- "**Available tools:**",
849
- "- `pipeline_next_task` — get full context for current step",
850
- "- `pipeline_run_acceptance` — run validators (CI, tests, security, shell, LLM)",
851
- "- `pipeline_advance` — mark step passed, advance to next",
852
- "- `pipeline_fail` — mark step failed, skip remaining steps for this item",
853
- "- `pipeline_status` — check overall pipeline progress",
854
- "",
855
- "⚠️ **IMPORTANT:** After each step, call `pipeline_run_acceptance` then `pipeline_advance`. The pipeline flows: implement → validate → create-mr → merge. Continue through ALL items — do not stop after completing one.",
977
+ "⚠️ **IMPORTANT:** After you complete this item and call `pipeline_advance`, the pipeline will automatically advance to the next step. Continue this loop until all items are done. Do not stop after completing a single item — keep going through implement → validate → create-mr → merge for every item.",
856
978
  "",
857
979
  "---",
858
980
  "",
@@ -884,7 +1006,6 @@ export default function (pi: ExtensionAPI) {
884
1006
  },
885
1007
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
886
1008
  if (!manager) manager = new PipelineManager(ctx.cwd);
887
- manager.reload();
888
1009
  const state = manager.getState();
889
1010
  if (!state || state.status !== "running") {
890
1011
  return { content: [{ type: "text" as const, text: "No running pipeline." }] };
@@ -980,7 +1101,6 @@ export default function (pi: ExtensionAPI) {
980
1101
  },
981
1102
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
982
1103
  if (!manager) manager = new PipelineManager(ctx.cwd);
983
- manager.reload();
984
1104
  const state = manager.getState();
985
1105
  if (!state || state.status !== "running") {
986
1106
  return { content: [{ type: "text" as const, text: "No running pipeline." }] };
@@ -1020,30 +1140,6 @@ export default function (pi: ExtensionAPI) {
1020
1140
  }
1021
1141
  }
1022
1142
 
1023
- // Build step-specific git-mandatory instructions
1024
- const repo = readRepository(ctx.cwd) || "";
1025
- const defaultBranch = getDefaultBranch(ctx.cwd);
1026
- const slug = issueId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1027
- const branch = `feat/${slug}`;
1028
-
1029
- let stepInstructions = "";
1030
- if (step.name === "implement") {
1031
- let branchExists = false;
1032
- try {
1033
- execSync(`git rev-parse --verify ${branch}`, { cwd: ctx.cwd, encoding: "utf-8" });
1034
- branchExists = true;
1035
- } catch { /* doesn't exist */ }
1036
- stepInstructions = buildImplementInstructions(issueId, branchExists);
1037
- } else if (step.name === "validate") {
1038
- stepInstructions = "## VALIDATION STEP\n\n1. Ensure all changes committed+push to feature branch\n2. Call `pipeline_run_acceptance`\n3. If pass: `pipeline_advance`. If fail: fix, commit+push, re-run.\n\n⛔ All changes must be committed and pushed before validation.";
1039
- } else if (step.name === "create-mr") {
1040
- stepInstructions = buildCreateMRInstructions(issueId, repo, defaultBranch);
1041
- } else if (step.name === "merge") {
1042
- stepInstructions = buildMergeInstructions(issueId, defaultBranch);
1043
- } else {
1044
- stepInstructions = "## Instructions\n\n1. Complete the work for this step\n2. Commit and push all changes\n3. Call `pipeline_run_acceptance` then `pipeline_advance`";
1045
- }
1046
-
1047
1143
  const text = [
1048
1144
  "## Pipeline Task",
1049
1145
  "",
@@ -1054,10 +1150,6 @@ export default function (pi: ExtensionAPI) {
1054
1150
  "",
1055
1151
  "---",
1056
1152
  "",
1057
- stepInstructions,
1058
- "",
1059
- "---",
1060
- "",
1061
1153
  stepPrompt || "",
1062
1154
  "",
1063
1155
  "---",
@@ -1065,6 +1157,14 @@ export default function (pi: ExtensionAPI) {
1065
1157
  "## Issue Context",
1066
1158
  "",
1067
1159
  issueContent,
1160
+ "",
1161
+ "---",
1162
+ "",
1163
+ "**Instructions:**",
1164
+ "1. Review the issue context above",
1165
+ "2. Follow the step prompt instructions",
1166
+ "3. When complete, call `pipeline_run_acceptance` to validate your work",
1167
+ "4. Then call `pipeline_advance` to move to the next step",
1068
1168
  ].join("\n");
1069
1169
 
1070
1170
  return { content: [{ type: "text" as const, text }] };
@@ -1079,7 +1179,6 @@ export default function (pi: ExtensionAPI) {
1079
1179
  parameters: { type: "object", properties: {} },
1080
1180
  async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
1081
1181
  if (!manager) manager = new PipelineManager(ctx.cwd);
1082
- manager.reload();
1083
1182
  const state = manager.getState();
1084
1183
  if (!state || state.status !== "running") {
1085
1184
  return { content: [{ type: "text" as const, text: "No running pipeline." }] };
@@ -1095,45 +1194,6 @@ export default function (pi: ExtensionAPI) {
1095
1194
  };
1096
1195
  }
1097
1196
 
1098
- if (acceptance.type === "llm") {
1099
- const promptPath = join(ctx.cwd, acceptance.prompt);
1100
- let validatorPrompt = "";
1101
- try {
1102
- if (existsSync(promptPath)) {
1103
- validatorPrompt = readFileSync(promptPath, "utf-8");
1104
- }
1105
- } catch {
1106
- // ignore
1107
- }
1108
- if (!validatorPrompt) {
1109
- return {
1110
- content: [{
1111
- type: "text" as const,
1112
- text: `LLM validator prompt not found: ${acceptance.prompt}. Skipping gate.`,
1113
- }],
1114
- };
1115
- }
1116
- return {
1117
- content: [{
1118
- type: "text" as const,
1119
- text: [
1120
- `## LLM Validator: ${step.name}`,
1121
- "",
1122
- "Read the validator agent definition below. Execute it as an agent:",
1123
- "1. Read and understand the validation criteria",
1124
- "2. Audit the current implementation against each criterion",
1125
- "3. Report pass/fail with evidence for each criterion",
1126
- "4. If all pass, call `pipeline_advance`",
1127
- "5. If any fail, fix the issues and re-run acceptance",
1128
- "",
1129
- "---",
1130
- "",
1131
- validatorPrompt,
1132
- ].join("\n"),
1133
- }],
1134
- };
1135
- }
1136
-
1137
1197
  if (acceptance.type === "shell") {
1138
1198
  const lines: string[] = [`## Acceptance Gate: ${step.name}\n`];
1139
1199
  const scriptPath = acceptance.command;
@@ -1226,21 +1286,8 @@ function buildSteps(stepNames: string[]): StepConfig[] {
1226
1286
  },
1227
1287
  merge: {
1228
1288
  name: "merge",
1229
- prompt: ".pi/prompts/issue-merge.md",
1230
1289
  acceptance: { type: "validator", validators: ["ci", "canonical"] },
1231
1290
  },
1232
- "architecture-validator": {
1233
- name: "architecture-validator",
1234
- acceptance: { type: "llm", prompt: ".pi/agents/architecture-validator.md" },
1235
- },
1236
- "security-validator": {
1237
- name: "security-validator",
1238
- acceptance: { type: "llm", prompt: ".pi/agents/security-validator.md" },
1239
- },
1240
- "operations-validator": {
1241
- name: "operations-validator",
1242
- acceptance: { type: "llm", prompt: ".pi/agents/operations-validator.md" },
1243
- },
1244
1291
  document: {
1245
1292
  name: "document",
1246
1293
  prompt: ".pi/prompts/blueprint-update.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-03T21:37:16Z",
2
+ "timestamp": "2026-07-03T21:41:44Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -8,7 +8,7 @@
8
8
  "failed": 1,
9
9
  "skipped": 12
10
10
  },
11
- "duration_seconds": 1,
11
+ "duration_seconds": 0,
12
12
  "results": [
13
13
  {
14
14
  "name": "check_mr_traceability.sh",