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,445 @@
|
|
|
1
|
+
// @cap-context CAP v2.0 session manager -- manages .cap/SESSION.json for cross-conversation workflow state.
|
|
2
|
+
// @cap-decision SESSION.json is ephemeral (gitignored) -- it tracks the current developer's workflow state, not project state. Project state lives in FEATURE-MAP.md.
|
|
3
|
+
// @cap-decision JSON format (not markdown) -- session state is machine-consumed, not human-read. JSON is faster to parse and type-safe.
|
|
4
|
+
// @cap-constraint Zero external dependencies -- uses only Node.js built-ins (fs, path).
|
|
5
|
+
// @cap-pattern All session reads/writes go through this module -- no direct fs.readFileSync of SESSION.json elsewhere.
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// @cap-feature(feature:F-003) Session State Management — .cap/SESSION.json for cross-conversation workflow state
|
|
10
|
+
|
|
11
|
+
// @cap-history(sessions:2, edits:4, since:2026-05-08, learned:2026-05-08) Frequently modified — 2 sessions, 4 edits
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
|
|
15
|
+
// @cap-decision Session schema is flat and extensible -- new workflow commands can add keys without schema migration.
|
|
16
|
+
// @cap-todo(ref:AC-16) SESSION.json tracks ephemeral workflow state: active feature ID, current workflow step, session timestamps
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} CapQuickMode
|
|
19
|
+
* @property {boolean} active - Whether quick-mode is currently active
|
|
20
|
+
* @property {string|null} feature - Feature ID being worked on (mirrors activeFeature when entered via /cap:quick)
|
|
21
|
+
* @property {string|null} startedAt - ISO timestamp when quick-mode started
|
|
22
|
+
* @property {string|null} startCommit - git HEAD SHA at quick-mode entry — used by /cap:finalize to compute changed files
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} CapSession
|
|
27
|
+
* @property {string} version - Session schema version (e.g., "2.0.0")
|
|
28
|
+
* @property {string|null} lastCommand - Last /cap: command executed
|
|
29
|
+
* @property {string|null} lastCommandTimestamp - ISO timestamp of last command
|
|
30
|
+
* @property {string|null} activeApp - Currently focused app path (e.g., "apps/flow") or null for single-repo/root
|
|
31
|
+
* @property {string|null} activeFeature - Currently focused feature ID
|
|
32
|
+
* @property {string|null} step - Current workflow step
|
|
33
|
+
* @property {string|null} startedAt - ISO timestamp of when session started
|
|
34
|
+
* @property {string|null} activeDebugSession - Active debug session ID
|
|
35
|
+
* @property {'A'|'B'|'C'} trustMode - @cap-feature(feature:F-075) Trust-Mode slot — mirrored from .cap/config.json by cap-trust-mode.
|
|
36
|
+
* @property {CapQuickMode} quickMode - @cap-feature(feature:F-092) Two-phase workflow state — Phase 1 fast-lane editing
|
|
37
|
+
* @property {Object<string,string>} metadata - Extensible key-value metadata
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const CAP_DIR = '.cap';
|
|
41
|
+
const SESSION_FILE = 'SESSION.json';
|
|
42
|
+
|
|
43
|
+
// @cap-todo(ref:AC-3) .cap/.gitignore ignores SESSION.json (ephemeral state shall not be committed)
|
|
44
|
+
const GITIGNORE_CONTENT = `# CAP ephemeral state -- do not commit
|
|
45
|
+
SESSION.json
|
|
46
|
+
debug/
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const CAP_MEMORY_RULE = `## CAP Project Memory
|
|
50
|
+
|
|
51
|
+
This project uses CAP (Code as Plan) with a project memory system at \`.cap/memory/\`.
|
|
52
|
+
|
|
53
|
+
The pipeline supports **two layouts** (F-093). Detect which is active before reading:
|
|
54
|
+
|
|
55
|
+
### V6 layout (per-feature, opt-in via \`.cap/config.json: { memory: { layout: "v6" } }\`)
|
|
56
|
+
|
|
57
|
+
When the top-level \`decisions.md\` or \`pitfalls.md\` contains the marker \`(V6 Index)\` in its title, this project is on V6:
|
|
58
|
+
|
|
59
|
+
1. **Read \`.cap/memory/decisions.md\` and \`.cap/memory/pitfalls.md\` first** — these are short index tables (\`Destination | Count | File\`) listing per-feature memory files.
|
|
60
|
+
2. **Then load only the per-feature file relevant to the current task** — e.g. \`.cap/memory/features/F-XXX-<topic>.md\` if you're working on F-XXX, or a \`platform/<topic>.md\` for cross-cutting concerns.
|
|
61
|
+
3. Skip reading the index entries you don't need — that is the whole point of V6 (token-cost-of-read reduction).
|
|
62
|
+
|
|
63
|
+
### V5 layout (legacy, default for projects without the V6 config flag)
|
|
64
|
+
|
|
65
|
+
When the top-level files do NOT have the \`(V6 Index)\` marker, read them directly as monolithic memory:
|
|
66
|
+
|
|
67
|
+
- \`.cap/memory/decisions.md\` — architectural decisions extracted from code tags and sessions
|
|
68
|
+
- \`.cap/memory/pitfalls.md\` — known pitfalls, gotchas, and things that broke before
|
|
69
|
+
- \`.cap/memory/patterns.md\` — recurring patterns and conventions observed in the codebase
|
|
70
|
+
- \`.cap/memory/hotspots.md\` — frequently edited files and areas of high churn
|
|
71
|
+
|
|
72
|
+
These files are auto-generated by the CAP memory pipeline after each session. They complement your auto-memory (user preferences, feedback) with project-specific technical context.
|
|
73
|
+
|
|
74
|
+
Use this knowledge to:
|
|
75
|
+
- Avoid repeating past mistakes documented in pitfalls
|
|
76
|
+
- Follow established patterns when writing new code
|
|
77
|
+
- Understand the reasoning behind architectural choices
|
|
78
|
+
- Focus attention on hotspot files that change frequently
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
// @cap-api getDefaultSession() -- Returns a fresh default session object.
|
|
82
|
+
// @cap-todo(ref:AC-2) SESSION.json with valid JSON structure: { active_feature: null, step: null, started_at: null }
|
|
83
|
+
/**
|
|
84
|
+
* @returns {CapSession}
|
|
85
|
+
*/
|
|
86
|
+
function getDefaultSession() {
|
|
87
|
+
return {
|
|
88
|
+
version: '2.0.0',
|
|
89
|
+
lastCommand: null,
|
|
90
|
+
lastCommandTimestamp: null,
|
|
91
|
+
activeApp: null,
|
|
92
|
+
activeFeature: null,
|
|
93
|
+
step: null,
|
|
94
|
+
startedAt: null,
|
|
95
|
+
activeDebugSession: null,
|
|
96
|
+
activeThread: null,
|
|
97
|
+
// @cap-feature(feature:F-075) Trust-Mode slot — mirrored from .cap/config.json.
|
|
98
|
+
// @cap-todo(ac:F-075/AC-1) SESSION.json carries trustMode field with default 'A'.
|
|
99
|
+
// Sessions created before F-075 will land here via the { ...default, ...parsed } merge
|
|
100
|
+
// in loadSession(), so backfill is automatic on first read.
|
|
101
|
+
trustMode: 'A',
|
|
102
|
+
// @cap-feature(feature:F-092) Two-phase workflow — Phase 1 (Visual Iteration) vs Phase 2 (Solidify).
|
|
103
|
+
// Default { active: false } so existing sessions / non-quick-mode workflows are unaffected.
|
|
104
|
+
quickMode: {
|
|
105
|
+
active: false,
|
|
106
|
+
feature: null,
|
|
107
|
+
startedAt: null,
|
|
108
|
+
startCommit: null,
|
|
109
|
+
},
|
|
110
|
+
metadata: {},
|
|
111
|
+
// @cap-feature(feature:F-057) Checkpoint persistence fields — additive, null by default.
|
|
112
|
+
// lastCheckpointAt = ISO timestamp of the most recent /cap:checkpoint that detected a breakpoint.
|
|
113
|
+
// lastCheckpointSnapshot = minimal snapshot of feature states + AC statuses at that moment,
|
|
114
|
+
// so future /cap:checkpoint runs can diff against it.
|
|
115
|
+
lastCheckpointAt: null,
|
|
116
|
+
lastCheckpointSnapshot: null,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// @cap-api loadSession(projectRoot) -- Loads .cap/SESSION.json. Returns default session if file missing or corrupt.
|
|
121
|
+
// @cap-todo(ref:AC-19) SESSION.json is the only mutable session artifact
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
124
|
+
* @returns {CapSession}
|
|
125
|
+
*/
|
|
126
|
+
function loadSession(projectRoot) {
|
|
127
|
+
const sessionPath = path.join(projectRoot, CAP_DIR, SESSION_FILE);
|
|
128
|
+
try {
|
|
129
|
+
if (!fs.existsSync(sessionPath)) return getDefaultSession();
|
|
130
|
+
const content = fs.readFileSync(sessionPath, 'utf8');
|
|
131
|
+
const parsed = JSON.parse(content);
|
|
132
|
+
// Merge with defaults to handle missing keys from older versions
|
|
133
|
+
return { ...getDefaultSession(), ...parsed };
|
|
134
|
+
} catch (_e) {
|
|
135
|
+
// Corrupt JSON -- return default
|
|
136
|
+
return getDefaultSession();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// @cap-api saveSession(projectRoot, session) -- Writes .cap/SESSION.json. Creates .cap/ directory if needed.
|
|
141
|
+
// @cap-todo(ref:AC-18) SESSION.json shall not be committed to version control (enforced by .cap/.gitignore)
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
144
|
+
* @param {CapSession} session - Session data to persist
|
|
145
|
+
*/
|
|
146
|
+
function saveSession(projectRoot, session) {
|
|
147
|
+
const capDir = path.join(projectRoot, CAP_DIR);
|
|
148
|
+
if (!fs.existsSync(capDir)) {
|
|
149
|
+
fs.mkdirSync(capDir, { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
const sessionPath = path.join(capDir, SESSION_FILE);
|
|
152
|
+
fs.writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n', 'utf8');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// @cap-api updateSession(projectRoot, updates) -- Partial update to session (merge, not overwrite).
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
158
|
+
* @param {Partial<CapSession>} updates - Fields to merge into current session
|
|
159
|
+
* @returns {CapSession} - The updated session
|
|
160
|
+
*/
|
|
161
|
+
function updateSession(projectRoot, updates) {
|
|
162
|
+
const session = loadSession(projectRoot);
|
|
163
|
+
// Shallow merge -- metadata gets replaced if present in updates
|
|
164
|
+
const updated = { ...session, ...updates };
|
|
165
|
+
saveSession(projectRoot, updated);
|
|
166
|
+
return updated;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// @cap-api startSession(projectRoot, featureId, step) -- Set active feature and step with timestamp.
|
|
170
|
+
// @cap-todo(ref:AC-17) SESSION.json connects to FEATURE-MAP.md only via feature IDs (loose coupling)
|
|
171
|
+
/**
|
|
172
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
173
|
+
* @param {string} featureId - Feature ID to focus on (e.g., "F-001")
|
|
174
|
+
* @param {string} step - Current workflow step name
|
|
175
|
+
* @returns {CapSession}
|
|
176
|
+
*/
|
|
177
|
+
function startSession(projectRoot, featureId, step) {
|
|
178
|
+
return updateSession(projectRoot, {
|
|
179
|
+
activeFeature: featureId,
|
|
180
|
+
step,
|
|
181
|
+
startedAt: new Date().toISOString(),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// @cap-api updateStep(projectRoot, step) -- Update current workflow step.
|
|
186
|
+
/**
|
|
187
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
188
|
+
* @param {string} step - New workflow step name
|
|
189
|
+
* @returns {CapSession}
|
|
190
|
+
*/
|
|
191
|
+
function updateStep(projectRoot, step) {
|
|
192
|
+
return updateSession(projectRoot, { step });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// @cap-api endSession(projectRoot) -- Clear active feature and step.
|
|
196
|
+
/**
|
|
197
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
198
|
+
* @returns {CapSession}
|
|
199
|
+
*/
|
|
200
|
+
function endSession(projectRoot) {
|
|
201
|
+
return updateSession(projectRoot, {
|
|
202
|
+
activeFeature: null,
|
|
203
|
+
step: null,
|
|
204
|
+
startedAt: null,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// @cap-api isInitialized(projectRoot) -- Check if .cap/ exists.
|
|
209
|
+
/**
|
|
210
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
211
|
+
* @returns {boolean}
|
|
212
|
+
*/
|
|
213
|
+
function isInitialized(projectRoot) {
|
|
214
|
+
return fs.existsSync(path.join(projectRoot, CAP_DIR));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// @cap-api initCapDirectory(projectRoot) -- Creates .cap/ directory structure and .gitignore. Idempotent.
|
|
218
|
+
// @cap-todo(ref:AC-4) No prompts, questions, wizards, or configuration forms
|
|
219
|
+
// @cap-todo(ref:AC-5) Completes in a single invocation with no follow-up steps
|
|
220
|
+
// @cap-todo(ref:AC-6) Idempotent -- running on already-initialized project shall not overwrite existing content
|
|
221
|
+
/**
|
|
222
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
223
|
+
*/
|
|
224
|
+
function initCapDirectory(projectRoot) {
|
|
225
|
+
const capDir = path.join(projectRoot, CAP_DIR);
|
|
226
|
+
const stackDocsDir = path.join(capDir, 'stack-docs');
|
|
227
|
+
const debugDir = path.join(capDir, 'debug');
|
|
228
|
+
const gitignorePath = path.join(capDir, '.gitignore');
|
|
229
|
+
const sessionPath = path.join(capDir, SESSION_FILE);
|
|
230
|
+
|
|
231
|
+
// Create directories (idempotent via recursive:true)
|
|
232
|
+
fs.mkdirSync(capDir, { recursive: true });
|
|
233
|
+
fs.mkdirSync(stackDocsDir, { recursive: true });
|
|
234
|
+
fs.mkdirSync(debugDir, { recursive: true });
|
|
235
|
+
|
|
236
|
+
// Write .gitignore (always overwrite -- it's infrastructure, not user content)
|
|
237
|
+
fs.writeFileSync(gitignorePath, GITIGNORE_CONTENT, 'utf8');
|
|
238
|
+
|
|
239
|
+
// Write SESSION.json only if it doesn't exist (preserve existing session)
|
|
240
|
+
if (!fs.existsSync(sessionPath)) {
|
|
241
|
+
saveSession(projectRoot, getDefaultSession());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Create .claude/rules/cap-memory.md -- bridges CAP project memory with Claude auto-memory
|
|
245
|
+
const rulesDir = path.join(projectRoot, '.claude', 'rules');
|
|
246
|
+
const memoryRulePath = path.join(rulesDir, 'cap-memory.md');
|
|
247
|
+
if (!fs.existsSync(memoryRulePath)) {
|
|
248
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
249
|
+
fs.writeFileSync(memoryRulePath, CAP_MEMORY_RULE, 'utf8');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// @cap-api setActiveApp(projectRoot, appPath) -- Set the active app in SESSION.json for monorepo scoping.
|
|
254
|
+
/**
|
|
255
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
256
|
+
* @param {string|null} appPath - Relative app path (e.g., "apps/flow") or null to clear
|
|
257
|
+
* @returns {CapSession}
|
|
258
|
+
*/
|
|
259
|
+
function setActiveApp(projectRoot, appPath) {
|
|
260
|
+
return updateSession(projectRoot, { activeApp: appPath || null });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// @cap-api getActiveApp(projectRoot) -- Get current active app path from SESSION.json.
|
|
264
|
+
/**
|
|
265
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
266
|
+
* @returns {string|null} - Active app path or null
|
|
267
|
+
*/
|
|
268
|
+
function getActiveApp(projectRoot) {
|
|
269
|
+
const session = loadSession(projectRoot);
|
|
270
|
+
return session.activeApp || null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// @cap-api getAppRoot(projectRoot) -- Returns the effective root for app-scoped operations.
|
|
274
|
+
// If activeApp is set, returns projectRoot + activeApp. Otherwise returns projectRoot.
|
|
275
|
+
// This is the KEY function for all scoping decisions.
|
|
276
|
+
/**
|
|
277
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
278
|
+
* @returns {string} - Absolute path to the active app root (or project root if no app)
|
|
279
|
+
*/
|
|
280
|
+
function getAppRoot(projectRoot) {
|
|
281
|
+
const activeApp = getActiveApp(projectRoot);
|
|
282
|
+
if (activeApp) {
|
|
283
|
+
return path.join(projectRoot, activeApp);
|
|
284
|
+
}
|
|
285
|
+
return projectRoot;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// @cap-api listApps(projectRoot) -- List available apps/packages in a monorepo using detectWorkspaces.
|
|
289
|
+
/**
|
|
290
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
291
|
+
* @returns {{ isMonorepo: boolean, apps: string[] }}
|
|
292
|
+
*/
|
|
293
|
+
function listApps(projectRoot) {
|
|
294
|
+
// Lazy require to avoid circular dependency
|
|
295
|
+
const { detectWorkspaces } = require('./cap-tag-scanner.cjs');
|
|
296
|
+
const workspaces = detectWorkspaces(projectRoot);
|
|
297
|
+
return {
|
|
298
|
+
isMonorepo: workspaces.isMonorepo,
|
|
299
|
+
apps: workspaces.packages,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// @cap-feature(feature:F-092, primary:true) Two-Phase Workflow — /cap:quick + /cap:finalize
|
|
304
|
+
// @cap-decision(F-092) Quick-mode state lives in SESSION.json, not in a separate file. Mirrors
|
|
305
|
+
// how activeFeature, activeApp, etc. live in the existing schema. Single source of truth for
|
|
306
|
+
// ephemeral workflow state.
|
|
307
|
+
// @cap-decision(F-092/git-snapshot) startCommit captures git HEAD at /cap:quick time. /cap:finalize
|
|
308
|
+
// diffs against this commit to compute changed-files. This decouples quick-mode lifetime from
|
|
309
|
+
// commit cadence — Bastian can iterate freely, commit or not, and finalize sees the full set.
|
|
310
|
+
|
|
311
|
+
const { execSync } = require('node:child_process');
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Resolve current git HEAD SHA for the given working directory.
|
|
315
|
+
* Returns null if not a git repo or git is unavailable — caller treats as "no snapshot, fall back to unstaged-only diff".
|
|
316
|
+
* @param {string} cwd
|
|
317
|
+
* @returns {string|null}
|
|
318
|
+
*/
|
|
319
|
+
function _getGitHeadSha(cwd) {
|
|
320
|
+
try {
|
|
321
|
+
const out = execSync('git rev-parse HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
322
|
+
return String(out).trim() || null;
|
|
323
|
+
} catch (_e) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// @cap-api startQuickMode(projectRoot, featureId) -- Enter Phase 1 (Visual Iteration) for a feature.
|
|
329
|
+
// @cap-todo(ac:F-092/AC-1) SESSION.json carries quickMode state with startCommit snapshot.
|
|
330
|
+
/**
|
|
331
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
332
|
+
* @param {string} featureId - Feature ID being worked on (e.g., "F-Hub-Spotlight-Carousel")
|
|
333
|
+
* @returns {CapSession}
|
|
334
|
+
*/
|
|
335
|
+
function startQuickMode(projectRoot, featureId) {
|
|
336
|
+
const startCommit = _getGitHeadSha(projectRoot);
|
|
337
|
+
return updateSession(projectRoot, {
|
|
338
|
+
activeFeature: featureId,
|
|
339
|
+
quickMode: {
|
|
340
|
+
active: true,
|
|
341
|
+
feature: featureId,
|
|
342
|
+
startedAt: new Date().toISOString(),
|
|
343
|
+
startCommit,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// @cap-api endQuickMode(projectRoot) -- Exit Phase 1 — typically called from /cap:finalize after consolidation.
|
|
349
|
+
// @cap-todo(ac:F-092/AC-10) Reset quickMode flag after successful finalize.
|
|
350
|
+
/**
|
|
351
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
352
|
+
* @returns {CapSession}
|
|
353
|
+
*/
|
|
354
|
+
function endQuickMode(projectRoot) {
|
|
355
|
+
return updateSession(projectRoot, {
|
|
356
|
+
quickMode: {
|
|
357
|
+
active: false,
|
|
358
|
+
feature: null,
|
|
359
|
+
startedAt: null,
|
|
360
|
+
startCommit: null,
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// @cap-api isQuickModeActive(projectRoot) -- Check whether the current session is in Phase 1.
|
|
366
|
+
/**
|
|
367
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
368
|
+
* @returns {boolean}
|
|
369
|
+
*/
|
|
370
|
+
function isQuickModeActive(projectRoot) {
|
|
371
|
+
const session = loadSession(projectRoot);
|
|
372
|
+
return Boolean(session.quickMode && session.quickMode.active === true);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// @cap-api getChangedFilesSinceQuickStart(projectRoot) -- Compute files changed since /cap:quick was entered.
|
|
376
|
+
// @cap-todo(ac:F-092/AC-4) Diff = committed since startCommit + currently unstaged.
|
|
377
|
+
// @cap-decision(F-092) Returns DEDUPLICATED set — a file edited then committed then re-edited shows up once.
|
|
378
|
+
// Filters to source files (excludes .cap/, node_modules/, dist/, build/, .git/) so finalize doesn't
|
|
379
|
+
// try to annotate generated artifacts.
|
|
380
|
+
/**
|
|
381
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
382
|
+
* @returns {{ files: string[], startCommit: string|null, error?: string }}
|
|
383
|
+
*/
|
|
384
|
+
function getChangedFilesSinceQuickStart(projectRoot) {
|
|
385
|
+
const session = loadSession(projectRoot);
|
|
386
|
+
const startCommit = session.quickMode && session.quickMode.startCommit;
|
|
387
|
+
const set = new Set();
|
|
388
|
+
const exclude = (p) =>
|
|
389
|
+
/^\.cap\//.test(p) ||
|
|
390
|
+
/^node_modules\//.test(p) ||
|
|
391
|
+
/^dist\//.test(p) ||
|
|
392
|
+
/^build\//.test(p) ||
|
|
393
|
+
/^\.git\//.test(p);
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
if (startCommit) {
|
|
397
|
+
// Files committed since quick-start (includes renames as both old + new path)
|
|
398
|
+
const committed = execSync(
|
|
399
|
+
`git diff --name-only ${startCommit} HEAD`,
|
|
400
|
+
{ cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
401
|
+
);
|
|
402
|
+
String(committed).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
|
|
403
|
+
}
|
|
404
|
+
// Currently unstaged changes
|
|
405
|
+
const unstaged = execSync(
|
|
406
|
+
'git diff --name-only HEAD',
|
|
407
|
+
{ cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
408
|
+
);
|
|
409
|
+
String(unstaged).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
|
|
410
|
+
// Untracked files (newly created in quick-mode)
|
|
411
|
+
const untracked = execSync(
|
|
412
|
+
'git ls-files --others --exclude-standard',
|
|
413
|
+
{ cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
414
|
+
);
|
|
415
|
+
String(untracked).trim().split('\n').filter(Boolean).forEach(f => { if (!exclude(f)) set.add(f); });
|
|
416
|
+
} catch (e) {
|
|
417
|
+
return { files: [], startCommit: startCommit || null, error: String(e && e.message ? e.message : e).trim() };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { files: Array.from(set).sort(), startCommit: startCommit || null };
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = {
|
|
424
|
+
CAP_DIR,
|
|
425
|
+
SESSION_FILE,
|
|
426
|
+
GITIGNORE_CONTENT,
|
|
427
|
+
loadSession,
|
|
428
|
+
saveSession,
|
|
429
|
+
updateSession,
|
|
430
|
+
getDefaultSession,
|
|
431
|
+
startSession,
|
|
432
|
+
updateStep,
|
|
433
|
+
endSession,
|
|
434
|
+
isInitialized,
|
|
435
|
+
initCapDirectory,
|
|
436
|
+
setActiveApp,
|
|
437
|
+
getActiveApp,
|
|
438
|
+
getAppRoot,
|
|
439
|
+
listApps,
|
|
440
|
+
// F-092: two-phase workflow
|
|
441
|
+
startQuickMode,
|
|
442
|
+
endQuickMode,
|
|
443
|
+
isQuickModeActive,
|
|
444
|
+
getChangedFilesSinceQuickStart,
|
|
445
|
+
};
|