prizmkit 1.0.152 → 1.1.0

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.
Files changed (81) hide show
  1. package/bin/create-prizmkit.js +27 -2
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/claude/paths.js +1 -1
  4. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +482 -57
  5. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +2 -6
  6. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +48 -8
  7. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +54 -1
  8. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +47 -10
  9. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +11 -0
  10. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +9 -0
  11. package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +12 -0
  12. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +7 -0
  13. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +8 -0
  14. package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +9 -0
  15. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +6 -0
  16. package/bundled/dev-pipeline/templates/sections/failure-capture.md +21 -0
  17. package/bundled/dev-pipeline/templates/sections/failure-log-check.md +8 -0
  18. package/bundled/dev-pipeline/templates/sections/feature-context.md +23 -0
  19. package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +15 -0
  20. package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +15 -0
  21. package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +31 -0
  22. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +36 -0
  23. package/bundled/dev-pipeline/templates/sections/phase-commit.md +26 -0
  24. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +14 -0
  25. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +20 -0
  26. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +3 -0
  27. package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +24 -0
  28. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +45 -0
  29. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +24 -0
  30. package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +36 -0
  31. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +24 -0
  32. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +41 -0
  33. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +32 -0
  34. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +17 -0
  35. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +16 -0
  36. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +28 -0
  37. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +36 -0
  38. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +82 -0
  39. package/bundled/dev-pipeline/templates/sections/phase0-init.md +4 -0
  40. package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +12 -0
  41. package/bundled/dev-pipeline/templates/sections/resume-header.md +2 -0
  42. package/bundled/dev-pipeline/templates/sections/session-context.md +6 -0
  43. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +6 -0
  44. package/bundled/skills/_metadata.json +21 -177
  45. package/bundled/skills/app-planner/SKILL.md +22 -3
  46. package/bundled/skills/app-planner/references/project-brief-guide.md +110 -0
  47. package/bundled/skills/bug-fix-workflow/SKILL.md +4 -0
  48. package/bundled/skills/bug-planner/SKILL.md +2 -2
  49. package/bundled/skills/dev-pipeline-launcher/SKILL.md +1 -1
  50. package/bundled/skills/prizm-kit/SKILL.md +18 -47
  51. package/bundled/skills/prizm-kit/assets/project-memory-template.md +1 -1
  52. package/bundled/skills/prizmkit-analyze/SKILL.md +4 -4
  53. package/bundled/skills/prizmkit-init/SKILL.md +4 -4
  54. package/bundled/skills/prizmkit-plan/SKILL.md +126 -108
  55. package/bundled/skills/prizmkit-plan/assets/plan-template.md +1 -2
  56. package/bundled/skills/refactor-workflow/SKILL.md +142 -124
  57. package/bundled/team/prizm-dev-team.json +2 -8
  58. package/package.json +1 -1
  59. package/src/clean.js +8 -0
  60. package/src/gitignore-template.js +12 -0
  61. package/src/index.js +3 -22
  62. package/src/scaffold.js +20 -11
  63. package/src/upgrade.js +6 -31
  64. package/bundled/skills/prizmkit-clarify/SKILL.md +0 -93
  65. package/bundled/skills/prizmkit-specify/SKILL.md +0 -118
  66. package/bundled/skills/prizmkit-specify/assets/spec-template.md +0 -56
  67. package/bundled/skills/prizmkit-tool-adr-manager/SKILL.md +0 -67
  68. package/bundled/skills/prizmkit-tool-adr-manager/assets/adr-template.md +0 -26
  69. package/bundled/skills/prizmkit-tool-api-doc-generator/SKILL.md +0 -55
  70. package/bundled/skills/prizmkit-tool-bug-reproducer/SKILL.md +0 -61
  71. package/bundled/skills/prizmkit-tool-ci-cd-generator/SKILL.md +0 -53
  72. package/bundled/skills/prizmkit-tool-db-migration/SKILL.md +0 -64
  73. package/bundled/skills/prizmkit-tool-dependency-health/SKILL.md +0 -122
  74. package/bundled/skills/prizmkit-tool-deployment-strategy/SKILL.md +0 -57
  75. package/bundled/skills/prizmkit-tool-error-triage/SKILL.md +0 -54
  76. package/bundled/skills/prizmkit-tool-log-analyzer/SKILL.md +0 -54
  77. package/bundled/skills/prizmkit-tool-monitoring-setup/SKILL.md +0 -74
  78. package/bundled/skills/prizmkit-tool-onboarding-generator/SKILL.md +0 -69
  79. package/bundled/skills/prizmkit-tool-perf-profiler/SKILL.md +0 -54
  80. package/bundled/skills/prizmkit-tool-security-audit/SKILL.md +0 -129
  81. package/bundled/skills/prizmkit-tool-tech-debt-tracker/SKILL.md +0 -138
@@ -7,6 +7,8 @@
7
7
  * npx prizmkit install .
8
8
  * npx prizmkit install my-project --platform claude --non-interactive
9
9
  * npx prizmkit uninstall . --dry-run
10
+ * npx prizmkit upgrade .
11
+ * npx prizmkit config . --platform codebuddy
10
12
  */
11
13
 
12
14
  import { readFileSync } from 'fs';
@@ -16,20 +18,21 @@ import { program } from 'commander';
16
18
  import { runScaffold } from '../src/index.js';
17
19
  import { runClean } from '../src/clean.js';
18
20
  import { runUpgrade } from '../src/upgrade.js';
21
+ import { runConfig } from '../src/config.js';
19
22
 
20
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
21
24
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
22
25
 
23
26
  program
24
27
  .name('prizmkit')
25
- .description('PrizmKit project installer/uninstaller')
28
+ .description('PrizmKit project installer/uninstaller/configurator')
26
29
  .version(pkg.version);
27
30
 
28
31
  program
29
32
  .command('install [directory]')
30
33
  .description('Install PrizmKit into a project directory')
31
34
  .option('--platform <platform>', 'Target platform: codebuddy, claude, or both')
32
- .option('--skills <suite>', 'Skill suite: full, core, minimal, or recommended:<type> (frontend/backend/fullstack/library)', 'full')
35
+ .option('--skills <suite>', 'Skill suite: core, minimal, or recommended:<type> (frontend/backend/fullstack/library)', 'core')
33
36
  .option('--team', 'Enable multi-agent team mode (default: true)')
34
37
  .option('--no-team', 'Disable multi-agent team mode')
35
38
  .option('--pipeline', 'Install dev-pipeline (default: true)')
@@ -77,4 +80,26 @@ program
77
80
  }
78
81
  });
79
82
 
83
+ program
84
+ .command('config [directory]')
85
+ .description('Reconfigure existing PrizmKit installation (change platform, skills, rules, options)')
86
+ .option('--platform <platform>', 'Target platform: codebuddy, claude, or both')
87
+ .option('--skills <suite>', 'Skill suite: core, minimal, or recommended:<type>')
88
+ .option('--rules <preset>', 'Rules preset: recommended, minimal, or none')
89
+ .option('--ai-cli <command>', 'AI CLI executable command (e.g. cbc, claude)')
90
+ .option('--team', 'Enable multi-agent team mode')
91
+ .option('--no-team', 'Disable multi-agent team mode')
92
+ .option('--pipeline', 'Enable dev-pipeline')
93
+ .option('--no-pipeline', 'Disable dev-pipeline')
94
+ .option('--non-interactive', 'Skip interactive prompts')
95
+ .option('--dry-run', 'Show what would change without applying')
96
+ .action(async (directory = '.', options) => {
97
+ try {
98
+ await runConfig(directory, options);
99
+ } catch (err) {
100
+ console.error(`\n Error: ${err.message}\n`);
101
+ process.exit(1);
102
+ }
103
+ });
104
+
80
105
  program.parse();
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.0.152",
3
- "bundledAt": "2026-04-01T14:11:50.110Z",
4
- "bundledFrom": "7fa7136"
2
+ "frameworkVersion": "1.1.0",
3
+ "bundledAt": "2026-04-02T15:24:18.389Z",
4
+ "bundledFrom": "f9bf2b6"
5
5
  }
@@ -17,7 +17,7 @@ export const GLOBAL_CONFIG_DIR = '.claude';
17
17
  export const GLOBAL_AGENTS_DIR = '.claude/agents';
18
18
 
19
19
  // Command file conventions (equivalent to skills)
20
- // Note: Actual command files are named <skill-name>.md (e.g. prizmkit-specify.md)
20
+ // Note: Actual command files are named <skill-name>.md
21
21
  // Directory-based commands with assets also use <skill-name>.md inside the directory.
22
22
  // Claude Code does not have a native ${COMMAND_DIR} variable equivalent.
23
23
 
@@ -1,9 +1,16 @@
1
1
  #!/usr/bin/env python3
2
2
  """Generate a session-specific bootstrap prompt from template and feature list.
3
3
 
4
- Reads a bootstrap-prompt.md template and a feature-list.json, resolves all
5
- {{PLACEHOLDER}} variables, handles conditional blocks, and writes the rendered
6
- prompt to the specified output path.
4
+ Supports two modes:
5
+ 1. **Section assembly** (preferred): Loads modular section files from
6
+ templates/sections/ and assembles them based on tier, conditions, and
7
+ feature configuration. Conditional logic is handled in Python code,
8
+ not regex-based template blocks.
9
+ 2. **Legacy template** (fallback): Reads a monolithic bootstrap-tier{1,2,3}.md
10
+ template and resolves {{PLACEHOLDER}} variables and {{IF_xxx}} blocks.
11
+
12
+ The section assembly mode is used when templates/sections/ directory exists.
13
+ Otherwise, falls back to legacy templates for backward compatibility.
7
14
 
8
15
  Usage:
9
16
  python3 generate-bootstrap-prompt.py \
@@ -239,6 +246,24 @@ def get_prev_session_status(state_dir, feature_id):
239
246
  return result
240
247
 
241
248
 
249
+ def _read_project_brief(project_root):
250
+ """Read project-brief.md from project root if it exists.
251
+
252
+ Returns the file content as a string, or a fallback message if absent.
253
+ This brief is generated by app-planner during interactive planning and
254
+ captures cross-feature design decisions, business intent, and user
255
+ preferences that individual feature descriptions may not include.
256
+ """
257
+ brief_path = os.path.join(project_root, "project-brief.md")
258
+ if os.path.isfile(brief_path):
259
+ try:
260
+ with open(brief_path, "r", encoding="utf-8") as f:
261
+ return f.read().strip()
262
+ except IOError:
263
+ return "(project-brief.md exists but could not be read)"
264
+ return "(No project brief available)"
265
+
266
+
242
267
  def resolve_project_root(script_dir):
243
268
  """Resolve project root as the parent directory of dev-pipeline/.
244
269
 
@@ -403,6 +428,385 @@ def determine_pipeline_mode(complexity):
403
428
  return mapping.get(complexity, "lite")
404
429
 
405
430
 
431
+ # ============================================================
432
+ # Section Assembly (new modular approach)
433
+ # ============================================================
434
+
435
+
436
+ def load_section(sections_dir, name):
437
+ """Load a section file from the sections directory.
438
+
439
+ Returns the file content as a string, or raises FileNotFoundError.
440
+ """
441
+ path = os.path.join(sections_dir, name)
442
+ if not os.path.isfile(path):
443
+ raise FileNotFoundError("Section file not found: {}".format(path))
444
+ with open(path, "r", encoding="utf-8") as f:
445
+ return f.read()
446
+
447
+
448
+ def _tier_header(pipeline_mode):
449
+ """Return the tier-specific header and mission description."""
450
+ headers = {
451
+ "lite": (
452
+ "# Dev-Pipeline Session Bootstrap — Tier 1 (Single Agent)\n",
453
+ "**Tier 1 — Single Agent**: You handle everything directly. "
454
+ "No subagents, no TeamCreate.\n",
455
+ ),
456
+ "standard": (
457
+ "# Dev-Pipeline Session Bootstrap — Tier 2 (Dual Agent)\n",
458
+ "**Tier 2 — Dual Agent**: You handle context + planning "
459
+ "directly. Then spawn Dev and Reviewer subagents. Spawn Dev "
460
+ "and Reviewer agents via the Agent tool.\n",
461
+ ),
462
+ "full": (
463
+ "# Dev-Pipeline Session Bootstrap — Tier 3 (Full Team)\n",
464
+ "**Tier 3 — Full Team**: For complex features, use the full "
465
+ "pipeline with Dev + Reviewer agents spawned via the Agent "
466
+ "tool.\n",
467
+ ),
468
+ }
469
+ return headers.get(pipeline_mode, headers["lite"])
470
+
471
+
472
+ def _tier_reminders(pipeline_mode, critic_enabled=False):
473
+ """Return tier-specific reminder text."""
474
+ common = [
475
+ "- MANDATORY skills: `/prizmkit-retrospective`, `/prizmkit-committer` "
476
+ "— never skip these",
477
+ "- Build context-snapshot.md FIRST; use it throughout instead of "
478
+ "re-reading files",
479
+ "- `/prizmkit-committer` is mandatory — do NOT skip the commit phase, "
480
+ "and do NOT replace it with manual git commit commands",
481
+ "- Do NOT run `git add`/`git commit` during implementation phases — "
482
+ "all changes are committed once in the commit phase",
483
+ "- If any files remain after the commit, amend the existing commit — "
484
+ "do NOT create a follow-up commit",
485
+ "- When staging files, always use explicit file names — NEVER use "
486
+ "`git add -A` or `git add .`",
487
+ ]
488
+
489
+ if pipeline_mode == "lite":
490
+ specific = [
491
+ "- Tier 1: you handle everything directly — no subagents needed",
492
+ ]
493
+ elif pipeline_mode == "standard":
494
+ specific = [
495
+ "- Tier 2: orchestrator builds context+plan, Analyzer checks "
496
+ "consistency, Dev implements, Reviewer reviews+tests — use "
497
+ "direct Agent spawn for agents",
498
+ "- context-snapshot.md is append-only: orchestrator writes "
499
+ "Sections 1-4, Dev appends Implementation Log, Reviewer "
500
+ "appends Review Notes",
501
+ "- Gate checks enforce Implementation Log and Review Notes are "
502
+ "written before proceeding",
503
+ "- Do NOT use `run_in_background=true` when spawning subagents",
504
+ "- On timeout: check snapshot + git diff HEAD → model:lite → "
505
+ "remaining steps only → max 2 retries per phase → "
506
+ "orchestrator fallback",
507
+ ]
508
+ else: # full
509
+ specific = [
510
+ "- Tier 3: full team — Dev (implementation) → Reviewer "
511
+ "(review + test) — spawn agents directly via Agent tool",
512
+ "- context-snapshot.md is append-only: orchestrator writes "
513
+ "Sections 1-4, Dev appends Implementation Log, Reviewer "
514
+ "appends Review Notes",
515
+ "- Gate checks enforce Implementation Log and Review Notes are "
516
+ "written before proceeding",
517
+ "- Do NOT use `run_in_background=true` when spawning agents",
518
+ "- On timeout: check snapshot → model:lite → remaining steps "
519
+ "only → max 2 retries → orchestrator fallback",
520
+ ]
521
+
522
+ lines = ["## Reminders\n"] + specific + common
523
+ return "\n".join(lines) + "\n"
524
+
525
+
526
+ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
527
+ critic_enabled, browser_enabled):
528
+ """Assemble prompt sections based on tier and conditions.
529
+
530
+ Uses Python code for conditional logic instead of regex-based
531
+ template blocks. Each section is loaded from a separate .md file.
532
+
533
+ Returns a list of (section_name, content) tuples in order.
534
+ """
535
+ sections = []
536
+
537
+ # --- Header ---
538
+ title, tier_desc = _tier_header(pipeline_mode)
539
+ sections.append(("header", title))
540
+
541
+ # --- Session Context ---
542
+ sections.append(("session-context",
543
+ load_section(sections_dir, "session-context.md")))
544
+
545
+ # --- Mission ---
546
+ mission = (
547
+ "## Your Mission\n\n"
548
+ "You are the **session orchestrator**. Implement Feature "
549
+ "{{FEATURE_ID}}: \"{{FEATURE_TITLE}}\".\n\n"
550
+ "**CRITICAL**: You MUST NOT exit until ALL work is complete "
551
+ "and committed."
552
+ )
553
+ if pipeline_mode != "lite":
554
+ mission += (
555
+ " When you spawn subagents, wait for each to finish "
556
+ "(run_in_background=false)."
557
+ )
558
+ if pipeline_mode == "full":
559
+ mission += (
560
+ " Do NOT spawn agents in background and exit — "
561
+ "that kills the session."
562
+ )
563
+ mission += "\n\n" + tier_desc
564
+ sections.append(("mission", mission))
565
+
566
+ # --- Feature Context (XML-wrapped, optimization 3) ---
567
+ sections.append(("feature-context",
568
+ load_section(sections_dir, "feature-context.md")))
569
+
570
+ # --- Context Budget Rules ---
571
+ sections.append(("context-budget-rules",
572
+ load_section(sections_dir, "context-budget-rules.md")))
573
+
574
+ # --- Directory Convention (tier-specific) ---
575
+ if pipeline_mode == "lite":
576
+ dc_file = "directory-convention-lite.md"
577
+ elif pipeline_mode == "standard":
578
+ dc_file = "directory-convention-agent.md"
579
+ else:
580
+ dc_file = "directory-convention-full.md"
581
+ sections.append(("directory-convention",
582
+ load_section(sections_dir, dc_file)))
583
+
584
+ # --- Subagent Timeout Recovery (only for agent tiers) ---
585
+ if pipeline_mode in ("standard", "full"):
586
+ sections.append(("timeout-recovery",
587
+ load_section(sections_dir,
588
+ "subagent-timeout-recovery.md")))
589
+
590
+ # --- Execution header ---
591
+ sections.append(("execution-header", "---\n\n## Execution\n"))
592
+
593
+ # --- Phase 0: Init or Test Baseline ---
594
+ if not init_done:
595
+ sections.append(("phase0-init",
596
+ load_section(sections_dir, "phase0-init.md")))
597
+ else:
598
+ if pipeline_mode in ("standard", "full"):
599
+ sections.append(("phase0-test-baseline",
600
+ load_section(sections_dir,
601
+ "phase0-test-baseline.md")))
602
+ else:
603
+ sections.append(("phase0-skip",
604
+ "### Phase 0: SKIP (already initialized)\n"))
605
+
606
+ # --- Resume header (if resuming) ---
607
+ if is_resume:
608
+ sections.append(("resume-header",
609
+ load_section(sections_dir, "resume-header.md")))
610
+
611
+ # --- Failure log check ---
612
+ sections.append(("failure-log-check",
613
+ load_section(sections_dir, "failure-log-check.md")))
614
+
615
+ # --- Context Snapshot + Plan (tier-dependent) ---
616
+ if pipeline_mode == "full":
617
+ # Tier 3: full specify + plan workflow
618
+ sections.append(("phase-specify-plan",
619
+ load_section(sections_dir,
620
+ "phase-specify-plan-full.md")))
621
+ else:
622
+ # Tier 1 & 2: separate context snapshot + plan
623
+ snapshot_base = load_section(sections_dir,
624
+ "phase-context-snapshot-base.md")
625
+ if pipeline_mode == "lite":
626
+ snapshot_suffix = load_section(
627
+ sections_dir, "phase-context-snapshot-lite-suffix.md")
628
+ else:
629
+ snapshot_suffix = load_section(
630
+ sections_dir, "phase-context-snapshot-agent-suffix.md")
631
+ sections.append(("phase-context-snapshot",
632
+ snapshot_base + "\n" + snapshot_suffix))
633
+
634
+ if pipeline_mode == "lite":
635
+ sections.append(("phase-plan",
636
+ load_section(sections_dir,
637
+ "phase-plan-lite.md")))
638
+ else:
639
+ sections.append(("phase-plan",
640
+ load_section(sections_dir,
641
+ "phase-plan-agent.md")))
642
+
643
+ # --- Analyze (only for agent tiers) ---
644
+ if pipeline_mode == "standard":
645
+ sections.append(("phase-analyze",
646
+ load_section(sections_dir,
647
+ "phase-analyze-agent.md")))
648
+ elif pipeline_mode == "full":
649
+ sections.append(("phase-analyze",
650
+ load_section(sections_dir,
651
+ "phase-analyze-full.md")))
652
+
653
+ # --- Critic: Plan Challenge (only if critic enabled) ---
654
+ if critic_enabled:
655
+ if pipeline_mode == "full":
656
+ sections.append(("phase-critic-plan",
657
+ load_section(sections_dir,
658
+ "phase-critic-plan-full.md")))
659
+ else:
660
+ sections.append(("phase-critic-plan",
661
+ load_section(sections_dir,
662
+ "phase-critic-plan.md")))
663
+
664
+ # --- Implement (tier-dependent) ---
665
+ if pipeline_mode == "lite":
666
+ sections.append(("phase-implement",
667
+ load_section(sections_dir,
668
+ "phase-implement-lite.md")))
669
+ elif pipeline_mode == "full":
670
+ sections.append(("phase-implement",
671
+ load_section(sections_dir,
672
+ "phase-implement-full.md")))
673
+ else:
674
+ sections.append(("phase-implement",
675
+ load_section(sections_dir,
676
+ "phase-implement-agent.md")))
677
+
678
+ # --- Critic: Code Challenge (only if critic enabled, agent tiers) ---
679
+ if critic_enabled and pipeline_mode in ("standard", "full"):
680
+ sections.append(("phase-critic-code",
681
+ load_section(sections_dir,
682
+ "phase-critic-code.md")))
683
+
684
+ # --- Review (only for agent tiers) ---
685
+ if pipeline_mode == "full":
686
+ sections.append(("phase-review",
687
+ load_section(sections_dir,
688
+ "phase-review-full.md")))
689
+ elif pipeline_mode == "standard":
690
+ sections.append(("phase-review",
691
+ load_section(sections_dir,
692
+ "phase-review-agent.md")))
693
+
694
+ # --- Browser Verification (conditional) ---
695
+ if browser_enabled:
696
+ sections.append(("phase-browser",
697
+ load_section(sections_dir,
698
+ "phase-browser-verification.md")))
699
+
700
+ # --- Deploy Verification ---
701
+ sections.append(("phase-deploy",
702
+ load_section(sections_dir,
703
+ "phase-deploy-verification.md")))
704
+
705
+ # --- Commit (tier-dependent) ---
706
+ if pipeline_mode == "full":
707
+ sections.append(("phase-commit",
708
+ load_section(sections_dir,
709
+ "phase-commit-full.md")))
710
+ else:
711
+ sections.append(("phase-commit",
712
+ load_section(sections_dir,
713
+ "phase-commit.md")))
714
+
715
+ # --- Critical Paths ---
716
+ if pipeline_mode == "lite":
717
+ cp_file = "critical-paths-lite.md"
718
+ elif pipeline_mode == "full":
719
+ cp_file = "critical-paths-full.md"
720
+ else:
721
+ cp_file = "critical-paths-agent.md"
722
+ sections.append(("critical-paths",
723
+ load_section(sections_dir, cp_file)))
724
+
725
+ # --- Failure Capture ---
726
+ sections.append(("failure-capture",
727
+ load_section(sections_dir, "failure-capture.md")))
728
+
729
+ # --- Reminders ---
730
+ sections.append(("reminders",
731
+ _tier_reminders(pipeline_mode, critic_enabled)))
732
+
733
+ return sections
734
+
735
+
736
+ def render_from_sections(sections, replacements):
737
+ """Join assembled sections and replace all {{PLACEHOLDER}} variables.
738
+
739
+ No regex-based conditional block processing needed — all conditions
740
+ were resolved during assembly.
741
+ """
742
+ content = "\n".join(text for _, text in sections)
743
+
744
+ # Replace all placeholders
745
+ for placeholder, value in replacements.items():
746
+ content = content.replace(placeholder, value)
747
+
748
+ return content
749
+
750
+
751
+ # ============================================================
752
+ # Rendered output validation (optimization 7)
753
+ # ============================================================
754
+
755
+
756
+ def validate_rendered(content):
757
+ """Validate rendered prompt content for completeness.
758
+
759
+ Checks:
760
+ 1. No unreplaced {{PLACEHOLDER}} variables remain
761
+ 2. No unclosed conditional blocks (legacy {{IF_xxx}} tags)
762
+ 3. Required sections present
763
+
764
+ Returns (is_valid, warnings, errors) tuple.
765
+ """
766
+ warnings = []
767
+ errors = []
768
+
769
+ # Check for unreplaced placeholders (excluding code blocks that may
770
+ # contain literal double braces like Jinja or Go templates)
771
+ unreplaced = re.findall(r"\{\{[A-Z][A-Z_0-9]+\}\}", content)
772
+ if unreplaced:
773
+ # Deduplicate
774
+ unique = sorted(set(unreplaced))
775
+ warnings.append(
776
+ "Unreplaced placeholders: {}".format(", ".join(unique))
777
+ )
778
+
779
+ # Check for unclosed conditional blocks (legacy)
780
+ unclosed_if = re.findall(
781
+ r"\{\{(?:IF|END_IF)_[A-Z_]+\}\}", content
782
+ )
783
+ if unclosed_if:
784
+ unique = sorted(set(unclosed_if))
785
+ errors.append(
786
+ "Unclosed conditional blocks: {}".format(", ".join(unique))
787
+ )
788
+
789
+ # Check required sections exist
790
+ required_markers = [
791
+ ("## Your Mission", "Mission section"),
792
+ ("## Execution", "Execution section"),
793
+ ("## Failure Capture", "Failure Capture Protocol"),
794
+ ]
795
+ for marker, label in required_markers:
796
+ if marker not in content:
797
+ errors.append("Missing required section: {}".format(label))
798
+
799
+ is_valid = len(errors) == 0
800
+
801
+ # Log results
802
+ for w in warnings:
803
+ LOGGER.warning("VALIDATE: %s", w)
804
+ for e in errors:
805
+ LOGGER.error("VALIDATE: %s", e)
806
+
807
+ return is_valid, warnings, errors
808
+
809
+
406
810
  def build_replacements(args, feature, features, global_context, script_dir):
407
811
  """Build the full dict of placeholder -> replacement value."""
408
812
  project_root = resolve_project_root(script_dir)
@@ -574,6 +978,7 @@ def build_replacements(args, feature, features, global_context, script_dir):
574
978
  features, feature
575
979
  ),
576
980
  "{{GLOBAL_CONTEXT}}": format_global_context(global_context, project_root),
981
+ "{{PROJECT_BRIEF}}": _read_project_brief(project_root),
577
982
  "{{TEAM_CONFIG_PATH}}": team_config_path,
578
983
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
579
984
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
@@ -646,87 +1051,104 @@ def main():
646
1051
 
647
1052
  # Resolve script directory
648
1053
  script_dir = os.path.dirname(os.path.abspath(__file__))
1054
+ templates_dir = os.path.join(script_dir, "..", "templates")
1055
+ sections_dir = os.path.join(templates_dir, "sections")
649
1056
 
650
- # Resolve template path
651
- if args.template:
652
- template_path = args.template
653
- else:
654
- # Determine pipeline mode to select the right tier template
655
- _complexity = None
656
- try:
657
- _fl, _ = load_json_file(args.feature_list)
658
- if _fl:
659
- for _f in _fl.get("features", []):
660
- if isinstance(_f, dict) and _f.get("id") == args.feature_id:
661
- _complexity = _f.get("estimated_complexity", "medium")
662
- break
663
- except Exception:
664
- pass
665
- _mode = args.mode or determine_pipeline_mode(_complexity or "medium")
666
- _tier_file_map = {
667
- "lite": "bootstrap-tier1.md",
668
- "standard": "bootstrap-tier2.md",
669
- "full": "bootstrap-tier3.md",
670
- }
671
- _tier_file = _tier_file_map.get(_mode, "bootstrap-tier2.md")
672
- _tier_path = os.path.join(script_dir, "..", "templates", _tier_file)
673
- # Fall back to legacy monolithic template if tier file doesn't exist
674
- if os.path.isfile(_tier_path):
675
- template_path = _tier_path
676
- else:
677
- template_path = os.path.join(
678
- script_dir, "..", "templates", "bootstrap-prompt.md"
679
- )
680
-
681
- # Load template
682
- template_content, err = read_text_file(template_path)
683
- if err:
684
- emit_failure("Template error: {}".format(err))
685
-
686
- # Load feature list
1057
+ # Load feature list early (needed for both code paths)
687
1058
  feature_list_data, err = load_json_file(args.feature_list)
688
1059
  if err:
689
1060
  emit_failure("Feature list error: {}".format(err))
690
1061
 
691
- # Extract features array
692
1062
  features = feature_list_data.get("features")
693
1063
  if not isinstance(features, list):
694
1064
  emit_failure("Feature list does not contain a 'features' array")
695
1065
 
696
- # Find the target feature
697
1066
  feature = find_feature(features, args.feature_id)
698
1067
  if feature is None:
699
- emit_failure("Feature '{}' not found in feature list".format(args.feature_id))
1068
+ emit_failure(
1069
+ "Feature '{}' not found in feature list".format(args.feature_id)
1070
+ )
700
1071
 
701
- # Extract global context
702
1072
  global_context = feature_list_data.get("global_context", {})
703
1073
  if not isinstance(global_context, dict):
704
1074
  global_context = {}
705
1075
 
706
- # Build replacements
1076
+ # Build replacements (shared by both code paths)
707
1077
  replacements, effective_resume, browser_enabled = build_replacements(
708
1078
  args, feature, features, global_context, script_dir
709
1079
  )
710
-
711
- # Update RESUME_PHASE in replacements to reflect auto-detection
712
1080
  replacements["{{RESUME_PHASE}}"] = effective_resume
713
1081
 
714
- # Render the template
715
- rendered = render_template(
716
- template_content, replacements, effective_resume, browser_enabled
717
- )
1082
+ # Extract state needed for assembly
1083
+ pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
1084
+ init_done = replacements.get("{{INIT_DONE}}", "false") == "true"
1085
+ is_resume = effective_resume != "null"
1086
+ critic_enabled = replacements.get("{{CRITIC_ENABLED}}", "false") == "true"
1087
+
1088
+ # ── Choose rendering path ──────────────────────────────────────────
1089
+ use_sections = os.path.isdir(sections_dir) and not args.template
1090
+
1091
+ if use_sections:
1092
+ # New modular section assembly (code-level conditional logic)
1093
+ LOGGER.info("Using section assembly from %s", sections_dir)
1094
+ try:
1095
+ sections = assemble_sections(
1096
+ pipeline_mode, sections_dir, init_done, is_resume,
1097
+ critic_enabled, browser_enabled,
1098
+ )
1099
+ rendered = render_from_sections(sections, replacements)
1100
+ except FileNotFoundError as exc:
1101
+ LOGGER.warning(
1102
+ "Section assembly failed (%s), falling back to legacy "
1103
+ "template", exc,
1104
+ )
1105
+ use_sections = False
718
1106
 
719
- # Write the output
1107
+ if not use_sections:
1108
+ # Legacy monolithic template path (backward compatible)
1109
+ if args.template:
1110
+ template_path = args.template
1111
+ else:
1112
+ complexity = feature.get("estimated_complexity", "medium")
1113
+ _mode = args.mode or determine_pipeline_mode(complexity)
1114
+ _tier_file_map = {
1115
+ "lite": "bootstrap-tier1.md",
1116
+ "standard": "bootstrap-tier2.md",
1117
+ "full": "bootstrap-tier3.md",
1118
+ }
1119
+ _tier_file = _tier_file_map.get(_mode, "bootstrap-tier2.md")
1120
+ _tier_path = os.path.join(templates_dir, _tier_file)
1121
+ if os.path.isfile(_tier_path):
1122
+ template_path = _tier_path
1123
+ else:
1124
+ template_path = os.path.join(
1125
+ templates_dir, "bootstrap-prompt.md"
1126
+ )
1127
+
1128
+ template_content, err = read_text_file(template_path)
1129
+ if err:
1130
+ emit_failure("Template error: {}".format(err))
1131
+
1132
+ rendered = render_template(
1133
+ template_content, replacements, effective_resume, browser_enabled
1134
+ )
1135
+
1136
+ # ── Validate rendered output ───────────────────────────────────────
1137
+ is_valid, warnings, errors = validate_rendered(rendered)
1138
+ if not is_valid:
1139
+ LOGGER.error(
1140
+ "Rendered prompt failed validation: %s",
1141
+ "; ".join(errors),
1142
+ )
1143
+ # Continue anyway — a partially valid prompt is better than none
1144
+
1145
+ # ── Write output ───────────────────────────────────────────────────
720
1146
  err = write_output(args.output, rendered)
721
1147
  if err:
722
1148
  emit_failure(err)
723
1149
 
724
- # Success
1150
+ # ── Success JSON ───────────────────────────────────────────────────
725
1151
  feature_model = feature.get("model", "")
726
- pipeline_mode = replacements.get("{{PIPELINE_MODE}}", "lite")
727
- critic_enabled = replacements.get("{{CRITIC_ENABLED}}", "false") == "true"
728
- # Map mode to agent count for logging
729
- # lite=1 (single agent), standard/full=3 (orchestrator + dev + reviewer)
730
1152
  mode_agent_counts = {"lite": 1, "standard": 3, "full": 3}
731
1153
  agent_count = mode_agent_counts.get(pipeline_mode, 1)
732
1154
  critic_count_val = int(replacements.get("{{CRITIC_COUNT}}", "1"))
@@ -739,6 +1161,9 @@ def main():
739
1161
  "pipeline_mode": pipeline_mode,
740
1162
  "agent_count": agent_count,
741
1163
  "critic_enabled": "true" if critic_enabled else "false",
1164
+ "render_mode": "sections" if use_sections else "legacy",
1165
+ "validation_warnings": len(warnings),
1166
+ "validation_errors": len(errors),
742
1167
  }
743
1168
  print(json.dumps(output, indent=2, ensure_ascii=False))
744
1169
  sys.exit(0)