prizmkit 1.0.153 → 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.
- package/bin/create-prizmkit.js +27 -2
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/paths.js +1 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +482 -57
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +2 -6
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +6 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +6 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +7 -1
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +11 -0
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +9 -0
- package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +12 -0
- package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +7 -0
- package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +8 -0
- package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +9 -0
- package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +6 -0
- package/bundled/dev-pipeline/templates/sections/failure-capture.md +21 -0
- package/bundled/dev-pipeline/templates/sections/failure-log-check.md +8 -0
- package/bundled/dev-pipeline/templates/sections/feature-context.md +23 -0
- package/bundled/dev-pipeline/templates/sections/phase-analyze-agent.md +15 -0
- package/bundled/dev-pipeline/templates/sections/phase-analyze-full.md +15 -0
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +31 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +36 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +26 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +14 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +20 -0
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +3 -0
- package/bundled/dev-pipeline/templates/sections/phase-critic-code.md +24 -0
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +45 -0
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +24 -0
- package/bundled/dev-pipeline/templates/sections/phase-deploy-verification.md +36 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +24 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +41 -0
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +32 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +17 -0
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +16 -0
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +28 -0
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +36 -0
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +82 -0
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +4 -0
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +12 -0
- package/bundled/dev-pipeline/templates/sections/resume-header.md +2 -0
- package/bundled/dev-pipeline/templates/sections/session-context.md +6 -0
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +6 -0
- package/bundled/skills/_metadata.json +21 -177
- package/bundled/skills/app-planner/SKILL.md +22 -3
- package/bundled/skills/app-planner/references/project-brief-guide.md +110 -0
- package/bundled/skills/bug-fix-workflow/SKILL.md +4 -0
- package/bundled/skills/bug-planner/SKILL.md +2 -2
- package/bundled/skills/dev-pipeline-launcher/SKILL.md +1 -1
- package/bundled/skills/prizm-kit/SKILL.md +18 -47
- package/bundled/skills/prizm-kit/assets/project-memory-template.md +1 -1
- package/bundled/skills/prizmkit-analyze/SKILL.md +4 -4
- package/bundled/skills/prizmkit-init/SKILL.md +4 -4
- package/bundled/skills/prizmkit-plan/SKILL.md +126 -108
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +1 -2
- package/bundled/skills/refactor-workflow/SKILL.md +142 -124
- package/bundled/team/prizm-dev-team.json +2 -8
- package/package.json +1 -1
- package/src/clean.js +8 -0
- package/src/gitignore-template.js +12 -0
- package/src/index.js +3 -22
- package/src/scaffold.js +20 -11
- package/src/upgrade.js +6 -31
- package/bundled/skills/prizmkit-clarify/SKILL.md +0 -93
- package/bundled/skills/prizmkit-specify/SKILL.md +0 -118
- package/bundled/skills/prizmkit-specify/assets/spec-template.md +0 -56
- package/bundled/skills/prizmkit-tool-adr-manager/SKILL.md +0 -67
- package/bundled/skills/prizmkit-tool-adr-manager/assets/adr-template.md +0 -26
- package/bundled/skills/prizmkit-tool-api-doc-generator/SKILL.md +0 -55
- package/bundled/skills/prizmkit-tool-bug-reproducer/SKILL.md +0 -61
- package/bundled/skills/prizmkit-tool-ci-cd-generator/SKILL.md +0 -53
- package/bundled/skills/prizmkit-tool-db-migration/SKILL.md +0 -64
- package/bundled/skills/prizmkit-tool-dependency-health/SKILL.md +0 -122
- package/bundled/skills/prizmkit-tool-deployment-strategy/SKILL.md +0 -57
- package/bundled/skills/prizmkit-tool-error-triage/SKILL.md +0 -54
- package/bundled/skills/prizmkit-tool-log-analyzer/SKILL.md +0 -54
- package/bundled/skills/prizmkit-tool-monitoring-setup/SKILL.md +0 -74
- package/bundled/skills/prizmkit-tool-onboarding-generator/SKILL.md +0 -69
- package/bundled/skills/prizmkit-tool-perf-profiler/SKILL.md +0 -54
- package/bundled/skills/prizmkit-tool-security-audit/SKILL.md +0 -129
- package/bundled/skills/prizmkit-tool-tech-debt-tracker/SKILL.md +0 -138
package/bin/create-prizmkit.js
CHANGED
|
@@ -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:
|
|
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();
|
package/bundled/VERSION.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
#
|
|
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(
|
|
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
|
-
#
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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)
|