cap-pro 1.0.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/.claude-plugin/README.md +26 -0
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/LICENSE +21 -0
- package/README.ja-JP.md +834 -0
- package/README.ko-KR.md +823 -0
- package/README.md +806 -0
- package/README.pt-BR.md +452 -0
- package/README.zh-CN.md +800 -0
- package/agents/cap-architect.md +269 -0
- package/agents/cap-brainstormer.md +207 -0
- package/agents/cap-curator.md +276 -0
- package/agents/cap-debugger.md +365 -0
- package/agents/cap-designer.md +246 -0
- package/agents/cap-historian.md +464 -0
- package/agents/cap-migrator.md +291 -0
- package/agents/cap-prototyper.md +197 -0
- package/agents/cap-validator.md +308 -0
- package/bin/install.js +5433 -0
- package/cap/bin/cap-tools.cjs +853 -0
- package/cap/bin/lib/arc-scanner.cjs +344 -0
- package/cap/bin/lib/cap-affinity-engine.cjs +862 -0
- package/cap/bin/lib/cap-anchor.cjs +228 -0
- package/cap/bin/lib/cap-annotation-writer.cjs +340 -0
- package/cap/bin/lib/cap-checkpoint.cjs +434 -0
- package/cap/bin/lib/cap-cluster-detect.cjs +945 -0
- package/cap/bin/lib/cap-cluster-display.cjs +52 -0
- package/cap/bin/lib/cap-cluster-format.cjs +245 -0
- package/cap/bin/lib/cap-cluster-helpers.cjs +295 -0
- package/cap/bin/lib/cap-cluster-io.cjs +212 -0
- package/cap/bin/lib/cap-completeness.cjs +540 -0
- package/cap/bin/lib/cap-deps.cjs +583 -0
- package/cap/bin/lib/cap-design-families.cjs +332 -0
- package/cap/bin/lib/cap-design.cjs +966 -0
- package/cap/bin/lib/cap-divergence-detector.cjs +400 -0
- package/cap/bin/lib/cap-doctor.cjs +752 -0
- package/cap/bin/lib/cap-feature-map-internals.cjs +19 -0
- package/cap/bin/lib/cap-feature-map-migrate.cjs +335 -0
- package/cap/bin/lib/cap-feature-map-monorepo.cjs +885 -0
- package/cap/bin/lib/cap-feature-map-shard.cjs +315 -0
- package/cap/bin/lib/cap-feature-map.cjs +1943 -0
- package/cap/bin/lib/cap-fitness-score.cjs +1075 -0
- package/cap/bin/lib/cap-impact-analysis.cjs +652 -0
- package/cap/bin/lib/cap-learn-review.cjs +1072 -0
- package/cap/bin/lib/cap-learning-signals.cjs +627 -0
- package/cap/bin/lib/cap-loader.cjs +227 -0
- package/cap/bin/lib/cap-logger.cjs +57 -0
- package/cap/bin/lib/cap-memory-bridge.cjs +764 -0
- package/cap/bin/lib/cap-memory-confidence.cjs +452 -0
- package/cap/bin/lib/cap-memory-dir.cjs +987 -0
- package/cap/bin/lib/cap-memory-engine.cjs +698 -0
- package/cap/bin/lib/cap-memory-extends.cjs +398 -0
- package/cap/bin/lib/cap-memory-graph.cjs +790 -0
- package/cap/bin/lib/cap-memory-migrate.cjs +2015 -0
- package/cap/bin/lib/cap-memory-pin.cjs +183 -0
- package/cap/bin/lib/cap-memory-platform.cjs +490 -0
- package/cap/bin/lib/cap-memory-prune.cjs +707 -0
- package/cap/bin/lib/cap-memory-schema.cjs +812 -0
- package/cap/bin/lib/cap-migrate-tags.cjs +309 -0
- package/cap/bin/lib/cap-migrate.cjs +540 -0
- package/cap/bin/lib/cap-pattern-apply.cjs +1203 -0
- package/cap/bin/lib/cap-pattern-pipeline.cjs +1034 -0
- package/cap/bin/lib/cap-plugin-manifest.cjs +80 -0
- package/cap/bin/lib/cap-realtime-affinity.cjs +399 -0
- package/cap/bin/lib/cap-reconcile.cjs +570 -0
- package/cap/bin/lib/cap-research-gate.cjs +218 -0
- package/cap/bin/lib/cap-scope-filter.cjs +402 -0
- package/cap/bin/lib/cap-semantic-pipeline.cjs +1038 -0
- package/cap/bin/lib/cap-session-extract.cjs +987 -0
- package/cap/bin/lib/cap-session.cjs +445 -0
- package/cap/bin/lib/cap-snapshot-linkage.cjs +963 -0
- package/cap/bin/lib/cap-stack-docs.cjs +646 -0
- package/cap/bin/lib/cap-tag-observer.cjs +371 -0
- package/cap/bin/lib/cap-tag-scanner.cjs +1766 -0
- package/cap/bin/lib/cap-telemetry.cjs +466 -0
- package/cap/bin/lib/cap-test-audit.cjs +1438 -0
- package/cap/bin/lib/cap-thread-migrator.cjs +307 -0
- package/cap/bin/lib/cap-thread-synthesis.cjs +545 -0
- package/cap/bin/lib/cap-thread-tracker.cjs +519 -0
- package/cap/bin/lib/cap-trace.cjs +399 -0
- package/cap/bin/lib/cap-trust-mode.cjs +336 -0
- package/cap/bin/lib/cap-ui-design-editor.cjs +642 -0
- package/cap/bin/lib/cap-ui-mind-map.cjs +712 -0
- package/cap/bin/lib/cap-ui-thread-nav.cjs +693 -0
- package/cap/bin/lib/cap-ui.cjs +1245 -0
- package/cap/bin/lib/cap-upgrade.cjs +1028 -0
- package/cap/bin/lib/cli/arg-helpers.cjs +49 -0
- package/cap/bin/lib/cli/frontmatter-router.cjs +31 -0
- package/cap/bin/lib/cli/init-router.cjs +68 -0
- package/cap/bin/lib/cli/phase-router.cjs +102 -0
- package/cap/bin/lib/cli/state-router.cjs +61 -0
- package/cap/bin/lib/cli/template-router.cjs +37 -0
- package/cap/bin/lib/cli/uat-router.cjs +29 -0
- package/cap/bin/lib/cli/validation-router.cjs +26 -0
- package/cap/bin/lib/cli/verification-router.cjs +31 -0
- package/cap/bin/lib/cli/workstream-router.cjs +39 -0
- package/cap/bin/lib/commands.cjs +961 -0
- package/cap/bin/lib/config.cjs +467 -0
- package/cap/bin/lib/convention-reader.cjs +258 -0
- package/cap/bin/lib/core.cjs +1241 -0
- package/cap/bin/lib/feature-aggregator.cjs +423 -0
- package/cap/bin/lib/frontmatter.cjs +337 -0
- package/cap/bin/lib/init.cjs +1443 -0
- package/cap/bin/lib/manifest-generator.cjs +383 -0
- package/cap/bin/lib/milestone.cjs +253 -0
- package/cap/bin/lib/model-profiles.cjs +69 -0
- package/cap/bin/lib/monorepo-context.cjs +226 -0
- package/cap/bin/lib/monorepo-migrator.cjs +509 -0
- package/cap/bin/lib/phase.cjs +889 -0
- package/cap/bin/lib/profile-output.cjs +989 -0
- package/cap/bin/lib/profile-pipeline.cjs +540 -0
- package/cap/bin/lib/roadmap.cjs +330 -0
- package/cap/bin/lib/security.cjs +394 -0
- package/cap/bin/lib/session-manager.cjs +292 -0
- package/cap/bin/lib/skeleton-generator.cjs +179 -0
- package/cap/bin/lib/state.cjs +1032 -0
- package/cap/bin/lib/template.cjs +231 -0
- package/cap/bin/lib/test-detector.cjs +62 -0
- package/cap/bin/lib/uat.cjs +283 -0
- package/cap/bin/lib/verify.cjs +889 -0
- package/cap/bin/lib/workspace-detector.cjs +371 -0
- package/cap/bin/lib/workstream.cjs +492 -0
- package/cap/commands/gsd/workstreams.md +63 -0
- package/cap/references/arc-standard.md +315 -0
- package/cap/references/cap-agent-architecture.md +101 -0
- package/cap/references/cap-gitignore-template +9 -0
- package/cap/references/cap-zero-deps.md +158 -0
- package/cap/references/checkpoints.md +778 -0
- package/cap/references/continuation-format.md +249 -0
- package/cap/references/contract-test-templates.md +312 -0
- package/cap/references/feature-map-template.md +25 -0
- package/cap/references/git-integration.md +295 -0
- package/cap/references/git-planning-commit.md +38 -0
- package/cap/references/model-profiles.md +174 -0
- package/cap/references/phase-numbering.md +126 -0
- package/cap/references/planning-config.md +202 -0
- package/cap/references/property-test-templates.md +316 -0
- package/cap/references/security-test-templates.md +347 -0
- package/cap/references/session-template.json +8 -0
- package/cap/references/tdd.md +263 -0
- package/cap/references/user-profiling.md +681 -0
- package/cap/references/verification-patterns.md +612 -0
- package/cap/templates/UAT.md +265 -0
- package/cap/templates/claude-md.md +175 -0
- package/cap/templates/codebase/architecture.md +255 -0
- package/cap/templates/codebase/concerns.md +310 -0
- package/cap/templates/codebase/conventions.md +307 -0
- package/cap/templates/codebase/integrations.md +280 -0
- package/cap/templates/codebase/stack.md +186 -0
- package/cap/templates/codebase/structure.md +285 -0
- package/cap/templates/codebase/testing.md +480 -0
- package/cap/templates/config.json +44 -0
- package/cap/templates/context.md +352 -0
- package/cap/templates/continue-here.md +78 -0
- package/cap/templates/copilot-instructions.md +7 -0
- package/cap/templates/debug-subagent-prompt.md +91 -0
- package/cap/templates/discussion-log.md +63 -0
- package/cap/templates/milestone-archive.md +123 -0
- package/cap/templates/milestone.md +115 -0
- package/cap/templates/phase-prompt.md +610 -0
- package/cap/templates/planner-subagent-prompt.md +117 -0
- package/cap/templates/project.md +186 -0
- package/cap/templates/requirements.md +231 -0
- package/cap/templates/research-project/ARCHITECTURE.md +204 -0
- package/cap/templates/research-project/FEATURES.md +147 -0
- package/cap/templates/research-project/PITFALLS.md +200 -0
- package/cap/templates/research-project/STACK.md +120 -0
- package/cap/templates/research-project/SUMMARY.md +170 -0
- package/cap/templates/research.md +552 -0
- package/cap/templates/roadmap.md +202 -0
- package/cap/templates/state.md +176 -0
- package/cap/templates/summary.md +364 -0
- package/cap/templates/user-preferences.md +498 -0
- package/cap/templates/verification-report.md +322 -0
- package/cap/workflows/add-phase.md +112 -0
- package/cap/workflows/add-tests.md +351 -0
- package/cap/workflows/add-todo.md +158 -0
- package/cap/workflows/audit-milestone.md +340 -0
- package/cap/workflows/audit-uat.md +109 -0
- package/cap/workflows/autonomous.md +891 -0
- package/cap/workflows/check-todos.md +177 -0
- package/cap/workflows/cleanup.md +152 -0
- package/cap/workflows/complete-milestone.md +767 -0
- package/cap/workflows/diagnose-issues.md +231 -0
- package/cap/workflows/discovery-phase.md +289 -0
- package/cap/workflows/discuss-phase-assumptions.md +653 -0
- package/cap/workflows/discuss-phase.md +1049 -0
- package/cap/workflows/do.md +104 -0
- package/cap/workflows/execute-phase.md +846 -0
- package/cap/workflows/execute-plan.md +514 -0
- package/cap/workflows/fast.md +105 -0
- package/cap/workflows/forensics.md +265 -0
- package/cap/workflows/health.md +181 -0
- package/cap/workflows/help.md +660 -0
- package/cap/workflows/insert-phase.md +130 -0
- package/cap/workflows/list-phase-assumptions.md +178 -0
- package/cap/workflows/list-workspaces.md +56 -0
- package/cap/workflows/manager.md +362 -0
- package/cap/workflows/map-codebase.md +377 -0
- package/cap/workflows/milestone-summary.md +223 -0
- package/cap/workflows/new-milestone.md +486 -0
- package/cap/workflows/new-project.md +1250 -0
- package/cap/workflows/new-workspace.md +237 -0
- package/cap/workflows/next.md +97 -0
- package/cap/workflows/node-repair.md +92 -0
- package/cap/workflows/note.md +156 -0
- package/cap/workflows/pause-work.md +176 -0
- package/cap/workflows/plan-milestone-gaps.md +273 -0
- package/cap/workflows/plan-phase.md +857 -0
- package/cap/workflows/plant-seed.md +169 -0
- package/cap/workflows/pr-branch.md +129 -0
- package/cap/workflows/profile-user.md +449 -0
- package/cap/workflows/progress.md +507 -0
- package/cap/workflows/quick.md +757 -0
- package/cap/workflows/remove-phase.md +155 -0
- package/cap/workflows/remove-workspace.md +90 -0
- package/cap/workflows/research-phase.md +82 -0
- package/cap/workflows/resume-project.md +326 -0
- package/cap/workflows/review.md +228 -0
- package/cap/workflows/session-report.md +146 -0
- package/cap/workflows/settings.md +283 -0
- package/cap/workflows/ship.md +228 -0
- package/cap/workflows/stats.md +60 -0
- package/cap/workflows/transition.md +671 -0
- package/cap/workflows/ui-phase.md +298 -0
- package/cap/workflows/ui-review.md +161 -0
- package/cap/workflows/update.md +323 -0
- package/cap/workflows/validate-phase.md +170 -0
- package/cap/workflows/verify-phase.md +254 -0
- package/cap/workflows/verify-work.md +637 -0
- package/commands/cap/annotate.md +165 -0
- package/commands/cap/brainstorm.md +393 -0
- package/commands/cap/checkpoint.md +106 -0
- package/commands/cap/completeness.md +94 -0
- package/commands/cap/continue.md +72 -0
- package/commands/cap/debug.md +588 -0
- package/commands/cap/deps.md +169 -0
- package/commands/cap/design.md +479 -0
- package/commands/cap/init.md +354 -0
- package/commands/cap/iterate.md +249 -0
- package/commands/cap/learn.md +459 -0
- package/commands/cap/memory.md +275 -0
- package/commands/cap/migrate-feature-map.md +91 -0
- package/commands/cap/migrate-memory.md +108 -0
- package/commands/cap/migrate-tags.md +91 -0
- package/commands/cap/migrate.md +131 -0
- package/commands/cap/prototype.md +510 -0
- package/commands/cap/reconcile.md +121 -0
- package/commands/cap/review.md +360 -0
- package/commands/cap/save.md +72 -0
- package/commands/cap/scan.md +404 -0
- package/commands/cap/start.md +356 -0
- package/commands/cap/status.md +118 -0
- package/commands/cap/test-audit.md +262 -0
- package/commands/cap/test.md +394 -0
- package/commands/cap/trace.md +133 -0
- package/commands/cap/ui.md +167 -0
- package/hooks/dist/cap-check-update.js +115 -0
- package/hooks/dist/cap-context-monitor.js +185 -0
- package/hooks/dist/cap-learn-review-hook.js +114 -0
- package/hooks/dist/cap-learning-hook.js +192 -0
- package/hooks/dist/cap-memory.js +299 -0
- package/hooks/dist/cap-prompt-guard.js +97 -0
- package/hooks/dist/cap-statusline.js +157 -0
- package/hooks/dist/cap-tag-observer.js +115 -0
- package/hooks/dist/cap-version-check.js +112 -0
- package/hooks/dist/cap-workflow-guard.js +175 -0
- package/hooks/hooks.json +55 -0
- package/package.json +58 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +93 -0
- package/scripts/cap-removal-checklist.md +202 -0
- package/scripts/prompt-injection-scan.sh +199 -0
- package/scripts/run-tests.cjs +181 -0
- package/scripts/secret-scan.sh +227 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<planning_config>
|
|
2
|
+
|
|
3
|
+
Configuration options for `.planning/` directory behavior.
|
|
4
|
+
|
|
5
|
+
<config_schema>
|
|
6
|
+
```json
|
|
7
|
+
"planning": {
|
|
8
|
+
"commit_docs": true,
|
|
9
|
+
"search_gitignored": false
|
|
10
|
+
},
|
|
11
|
+
"git": {
|
|
12
|
+
"branching_strategy": "none",
|
|
13
|
+
"phase_branch_template": "gsd/phase-{phase}-{slug}",
|
|
14
|
+
"milestone_branch_template": "gsd/{milestone}-{slug}",
|
|
15
|
+
"quick_branch_template": null
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Option | Default | Description |
|
|
20
|
+
|--------|---------|-------------|
|
|
21
|
+
| `commit_docs` | `true` | Whether to commit planning artifacts to git |
|
|
22
|
+
| `search_gitignored` | `false` | Add `--no-ignore` to broad rg searches |
|
|
23
|
+
| `git.branching_strategy` | `"none"` | Git branching approach: `"none"`, `"phase"`, or `"milestone"` |
|
|
24
|
+
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
|
25
|
+
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |
|
|
26
|
+
| `git.quick_branch_template` | `null` | Optional branch template for quick-task runs |
|
|
27
|
+
</config_schema>
|
|
28
|
+
|
|
29
|
+
<commit_docs_behavior>
|
|
30
|
+
|
|
31
|
+
**When `commit_docs: true` (default):**
|
|
32
|
+
- Planning files committed normally
|
|
33
|
+
- SUMMARY.md, STATE.md, ROADMAP.md tracked in git
|
|
34
|
+
- Full history of planning decisions preserved
|
|
35
|
+
|
|
36
|
+
**When `commit_docs: false`:**
|
|
37
|
+
- Skip all `git add`/`git commit` for `.planning/` files
|
|
38
|
+
- User must add `.planning/` to `.gitignore`
|
|
39
|
+
- Useful for: OSS contributions, client projects, keeping planning private
|
|
40
|
+
|
|
41
|
+
**Using cap-tools.cjs (preferred):**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Commit with automatic commit_docs + gitignore checks:
|
|
45
|
+
node "$HOME/.claude/cap/bin/cap-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
|
46
|
+
|
|
47
|
+
# Load config via state load (returns JSON):
|
|
48
|
+
INIT=$(node "$HOME/.claude/cap/bin/cap-tools.cjs" state load)
|
|
49
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
50
|
+
# commit_docs is available in the JSON output
|
|
51
|
+
|
|
52
|
+
# Or use init commands which include commit_docs:
|
|
53
|
+
INIT=$(node "$HOME/.claude/cap/bin/cap-tools.cjs" init execute-phase "1")
|
|
54
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
55
|
+
# commit_docs is included in all init command outputs
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Auto-detection:** If `.planning/` is gitignored, `commit_docs` is automatically `false` regardless of config.json. This prevents git errors when users have `.planning/` in `.gitignore`.
|
|
59
|
+
|
|
60
|
+
**Commit via CLI (handles checks automatically):**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
node "$HOME/.claude/cap/bin/cap-tools.cjs" commit "docs: update state" --files .planning/STATE.md
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The CLI checks `commit_docs` config and gitignore status internally — no manual conditionals needed.
|
|
67
|
+
|
|
68
|
+
</commit_docs_behavior>
|
|
69
|
+
|
|
70
|
+
<search_behavior>
|
|
71
|
+
|
|
72
|
+
**When `search_gitignored: false` (default):**
|
|
73
|
+
- Standard rg behavior (respects .gitignore)
|
|
74
|
+
- Direct path searches work: `rg "pattern" .planning/` finds files
|
|
75
|
+
- Broad searches skip gitignored: `rg "pattern"` skips `.planning/`
|
|
76
|
+
|
|
77
|
+
**When `search_gitignored: true`:**
|
|
78
|
+
- Add `--no-ignore` to broad rg searches that should include `.planning/`
|
|
79
|
+
- Only needed when searching entire repo and expecting `.planning/` matches
|
|
80
|
+
|
|
81
|
+
**Note:** Most GSD operations use direct file reads or explicit paths, which work regardless of gitignore status.
|
|
82
|
+
|
|
83
|
+
</search_behavior>
|
|
84
|
+
|
|
85
|
+
<setup_uncommitted_mode>
|
|
86
|
+
|
|
87
|
+
To use uncommitted mode:
|
|
88
|
+
|
|
89
|
+
1. **Set config:**
|
|
90
|
+
```json
|
|
91
|
+
"planning": {
|
|
92
|
+
"commit_docs": false,
|
|
93
|
+
"search_gitignored": true
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
2. **Add to .gitignore:**
|
|
98
|
+
```
|
|
99
|
+
.planning/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
3. **Existing tracked files:** If `.planning/` was previously tracked:
|
|
103
|
+
```bash
|
|
104
|
+
git rm -r --cached .planning/
|
|
105
|
+
git commit -m "chore: stop tracking planning docs"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
4. **Branch merges:** When using `branching_strategy: phase` or `milestone`, the `complete-milestone` workflow automatically strips `.planning/` files from staging before merge commits when `commit_docs: false`.
|
|
109
|
+
|
|
110
|
+
</setup_uncommitted_mode>
|
|
111
|
+
|
|
112
|
+
<branching_strategy_behavior>
|
|
113
|
+
|
|
114
|
+
**Branching Strategies:**
|
|
115
|
+
|
|
116
|
+
| Strategy | When branch created | Branch scope | Merge point |
|
|
117
|
+
|----------|---------------------|--------------|-------------|
|
|
118
|
+
| `none` | Never | N/A | N/A |
|
|
119
|
+
| `phase` | At `execute-phase` start | Single phase | User merges after phase |
|
|
120
|
+
| `milestone` | At first `execute-phase` of milestone | Entire milestone | At `complete-milestone` |
|
|
121
|
+
|
|
122
|
+
**When `git.branching_strategy: "none"` (default):**
|
|
123
|
+
- All work commits to current branch
|
|
124
|
+
- Standard GSD behavior
|
|
125
|
+
|
|
126
|
+
**When `git.branching_strategy: "phase"`:**
|
|
127
|
+
- `execute-phase` creates/switches to a branch before execution
|
|
128
|
+
- Branch name from `phase_branch_template` (e.g., `gsd/phase-03-authentication`)
|
|
129
|
+
- All plan commits go to that branch
|
|
130
|
+
- User merges branches manually after phase completion
|
|
131
|
+
- `complete-milestone` offers to merge all phase branches
|
|
132
|
+
|
|
133
|
+
**When `git.branching_strategy: "milestone"`:**
|
|
134
|
+
- First `execute-phase` of milestone creates the milestone branch
|
|
135
|
+
- Branch name from `milestone_branch_template` (e.g., `gsd/v1.0-mvp`)
|
|
136
|
+
- All phases in milestone commit to same branch
|
|
137
|
+
- `complete-milestone` offers to merge milestone branch to main
|
|
138
|
+
|
|
139
|
+
**Template variables:**
|
|
140
|
+
|
|
141
|
+
| Variable | Available in | Description |
|
|
142
|
+
|----------|--------------|-------------|
|
|
143
|
+
| `{phase}` | phase_branch_template | Zero-padded phase number (e.g., "03") |
|
|
144
|
+
| `{slug}` | Both | Lowercase, hyphenated name |
|
|
145
|
+
| `{milestone}` | milestone_branch_template | Milestone version (e.g., "v1.0") |
|
|
146
|
+
|
|
147
|
+
**Checking the config:**
|
|
148
|
+
|
|
149
|
+
Use `init execute-phase` which returns all config as JSON:
|
|
150
|
+
```bash
|
|
151
|
+
INIT=$(node "$HOME/.claude/cap/bin/cap-tools.cjs" init execute-phase "1")
|
|
152
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
153
|
+
# JSON output includes: branching_strategy, phase_branch_template, milestone_branch_template
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or use `state load` for the config values:
|
|
157
|
+
```bash
|
|
158
|
+
INIT=$(node "$HOME/.claude/cap/bin/cap-tools.cjs" state load)
|
|
159
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
160
|
+
# Parse branching_strategy, phase_branch_template, milestone_branch_template from JSON
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Branch creation:**
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# For phase strategy
|
|
167
|
+
if [ "$BRANCHING_STRATEGY" = "phase" ]; then
|
|
168
|
+
PHASE_SLUG=$(echo "$PHASE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
|
169
|
+
BRANCH_NAME=$(echo "$PHASE_BRANCH_TEMPLATE" | sed "s/{phase}/$PADDED_PHASE/g" | sed "s/{slug}/$PHASE_SLUG/g")
|
|
170
|
+
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
# For milestone strategy
|
|
174
|
+
if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
|
|
175
|
+
MILESTONE_SLUG=$(echo "$MILESTONE_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
|
|
176
|
+
BRANCH_NAME=$(echo "$MILESTONE_BRANCH_TEMPLATE" | sed "s/{milestone}/$MILESTONE_VERSION/g" | sed "s/{slug}/$MILESTONE_SLUG/g")
|
|
177
|
+
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
|
|
178
|
+
fi
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Merge options at complete-milestone:**
|
|
182
|
+
|
|
183
|
+
| Option | Git command | Result |
|
|
184
|
+
|--------|-------------|--------|
|
|
185
|
+
| Squash merge (recommended) | `git merge --squash` | Single clean commit per branch |
|
|
186
|
+
| Merge with history | `git merge --no-ff` | Preserves all individual commits |
|
|
187
|
+
| Delete without merging | `git branch -D` | Discard branch work |
|
|
188
|
+
| Keep branches | (none) | Manual handling later |
|
|
189
|
+
|
|
190
|
+
Squash merge is recommended — keeps main branch history clean while preserving the full development history in the branch (until deleted).
|
|
191
|
+
|
|
192
|
+
**Use cases:**
|
|
193
|
+
|
|
194
|
+
| Strategy | Best for |
|
|
195
|
+
|----------|----------|
|
|
196
|
+
| `none` | Solo development, simple projects |
|
|
197
|
+
| `phase` | Code review per phase, granular rollback, team collaboration |
|
|
198
|
+
| `milestone` | Release branches, staging environments, PR per version |
|
|
199
|
+
|
|
200
|
+
</branching_strategy_behavior>
|
|
201
|
+
|
|
202
|
+
</planning_config>
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Property-Based Test Templates
|
|
2
|
+
|
|
3
|
+
Reference document for the cap-validator agent (test mode) when generating property-based tests. Property-based testing verifies invariants that hold for ALL valid inputs, not just hand-picked examples. Recommend `fast-check` as the property testing library.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Booking Invariants
|
|
8
|
+
|
|
9
|
+
Properties that must hold for any valid booking system.
|
|
10
|
+
|
|
11
|
+
### No overlapping bookings
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { describe, it, expect } from 'vitest';
|
|
15
|
+
import * as fc from 'fast-check';
|
|
16
|
+
|
|
17
|
+
describe('Property: booking invariants', () => {
|
|
18
|
+
// Arbitrary for generating valid booking time ranges
|
|
19
|
+
const bookingArb = fc.record({
|
|
20
|
+
start: fc.date({ min: new Date('2026-01-01'), max: new Date('2026-12-31') }),
|
|
21
|
+
durationMinutes: fc.integer({ min: 15, max: 480 }),
|
|
22
|
+
resourceId: fc.constantFrom('room-a', 'room-b', 'room-c'),
|
|
23
|
+
}).map(({ start, durationMinutes, resourceId }) => ({
|
|
24
|
+
start,
|
|
25
|
+
end: new Date(start.getTime() + durationMinutes * 60000),
|
|
26
|
+
resourceId,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
it('no two confirmed bookings overlap for the same resource', () => {
|
|
30
|
+
fc.assert(
|
|
31
|
+
fc.property(
|
|
32
|
+
fc.array(bookingArb, { minLength: 2, maxLength: 20 }),
|
|
33
|
+
(bookings) => {
|
|
34
|
+
const confirmed = createBookings(bookings); // system under test
|
|
35
|
+
const byResource = groupByResource(confirmed);
|
|
36
|
+
|
|
37
|
+
for (const [, resourceBookings] of Object.entries(byResource)) {
|
|
38
|
+
const sorted = resourceBookings.sort((a, b) => a.start - b.start);
|
|
39
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
40
|
+
// No overlap: previous end <= current start
|
|
41
|
+
expect(sorted[i - 1].end.getTime()).toBeLessThanOrEqual(sorted[i].start.getTime());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('booking duration is always positive', () => {
|
|
50
|
+
fc.assert(
|
|
51
|
+
fc.property(bookingArb, (booking) => {
|
|
52
|
+
expect(booking.end.getTime()).toBeGreaterThan(booking.start.getTime());
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('booking end is always after start', () => {
|
|
58
|
+
fc.assert(
|
|
59
|
+
fc.property(bookingArb, (booking) => {
|
|
60
|
+
const created = createBooking(booking); // system under test
|
|
61
|
+
expect(new Date(created.end_time).getTime()).toBeGreaterThan(
|
|
62
|
+
new Date(created.start_time).getTime()
|
|
63
|
+
);
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('cancelled booking frees the time slot', () => {
|
|
69
|
+
fc.assert(
|
|
70
|
+
fc.property(bookingArb, (booking) => {
|
|
71
|
+
const created = createBooking(booking);
|
|
72
|
+
cancelBooking(created.id);
|
|
73
|
+
// Same time slot should now be available
|
|
74
|
+
const rebooked = createBooking(booking);
|
|
75
|
+
expect(rebooked.status).toBe('confirmed');
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 2. Auth Invariants
|
|
85
|
+
|
|
86
|
+
Properties that must hold for any authentication/token system.
|
|
87
|
+
|
|
88
|
+
### Token encode/decode roundtrip
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
describe('Property: auth token invariants', () => {
|
|
92
|
+
const userPayloadArb = fc.record({
|
|
93
|
+
sub: fc.uuid(),
|
|
94
|
+
email: fc.emailAddress(),
|
|
95
|
+
role: fc.constantFrom('user', 'admin', 'moderator'),
|
|
96
|
+
name: fc.string({ minLength: 1, maxLength: 100 }),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('decode(encode(payload)) === payload', () => {
|
|
100
|
+
fc.assert(
|
|
101
|
+
fc.property(userPayloadArb, (payload) => {
|
|
102
|
+
const token = encodeToken(payload);
|
|
103
|
+
const decoded = decodeToken(token);
|
|
104
|
+
expect(decoded.sub).toBe(payload.sub);
|
|
105
|
+
expect(decoded.email).toBe(payload.email);
|
|
106
|
+
expect(decoded.role).toBe(payload.role);
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('expired tokens are always rejected', () => {
|
|
112
|
+
fc.assert(
|
|
113
|
+
fc.property(
|
|
114
|
+
userPayloadArb,
|
|
115
|
+
fc.integer({ min: 1, max: 365 * 24 * 3600 }), // seconds in past
|
|
116
|
+
(payload, secondsAgo) => {
|
|
117
|
+
const expiredAt = Math.floor(Date.now() / 1000) - secondsAgo;
|
|
118
|
+
const token = encodeToken({ ...payload, exp: expiredAt });
|
|
119
|
+
expect(() => verifyToken(token)).toThrow();
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('valid tokens are always accepted within TTL', () => {
|
|
126
|
+
fc.assert(
|
|
127
|
+
fc.property(
|
|
128
|
+
userPayloadArb,
|
|
129
|
+
fc.integer({ min: 1, max: 3600 }), // seconds in future
|
|
130
|
+
(payload, secondsFromNow) => {
|
|
131
|
+
const expiresAt = Math.floor(Date.now() / 1000) + secondsFromNow;
|
|
132
|
+
const token = encodeToken({ ...payload, exp: expiresAt });
|
|
133
|
+
const result = verifyToken(token);
|
|
134
|
+
expect(result.sub).toBe(payload.sub);
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('different payloads produce different tokens', () => {
|
|
141
|
+
fc.assert(
|
|
142
|
+
fc.property(userPayloadArb, userPayloadArb, (a, b) => {
|
|
143
|
+
fc.pre(a.sub !== b.sub); // precondition: different users
|
|
144
|
+
const tokenA = encodeToken(a);
|
|
145
|
+
const tokenB = encodeToken(b);
|
|
146
|
+
expect(tokenA).not.toBe(tokenB);
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 3. Data Invariants
|
|
156
|
+
|
|
157
|
+
Properties that must hold for any data persistence layer.
|
|
158
|
+
|
|
159
|
+
### Write-then-read consistency
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
describe('Property: data persistence invariants', () => {
|
|
163
|
+
const resourceArb = fc.record({
|
|
164
|
+
name: fc.string({ minLength: 1, maxLength: 200 }),
|
|
165
|
+
description: fc.string({ maxLength: 1000 }),
|
|
166
|
+
tags: fc.array(fc.string({ minLength: 1, maxLength: 50 }), { maxLength: 10 }),
|
|
167
|
+
active: fc.boolean(),
|
|
168
|
+
price: fc.float({ min: 0, max: 10000, noNaN: true }),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('write then read returns same data', () => {
|
|
172
|
+
fc.assert(
|
|
173
|
+
fc.property(resourceArb, async (resource) => {
|
|
174
|
+
const created = await createResource(resource);
|
|
175
|
+
const fetched = await getResource(created.id);
|
|
176
|
+
expect(fetched.name).toBe(resource.name);
|
|
177
|
+
expect(fetched.description).toBe(resource.description);
|
|
178
|
+
expect(fetched.tags).toEqual(resource.tags);
|
|
179
|
+
expect(fetched.active).toBe(resource.active);
|
|
180
|
+
})
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('delete then read returns nothing', () => {
|
|
185
|
+
fc.assert(
|
|
186
|
+
fc.property(resourceArb, async (resource) => {
|
|
187
|
+
const created = await createResource(resource);
|
|
188
|
+
await deleteResource(created.id);
|
|
189
|
+
const fetched = await getResource(created.id);
|
|
190
|
+
expect(fetched).toBeNull();
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('update preserves unmodified fields', () => {
|
|
196
|
+
fc.assert(
|
|
197
|
+
fc.property(
|
|
198
|
+
resourceArb,
|
|
199
|
+
fc.record({ name: fc.string({ minLength: 1, maxLength: 200 }) }),
|
|
200
|
+
async (resource, update) => {
|
|
201
|
+
const created = await createResource(resource);
|
|
202
|
+
await updateResource(created.id, update);
|
|
203
|
+
const fetched = await getResource(created.id);
|
|
204
|
+
// Updated field changed
|
|
205
|
+
expect(fetched.name).toBe(update.name);
|
|
206
|
+
// Non-updated fields preserved
|
|
207
|
+
expect(fetched.description).toBe(resource.description);
|
|
208
|
+
expect(fetched.active).toBe(resource.active);
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('listing includes all created resources', () => {
|
|
215
|
+
fc.assert(
|
|
216
|
+
fc.property(
|
|
217
|
+
fc.array(resourceArb, { minLength: 1, maxLength: 10 }),
|
|
218
|
+
async (resources) => {
|
|
219
|
+
const ids = [];
|
|
220
|
+
for (const r of resources) {
|
|
221
|
+
const created = await createResource(r);
|
|
222
|
+
ids.push(created.id);
|
|
223
|
+
}
|
|
224
|
+
const list = await listResources();
|
|
225
|
+
for (const id of ids) {
|
|
226
|
+
expect(list.some(item => item.id === id)).toBe(true);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### String handling invariants
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
describe('Property: string handling', () => {
|
|
239
|
+
it('stored strings are never truncated silently', () => {
|
|
240
|
+
fc.assert(
|
|
241
|
+
fc.property(
|
|
242
|
+
fc.string({ minLength: 1, maxLength: 500 }),
|
|
243
|
+
async (input) => {
|
|
244
|
+
const created = await createResource({ name: input });
|
|
245
|
+
const fetched = await getResource(created.id);
|
|
246
|
+
expect(fetched.name.length).toBe(input.length);
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('unicode strings roundtrip correctly', () => {
|
|
253
|
+
fc.assert(
|
|
254
|
+
fc.property(
|
|
255
|
+
fc.fullUnicode(), // generates full Unicode range
|
|
256
|
+
async (input) => {
|
|
257
|
+
fc.pre(input.length > 0 && input.length <= 200);
|
|
258
|
+
const created = await createResource({ name: input });
|
|
259
|
+
const fetched = await getResource(created.id);
|
|
260
|
+
expect(fetched.name).toBe(input);
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 4. Pagination Invariants
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
describe('Property: pagination', () => {
|
|
274
|
+
it('paginating through all pages returns all items', () => {
|
|
275
|
+
fc.assert(
|
|
276
|
+
fc.property(
|
|
277
|
+
fc.integer({ min: 1, max: 100 }), // total items
|
|
278
|
+
fc.integer({ min: 1, max: 20 }), // page size
|
|
279
|
+
async (totalItems, pageSize) => {
|
|
280
|
+
// Seed database with totalItems
|
|
281
|
+
await seedResources(totalItems);
|
|
282
|
+
const allItems = [];
|
|
283
|
+
let page = 1;
|
|
284
|
+
let hasMore = true;
|
|
285
|
+
while (hasMore) {
|
|
286
|
+
const result = await listResources({ page, pageSize });
|
|
287
|
+
allItems.push(...result.data);
|
|
288
|
+
hasMore = result.data.length === pageSize;
|
|
289
|
+
page++;
|
|
290
|
+
}
|
|
291
|
+
expect(allItems.length).toBe(totalItems);
|
|
292
|
+
// No duplicates
|
|
293
|
+
const ids = allItems.map(i => i.id);
|
|
294
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Usage Notes
|
|
305
|
+
|
|
306
|
+
When generating property-based tests:
|
|
307
|
+
|
|
308
|
+
1. **Install fast-check**: `npm install -D fast-check`
|
|
309
|
+
2. **Start with invariants** -- what must ALWAYS be true, regardless of input?
|
|
310
|
+
3. **Use `fc.pre()` for preconditions** -- skip inputs that don't apply
|
|
311
|
+
4. **Keep properties simple** -- one property per test, clearly named
|
|
312
|
+
5. **Use shrinking** -- fast-check automatically finds minimal failing examples
|
|
313
|
+
6. **Combine with example tests** -- property tests complement, not replace, example-based tests
|
|
314
|
+
7. **For async properties**: use `fc.assert(fc.asyncProperty(...))`
|
|
315
|
+
8. **Set reasonable bounds** -- limit array sizes and string lengths to keep tests fast
|
|
316
|
+
9. **Suggest property tests for**: any code with invariants, encode/decode pairs, sorting, filtering, pagination, CRUD operations
|