pan-wizard 2.8.1
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/LICENSE +21 -0
- package/README.md +772 -0
- package/agents/pan-debugger.md +1246 -0
- package/agents/pan-document_code.md +965 -0
- package/agents/pan-executor.md +469 -0
- package/agents/pan-integration-checker.md +443 -0
- package/agents/pan-phase-researcher.md +572 -0
- package/agents/pan-plan-checker.md +763 -0
- package/agents/pan-planner.md +1297 -0
- package/agents/pan-project-researcher.md +647 -0
- package/agents/pan-research-synthesizer.md +239 -0
- package/agents/pan-reviewer.md +112 -0
- package/agents/pan-roadmapper.md +642 -0
- package/agents/pan-verifier.md +672 -0
- package/assets/pan-logo-2000-transparent.svg +30 -0
- package/assets/pan-logo-2000.svg +43 -0
- package/assets/terminal.svg +119 -0
- package/bin/install-lib.cjs +616 -0
- package/bin/install.js +1936 -0
- package/commands/pan/add-phase.md +44 -0
- package/commands/pan/assumptions.md +47 -0
- package/commands/pan/audit-deployment.md +378 -0
- package/commands/pan/debug.md +168 -0
- package/commands/pan/discord.md +19 -0
- package/commands/pan/discuss-phase.md +84 -0
- package/commands/pan/exec-phase.md +45 -0
- package/commands/pan/focus-auto.md +323 -0
- package/commands/pan/focus-design.md +816 -0
- package/commands/pan/focus-exec.md +316 -0
- package/commands/pan/focus-plan.md +101 -0
- package/commands/pan/focus-scan.md +272 -0
- package/commands/pan/focus-sync.md +104 -0
- package/commands/pan/health.md +23 -0
- package/commands/pan/help.md +23 -0
- package/commands/pan/insert-phase.md +33 -0
- package/commands/pan/map-codebase.md +72 -0
- package/commands/pan/milestone-audit.md +37 -0
- package/commands/pan/milestone-cleanup.md +19 -0
- package/commands/pan/milestone-done.md +137 -0
- package/commands/pan/milestone-gaps.md +35 -0
- package/commands/pan/milestone-new.md +45 -0
- package/commands/pan/new-project.md +43 -0
- package/commands/pan/patches.md +110 -0
- package/commands/pan/pause.md +39 -0
- package/commands/pan/phase-budget.md +23 -0
- package/commands/pan/phase-tests.md +42 -0
- package/commands/pan/plan-phase.md +46 -0
- package/commands/pan/profile.md +36 -0
- package/commands/pan/progress.md +25 -0
- package/commands/pan/quick.md +42 -0
- package/commands/pan/remove-phase.md +32 -0
- package/commands/pan/research-phase.md +190 -0
- package/commands/pan/resume.md +41 -0
- package/commands/pan/retro.md +33 -0
- package/commands/pan/settings.md +37 -0
- package/commands/pan/todo-add.md +48 -0
- package/commands/pan/todo-check.md +46 -0
- package/commands/pan/update.md +38 -0
- package/commands/pan/verify-phase.md +39 -0
- package/hooks/dist/pan-check-update.js +62 -0
- package/hooks/dist/pan-context-monitor.js +122 -0
- package/hooks/dist/pan-statusline.js +108 -0
- package/package.json +66 -0
- package/pan-wizard-core/bin/lib/codebase.cjs +746 -0
- package/pan-wizard-core/bin/lib/commands.cjs +1435 -0
- package/pan-wizard-core/bin/lib/config.cjs +611 -0
- package/pan-wizard-core/bin/lib/constants.cjs +696 -0
- package/pan-wizard-core/bin/lib/context-budget.cjs +150 -0
- package/pan-wizard-core/bin/lib/core.cjs +650 -0
- package/pan-wizard-core/bin/lib/focus.cjs +900 -0
- package/pan-wizard-core/bin/lib/frontmatter.cjs +442 -0
- package/pan-wizard-core/bin/lib/init.cjs +881 -0
- package/pan-wizard-core/bin/lib/milestone.cjs +276 -0
- package/pan-wizard-core/bin/lib/phase.cjs +1212 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +470 -0
- package/pan-wizard-core/bin/lib/state.cjs +1029 -0
- package/pan-wizard-core/bin/lib/template.cjs +314 -0
- package/pan-wizard-core/bin/lib/utils.cjs +171 -0
- package/pan-wizard-core/bin/lib/verify.cjs +1808 -0
- package/pan-wizard-core/bin/pan-tools.cjs +773 -0
- package/pan-wizard-core/references/checkpoints.md +776 -0
- package/pan-wizard-core/references/continuation-format.md +249 -0
- package/pan-wizard-core/references/decimal-phase-calculation.md +65 -0
- package/pan-wizard-core/references/git-integration.md +248 -0
- package/pan-wizard-core/references/git-planning-commit.md +38 -0
- package/pan-wizard-core/references/model-profile-resolution.md +34 -0
- package/pan-wizard-core/references/model-profiles.md +111 -0
- package/pan-wizard-core/references/phase-argument-parsing.md +61 -0
- package/pan-wizard-core/references/planning-config.md +196 -0
- package/pan-wizard-core/references/questioning.md +145 -0
- package/pan-wizard-core/references/tdd.md +263 -0
- package/pan-wizard-core/references/ui-brand.md +160 -0
- package/pan-wizard-core/references/verification-patterns.md +612 -0
- package/pan-wizard-core/templates/codebase/architecture.md +283 -0
- package/pan-wizard-core/templates/codebase/best-practices.md +133 -0
- package/pan-wizard-core/templates/codebase/concerns.md +325 -0
- package/pan-wizard-core/templates/codebase/conventions.md +307 -0
- package/pan-wizard-core/templates/codebase/integrations.md +305 -0
- package/pan-wizard-core/templates/codebase/relationships.md +124 -0
- package/pan-wizard-core/templates/codebase/stack.md +199 -0
- package/pan-wizard-core/templates/codebase/structure.md +298 -0
- package/pan-wizard-core/templates/codebase/testing.md +480 -0
- package/pan-wizard-core/templates/config.json +37 -0
- package/pan-wizard-core/templates/context.md +283 -0
- package/pan-wizard-core/templates/continue-here.md +78 -0
- package/pan-wizard-core/templates/debug-subagent-prompt.md +91 -0
- package/pan-wizard-core/templates/debug.md +164 -0
- package/pan-wizard-core/templates/discovery.md +146 -0
- package/pan-wizard-core/templates/milestone-archive.md +123 -0
- package/pan-wizard-core/templates/milestone.md +115 -0
- package/pan-wizard-core/templates/phase-prompt.md +593 -0
- package/pan-wizard-core/templates/planner-subagent-prompt.md +117 -0
- package/pan-wizard-core/templates/project.md +184 -0
- package/pan-wizard-core/templates/requirements.md +231 -0
- package/pan-wizard-core/templates/research-project/architecture.md +204 -0
- package/pan-wizard-core/templates/research-project/features.md +147 -0
- package/pan-wizard-core/templates/research-project/pitfalls.md +200 -0
- package/pan-wizard-core/templates/research-project/stack.md +120 -0
- package/pan-wizard-core/templates/research-project/summary.md +170 -0
- package/pan-wizard-core/templates/research.md +552 -0
- package/pan-wizard-core/templates/retrospective.md +54 -0
- package/pan-wizard-core/templates/roadmap.md +202 -0
- package/pan-wizard-core/templates/standards.md +24 -0
- package/pan-wizard-core/templates/state.md +176 -0
- package/pan-wizard-core/templates/summary-complex.md +59 -0
- package/pan-wizard-core/templates/summary-minimal.md +41 -0
- package/pan-wizard-core/templates/summary-standard.md +49 -0
- package/pan-wizard-core/templates/summary.md +249 -0
- package/pan-wizard-core/templates/uat.md +247 -0
- package/pan-wizard-core/templates/user-setup.md +311 -0
- package/pan-wizard-core/templates/validation.md +76 -0
- package/pan-wizard-core/templates/verification-report.md +322 -0
- package/pan-wizard-core/workflows/add-phase.md +111 -0
- package/pan-wizard-core/workflows/assumptions.md +178 -0
- package/pan-wizard-core/workflows/diagnose-issues.md +219 -0
- package/pan-wizard-core/workflows/discuss-phase.md +542 -0
- package/pan-wizard-core/workflows/exec-phase.md +572 -0
- package/pan-wizard-core/workflows/execute-plan.md +448 -0
- package/pan-wizard-core/workflows/health.md +156 -0
- package/pan-wizard-core/workflows/help.md +431 -0
- package/pan-wizard-core/workflows/insert-phase.md +129 -0
- package/pan-wizard-core/workflows/map-codebase.md +401 -0
- package/pan-wizard-core/workflows/milestone-audit.md +297 -0
- package/pan-wizard-core/workflows/milestone-cleanup.md +152 -0
- package/pan-wizard-core/workflows/milestone-gaps.md +274 -0
- package/pan-wizard-core/workflows/milestone-new.md +382 -0
- package/pan-wizard-core/workflows/new-project.md +1178 -0
- package/pan-wizard-core/workflows/pause.md +122 -0
- package/pan-wizard-core/workflows/phase-tests.md +388 -0
- package/pan-wizard-core/workflows/plan-phase.md +569 -0
- package/pan-wizard-core/workflows/profile.md +115 -0
- package/pan-wizard-core/workflows/progress.md +381 -0
- package/pan-wizard-core/workflows/quick.md +453 -0
- package/pan-wizard-core/workflows/remove-phase.md +154 -0
- package/pan-wizard-core/workflows/research-phase.md +73 -0
- package/pan-wizard-core/workflows/resume-project.md +306 -0
- package/pan-wizard-core/workflows/retro.md +121 -0
- package/pan-wizard-core/workflows/settings.md +213 -0
- package/pan-wizard-core/workflows/todo-add.md +157 -0
- package/pan-wizard-core/workflows/todo-check.md +176 -0
- package/pan-wizard-core/workflows/transition.md +544 -0
- package/pan-wizard-core/workflows/update.md +219 -0
- package/pan-wizard-core/workflows/verify-phase.md +301 -0
- package/scripts/build-hooks.js +43 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config — Planning config CRUD operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { output, error, safeReadFile, toPosix, findPhaseInternal } = require('./core.cjs');
|
|
9
|
+
const {
|
|
10
|
+
PLANNING_DIR, CONFIG_FILE, PROJECT_FILE, STANDARDS_FILE,
|
|
11
|
+
STANDARDS_CATALOG, STANDARDS_CATEGORIES, STANDARDS_RECOMMENDATIONS,
|
|
12
|
+
PHASE_KEYWORDS_TO_STANDARDS, STANDARDS_EXTERNAL_TOOLS,
|
|
13
|
+
} = require('./constants.cjs');
|
|
14
|
+
const { readJsonFile, planningPath, fileAccessible, hasBraveSearchKey } = require('./utils.cjs');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Count checked checklist items in a standards section.
|
|
18
|
+
* @param {string} content - Full standards.md content
|
|
19
|
+
* @param {string} sectionName - Section header name (e.g., "Code Review")
|
|
20
|
+
* @returns {number} Number of checked items (- [x])
|
|
21
|
+
*/
|
|
22
|
+
function countCheckedInSection(content, sectionName) {
|
|
23
|
+
const sectionStart = content.indexOf('## ' + sectionName);
|
|
24
|
+
if (sectionStart === -1) return 0;
|
|
25
|
+
const nextSection = content.indexOf('\n## ', sectionStart + 1);
|
|
26
|
+
const section = nextSection > -1 ? content.slice(sectionStart, nextSection) : content.slice(sectionStart);
|
|
27
|
+
return (section.match(/- \[x\]/gi) || []).length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Ensure .planning/config.json exists, creating it with defaults if missing.
|
|
32
|
+
* @param {string} cwd - Working directory path
|
|
33
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Build default config by merging hardcoded defaults with user-level overrides.
|
|
38
|
+
* @param {boolean} hasBraveSearch - Whether Brave Search API key is available
|
|
39
|
+
* @param {Object} userDefaults - User-level defaults from ~/.pan-wizard/defaults.json
|
|
40
|
+
* @returns {Object} Merged config defaults
|
|
41
|
+
*/
|
|
42
|
+
function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
43
|
+
const hardcoded = {
|
|
44
|
+
model_profile: 'balanced',
|
|
45
|
+
commit_docs: true,
|
|
46
|
+
search_gitignored: false,
|
|
47
|
+
branching_strategy: 'none',
|
|
48
|
+
phase_branch_template: 'pan/phase-{phase}-{slug}',
|
|
49
|
+
milestone_branch_template: 'pan/{milestone}-{slug}',
|
|
50
|
+
workflow: {
|
|
51
|
+
research: true,
|
|
52
|
+
plan_check: true,
|
|
53
|
+
verifier: true,
|
|
54
|
+
nyquist_validation: false,
|
|
55
|
+
},
|
|
56
|
+
parallelization: true,
|
|
57
|
+
brave_search: hasBraveSearch,
|
|
58
|
+
budget: {
|
|
59
|
+
default_points: 50,
|
|
60
|
+
micro_threshold_tasks: 3,
|
|
61
|
+
micro_threshold_files: 2,
|
|
62
|
+
},
|
|
63
|
+
commit: {
|
|
64
|
+
safety_checks: true,
|
|
65
|
+
conventional_types: true,
|
|
66
|
+
sensitive_patterns: ['\\.env$', '\\.pem$', '\\.key$', 'credentials', 'secret', 'password', 'token'],
|
|
67
|
+
},
|
|
68
|
+
execution: {
|
|
69
|
+
default_mode: 'wave_order',
|
|
70
|
+
rollback_snapshots: true,
|
|
71
|
+
error_pattern_learning: true,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
...hardcoded,
|
|
76
|
+
...userDefaults,
|
|
77
|
+
workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
|
|
78
|
+
budget: { ...hardcoded.budget, ...(userDefaults.budget || {}) },
|
|
79
|
+
commit: { ...hardcoded.commit, ...(userDefaults.commit || {}) },
|
|
80
|
+
execution: { ...hardcoded.execution, ...(userDefaults.execution || {}) },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cmdConfigEnsureSection(cwd, raw) {
|
|
85
|
+
const configPath = path.join(planningPath(cwd), CONFIG_FILE);
|
|
86
|
+
|
|
87
|
+
try { fs.mkdirSync(planningPath(cwd), { recursive: true }); }
|
|
88
|
+
catch (err) { error('Failed to create .planning directory: ' + err.message); }
|
|
89
|
+
|
|
90
|
+
if (fileAccessible(configPath)) {
|
|
91
|
+
output({ created: false, reason: 'already_exists' }, raw, 'exists');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hasBraveSearch = hasBraveSearchKey();
|
|
96
|
+
|
|
97
|
+
const userDefaults = readJsonFile(path.join(os.homedir(), '.pan-wizard', 'defaults.json')) || {};
|
|
98
|
+
const defaults = buildConfigDefaults(hasBraveSearch, userDefaults);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
|
|
102
|
+
output({ created: true, path: PLANNING_DIR + '/' + CONFIG_FILE }, raw, 'created');
|
|
103
|
+
} catch (err) {
|
|
104
|
+
error('Failed to create config.json: ' + err.message);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Set a configuration value in config.json using dot-notation key path.
|
|
110
|
+
* @param {string} cwd - Working directory path
|
|
111
|
+
* @param {string} keyPath - Dot-notation key path (e.g., "workflow.research")
|
|
112
|
+
* @param {string} value - Value to set (auto-parsed for booleans and numbers)
|
|
113
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
116
|
+
function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
117
|
+
const configPath = path.join(planningPath(cwd), CONFIG_FILE);
|
|
118
|
+
|
|
119
|
+
if (!keyPath) {
|
|
120
|
+
error('Usage: config-set <key.path> <value>');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Parse value (handle booleans and numbers)
|
|
124
|
+
let parsedValue = value;
|
|
125
|
+
if (value === 'true') parsedValue = true;
|
|
126
|
+
else if (value === 'false') parsedValue = false;
|
|
127
|
+
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
|
|
128
|
+
|
|
129
|
+
// Load existing config or start with empty object
|
|
130
|
+
let config = {};
|
|
131
|
+
try {
|
|
132
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// ENOENT means config doesn't exist yet — start fresh with empty object
|
|
135
|
+
if (err.code !== 'ENOENT') {
|
|
136
|
+
error('Failed to read config.json: ' + err.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Traverse the dot-notation key path to build nested objects.
|
|
141
|
+
// For a path like "workflow.research", this loop walks through each
|
|
142
|
+
// segment except the last, creating intermediate objects as needed.
|
|
143
|
+
// After the loop, `current` points to the parent object and the
|
|
144
|
+
// final segment is used as the property key for assignment.
|
|
145
|
+
const keys = keyPath.split('.');
|
|
146
|
+
let current = config;
|
|
147
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
148
|
+
const key = keys[i];
|
|
149
|
+
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
150
|
+
current[key] = {};
|
|
151
|
+
}
|
|
152
|
+
current = current[key];
|
|
153
|
+
}
|
|
154
|
+
current[keys[keys.length - 1]] = parsedValue;
|
|
155
|
+
|
|
156
|
+
// Write back
|
|
157
|
+
try {
|
|
158
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
159
|
+
const result = { updated: true, key: keyPath, value: parsedValue };
|
|
160
|
+
output(result, raw, `${keyPath}=${parsedValue}`);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// Config file could not be written — disk full, permissions, etc.
|
|
163
|
+
error('Failed to write config.json: ' + err.message);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get a configuration value from config.json using dot-notation key path.
|
|
169
|
+
* @param {string} cwd - Working directory path
|
|
170
|
+
* @param {string} keyPath - Dot-notation key path (e.g., "workflow.auto_advance")
|
|
171
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
172
|
+
* @returns {void}
|
|
173
|
+
*/
|
|
174
|
+
function cmdConfigGet(cwd, keyPath, raw) {
|
|
175
|
+
const configPath = path.join(planningPath(cwd), CONFIG_FILE);
|
|
176
|
+
|
|
177
|
+
if (!keyPath) {
|
|
178
|
+
error('Usage: config-get <key.path>');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let config = {};
|
|
182
|
+
try {
|
|
183
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
184
|
+
} catch (err) {
|
|
185
|
+
if (err.code === 'ENOENT') {
|
|
186
|
+
error('No config.json found at ' + configPath);
|
|
187
|
+
}
|
|
188
|
+
error('Failed to read config.json: ' + err.message);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Traverse dot-notation path (e.g., "workflow.auto_advance")
|
|
192
|
+
const keys = keyPath.split('.');
|
|
193
|
+
let current = config;
|
|
194
|
+
for (const key of keys) {
|
|
195
|
+
if (current === undefined || current === null || typeof current !== 'object') {
|
|
196
|
+
error(`Key not found: ${keyPath}`);
|
|
197
|
+
}
|
|
198
|
+
current = current[key];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (current === undefined) {
|
|
202
|
+
error(`Key not found: ${keyPath}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
output(current, raw, String(current));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── Standards commands ─────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* List available standards from the built-in catalog.
|
|
212
|
+
* @param {string} _cwd - Working directory (unused — catalog is in-memory)
|
|
213
|
+
* @param {string} category - Optional category filter
|
|
214
|
+
* @param {boolean} raw - If true, output raw value
|
|
215
|
+
*/
|
|
216
|
+
function cmdStandardsList(_cwd, category, raw) {
|
|
217
|
+
let entries = Object.entries(STANDARDS_CATALOG);
|
|
218
|
+
if (category) {
|
|
219
|
+
if (!STANDARDS_CATEGORIES.includes(category)) {
|
|
220
|
+
error('Unknown category: ' + category + '. Valid: ' + STANDARDS_CATEGORIES.join(', '));
|
|
221
|
+
}
|
|
222
|
+
entries = entries.filter(([, s]) => s.category === category);
|
|
223
|
+
}
|
|
224
|
+
const standards = entries.map(([id, s]) => ({
|
|
225
|
+
id,
|
|
226
|
+
name: s.name,
|
|
227
|
+
category: s.category,
|
|
228
|
+
description: s.description,
|
|
229
|
+
level: s.level,
|
|
230
|
+
checklist_items: s.checklist.length,
|
|
231
|
+
}));
|
|
232
|
+
output({ standards, count: standards.length }, raw, standards.map(s => `${s.id} — ${s.name}`).join('\n'));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Parse standards.md to extract selected standard IDs.
|
|
237
|
+
* @param {string} content - standards.md file content
|
|
238
|
+
* @returns {string[]} Array of standard IDs found
|
|
239
|
+
*/
|
|
240
|
+
function parseStandardsFile(content) {
|
|
241
|
+
const ids = [];
|
|
242
|
+
for (const [id, s] of Object.entries(STANDARDS_CATALOG)) {
|
|
243
|
+
if (content.includes('## ' + s.name)) ids.push(id);
|
|
244
|
+
}
|
|
245
|
+
return ids;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Render standards.md content from selected standard IDs.
|
|
250
|
+
* @param {string[]} ids - Array of standard IDs
|
|
251
|
+
* @returns {string} Rendered markdown content
|
|
252
|
+
*/
|
|
253
|
+
function renderStandardsMd(ids) {
|
|
254
|
+
const sections = ids.map(id => {
|
|
255
|
+
const s = STANDARDS_CATALOG[id];
|
|
256
|
+
if (!s) return '';
|
|
257
|
+
const items = s.checklist.map(c => '- [ ] ' + c).join('\n');
|
|
258
|
+
return `## ${s.name}\n\n**Category:** ${s.category} | **Level:** ${s.level}\n**Reference:** ${s.url}\n\n${s.description}\n\n### Checklist\n${items}`;
|
|
259
|
+
}).filter(Boolean);
|
|
260
|
+
|
|
261
|
+
return '# Project Standards\n\nStandards selected for this project. Agents reference this file during planning, execution, and verification.\n\n' +
|
|
262
|
+
sections.join('\n\n') +
|
|
263
|
+
'\n\n---\n\n> Manage standards with `pan-tools standards select|remove|status`.\n> Standards guide AI decisions — they do not replace dedicated scanning tools.\n';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add a standard to the project.
|
|
268
|
+
* @param {string} cwd - Working directory
|
|
269
|
+
* @param {string} standardId - Standard ID from catalog
|
|
270
|
+
* @param {boolean} raw - If true, output raw value
|
|
271
|
+
*/
|
|
272
|
+
function cmdStandardsSelect(cwd, standardId, raw) {
|
|
273
|
+
if (!standardId) {
|
|
274
|
+
error('Usage: standards select <standard-id>. Run "standards list" to see available.');
|
|
275
|
+
}
|
|
276
|
+
if (!STANDARDS_CATALOG[standardId]) {
|
|
277
|
+
error('Unknown standard: ' + standardId + '. Run "standards list" to see available.');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const stdPath = path.join(planningPath(cwd), STANDARDS_FILE);
|
|
281
|
+
const existing = safeReadFile(stdPath) || '';
|
|
282
|
+
const currentIds = parseStandardsFile(existing);
|
|
283
|
+
|
|
284
|
+
if (currentIds.includes(standardId)) {
|
|
285
|
+
error(standardId + ' already in project standards');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
currentIds.push(standardId);
|
|
289
|
+
const content = renderStandardsMd(currentIds);
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
fs.writeFileSync(stdPath, content, 'utf-8');
|
|
293
|
+
} catch (err) {
|
|
294
|
+
error('Failed to write standards.md: ' + err.message);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
output({
|
|
298
|
+
added: standardId,
|
|
299
|
+
project_standards: currentIds,
|
|
300
|
+
standards_file: toPosix(PLANNING_DIR + '/' + STANDARDS_FILE),
|
|
301
|
+
}, raw, 'Added ' + STANDARDS_CATALOG[standardId].name);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Remove a standard from the project.
|
|
306
|
+
* @param {string} cwd - Working directory
|
|
307
|
+
* @param {string} standardId - Standard ID to remove
|
|
308
|
+
* @param {boolean} raw - If true, output raw value
|
|
309
|
+
*/
|
|
310
|
+
function cmdStandardsRemove(cwd, standardId, raw) {
|
|
311
|
+
if (!standardId) {
|
|
312
|
+
error('Usage: standards remove <standard-id>');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const stdPath = path.join(planningPath(cwd), STANDARDS_FILE);
|
|
316
|
+
const existing = safeReadFile(stdPath);
|
|
317
|
+
if (!existing) {
|
|
318
|
+
error('No standards.md found. Nothing to remove.');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const currentIds = parseStandardsFile(existing);
|
|
322
|
+
if (!currentIds.includes(standardId)) {
|
|
323
|
+
error(standardId + ' not in project standards. Current: ' + (currentIds.join(', ') || 'none'));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const newIds = currentIds.filter(id => id !== standardId);
|
|
327
|
+
|
|
328
|
+
if (newIds.length === 0) {
|
|
329
|
+
try { fs.unlinkSync(stdPath); } catch { /* ignore */ }
|
|
330
|
+
output({ removed: standardId, project_standards: [], standards_file: null }, raw, 'Removed ' + standardId + ' (no standards remaining, file deleted)');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const content = renderStandardsMd(newIds);
|
|
335
|
+
try {
|
|
336
|
+
fs.writeFileSync(stdPath, content, 'utf-8');
|
|
337
|
+
} catch (err) {
|
|
338
|
+
error('Failed to write standards.md: ' + err.message);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
output({
|
|
342
|
+
removed: standardId,
|
|
343
|
+
project_standards: newIds,
|
|
344
|
+
standards_file: toPosix(PLANNING_DIR + '/' + STANDARDS_FILE),
|
|
345
|
+
}, raw, 'Removed ' + standardId);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Report compliance status for selected standards.
|
|
350
|
+
* @param {string} cwd - Working directory
|
|
351
|
+
* @param {boolean} raw - If true, output raw value
|
|
352
|
+
*/
|
|
353
|
+
function cmdStandardsStatus(cwd, raw) {
|
|
354
|
+
const stdPath = path.join(planningPath(cwd), STANDARDS_FILE);
|
|
355
|
+
const existing = safeReadFile(stdPath);
|
|
356
|
+
|
|
357
|
+
if (!existing) {
|
|
358
|
+
output({
|
|
359
|
+
project_standards: [],
|
|
360
|
+
checks: [],
|
|
361
|
+
overall_status: 'none',
|
|
362
|
+
}, raw, 'No standards selected. Run "standards select <id>" to add one.');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const currentIds = parseStandardsFile(existing);
|
|
367
|
+
const checks = currentIds.map(id => {
|
|
368
|
+
const s = STANDARDS_CATALOG[id];
|
|
369
|
+
if (!s) return null;
|
|
370
|
+
const total = s.checklist.length;
|
|
371
|
+
const checked = countCheckedInSection(existing, s.name);
|
|
372
|
+
return {
|
|
373
|
+
standard_id: id,
|
|
374
|
+
standard_name: s.name,
|
|
375
|
+
category: s.category,
|
|
376
|
+
status: checked === total ? 'complete' : checked > 0 ? 'partial' : 'configured',
|
|
377
|
+
checklist_items: total,
|
|
378
|
+
verified_items: checked,
|
|
379
|
+
coverage: Math.round((checked / total) * 100) + '%',
|
|
380
|
+
};
|
|
381
|
+
}).filter(Boolean);
|
|
382
|
+
|
|
383
|
+
const allComplete = checks.every(c => c.status === 'complete');
|
|
384
|
+
const anyPartial = checks.some(c => c.status === 'partial');
|
|
385
|
+
|
|
386
|
+
output({
|
|
387
|
+
project_standards: currentIds,
|
|
388
|
+
checks,
|
|
389
|
+
overall_status: allComplete ? 'complete' : anyPartial ? 'partial' : 'configured',
|
|
390
|
+
}, raw, checks.map(c => `${c.standard_id}: ${c.coverage} (${c.verified_items}/${c.checklist_items})`).join('\n'));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Recommend standards based on project.md content analysis.
|
|
395
|
+
* @param {string} cwd - Working directory
|
|
396
|
+
* @param {boolean} raw - If true, output raw value
|
|
397
|
+
*/
|
|
398
|
+
function cmdStandardsRecommend(cwd, raw) {
|
|
399
|
+
const projectPath = path.join(planningPath(cwd), PROJECT_FILE);
|
|
400
|
+
const content = safeReadFile(projectPath);
|
|
401
|
+
if (!content) {
|
|
402
|
+
error('project.md not found. Run /pan:new-project first.');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const lower = content.toLowerCase();
|
|
406
|
+
const detectedTypes = [];
|
|
407
|
+
|
|
408
|
+
if (/\b(react|next\.?js|vue|angular|html|css|frontend|web\s*app|website|dashboard)\b/.test(lower)) detectedTypes.push('web');
|
|
409
|
+
if (/\b(api|rest|graphql|endpoint|backend|server|express|fastify)\b/.test(lower)) detectedTypes.push('api');
|
|
410
|
+
if (/\b(llm|gpt|claude|openai|anthropic|ai\s*model|machine\s*learning|neural|transformer)\b/.test(lower)) detectedTypes.push('ai');
|
|
411
|
+
if (/\b(agent|autonomous|multi-agent|agentic|tool\s*use|tool\s*calling)\b/.test(lower)) detectedTypes.push('agent');
|
|
412
|
+
if (/\b(enterprise|togaf|architecture\s*governance|compliance)\b/.test(lower)) detectedTypes.push('enterprise');
|
|
413
|
+
if (/\b(cli|command.line|terminal|shell|argv)\b/.test(lower)) detectedTypes.push('cli');
|
|
414
|
+
|
|
415
|
+
if (detectedTypes.length === 0) detectedTypes.push('general');
|
|
416
|
+
|
|
417
|
+
const seen = new Set();
|
|
418
|
+
const recommendations = [];
|
|
419
|
+
for (const type of detectedTypes) {
|
|
420
|
+
const recs = STANDARDS_RECOMMENDATIONS[type] || [];
|
|
421
|
+
for (const id of recs) {
|
|
422
|
+
if (seen.has(id)) continue;
|
|
423
|
+
seen.add(id);
|
|
424
|
+
const s = STANDARDS_CATALOG[id];
|
|
425
|
+
recommendations.push({ id, name: s.name, reason: type + ' project detected', priority: recommendations.length < 3 ? 'high' : 'medium' });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
output({
|
|
430
|
+
project_types: detectedTypes,
|
|
431
|
+
recommendations,
|
|
432
|
+
}, raw, recommendations.map(r => `[${r.priority}] ${r.id} — ${r.reason}`).join('\n'));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Detect which keywords from a content string match standard-relevant keywords.
|
|
437
|
+
* @param {string} content - Text content to scan
|
|
438
|
+
* @returns {string[]} Unique standard IDs that match
|
|
439
|
+
*/
|
|
440
|
+
function detectStandardsFromContent(content) {
|
|
441
|
+
const lower = content.toLowerCase();
|
|
442
|
+
const matched = new Set();
|
|
443
|
+
for (const [keyword, ids] of Object.entries(PHASE_KEYWORDS_TO_STANDARDS)) {
|
|
444
|
+
if (lower.includes(keyword)) {
|
|
445
|
+
for (const id of ids) matched.add(id);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return Array.from(matched);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Build compliance report for detected standards against selected standards.
|
|
453
|
+
* @param {string[]} detectedIds - Standard IDs detected from content
|
|
454
|
+
* @param {string[]} selectedIds - Standard IDs selected in standards.md
|
|
455
|
+
* @param {string} stdContent - Raw standards.md content
|
|
456
|
+
* @returns {Array} Compliance entries
|
|
457
|
+
*/
|
|
458
|
+
function buildComplianceReport(detectedIds, selectedIds, stdContent) {
|
|
459
|
+
return detectedIds.map(id => {
|
|
460
|
+
const s = STANDARDS_CATALOG[id];
|
|
461
|
+
if (!s) return null;
|
|
462
|
+
if (!selectedIds.includes(id)) {
|
|
463
|
+
return {
|
|
464
|
+
standard_id: id, standard_name: s.name, category: s.category,
|
|
465
|
+
selected: false, status: 'not_selected', coverage: null,
|
|
466
|
+
action: 'Consider selecting with: pan-tools standards select ' + id,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const total = s.checklist.length;
|
|
470
|
+
const checked = countCheckedInSection(stdContent, s.name);
|
|
471
|
+
return {
|
|
472
|
+
standard_id: id, standard_name: s.name, category: s.category,
|
|
473
|
+
selected: true,
|
|
474
|
+
status: checked === total ? 'complete' : checked > 0 ? 'partial' : 'configured',
|
|
475
|
+
checklist_items: total, verified_items: checked,
|
|
476
|
+
coverage: Math.round((checked / total) * 100) + '%',
|
|
477
|
+
};
|
|
478
|
+
}).filter(Boolean);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Track which standards are relevant to a specific phase and their compliance state.
|
|
483
|
+
* Analyzes phase plan.md files for keywords that map to standards.
|
|
484
|
+
* @param {string} cwd - Working directory
|
|
485
|
+
* @param {string} phaseNum - Phase number (e.g., "1", "2.1")
|
|
486
|
+
* @param {boolean} raw - If true, output raw value
|
|
487
|
+
*/
|
|
488
|
+
function cmdStandardsPhaseTrack(cwd, phaseNum, raw) {
|
|
489
|
+
if (!phaseNum) {
|
|
490
|
+
error('Usage: standards phase-track <phase-number>');
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Find the phase directory
|
|
494
|
+
const phase = findPhaseInternal(cwd, phaseNum);
|
|
495
|
+
if (!phase) {
|
|
496
|
+
error('Phase ' + phaseNum + ' not found');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Read all plan.md files in the phase directory
|
|
500
|
+
const phaseDir = path.join(cwd, phase.directory);
|
|
501
|
+
let files;
|
|
502
|
+
try {
|
|
503
|
+
files = fs.readdirSync(phaseDir);
|
|
504
|
+
} catch {
|
|
505
|
+
error('Cannot read phase directory: ' + phase.directory);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const planFiles = files.filter(f => f.endsWith('-plan.md') || f === 'plan.md');
|
|
509
|
+
let combinedContent = '';
|
|
510
|
+
for (const pf of planFiles) {
|
|
511
|
+
const content = safeReadFile(path.join(phaseDir, pf));
|
|
512
|
+
if (content) combinedContent += '\n' + content;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!combinedContent) {
|
|
516
|
+
output({
|
|
517
|
+
phase: phaseNum,
|
|
518
|
+
phase_name: phase.name,
|
|
519
|
+
relevant_standards: [],
|
|
520
|
+
compliance: [],
|
|
521
|
+
message: 'No plan files found in phase',
|
|
522
|
+
}, raw, 'Phase ' + phaseNum + ': no plan files found');
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Detect relevant standards from phase content
|
|
527
|
+
const detectedIds = detectStandardsFromContent(combinedContent);
|
|
528
|
+
|
|
529
|
+
// Read standards.md for compliance state
|
|
530
|
+
const stdPath = path.join(planningPath(cwd), STANDARDS_FILE);
|
|
531
|
+
const stdContent = safeReadFile(stdPath) || '';
|
|
532
|
+
const selectedIds = parseStandardsFile(stdContent);
|
|
533
|
+
|
|
534
|
+
const compliance = buildComplianceReport(detectedIds, selectedIds, stdContent);
|
|
535
|
+
|
|
536
|
+
output({
|
|
537
|
+
phase: phaseNum,
|
|
538
|
+
phase_name: phase.name,
|
|
539
|
+
relevant_standards: detectedIds,
|
|
540
|
+
compliance,
|
|
541
|
+
}, raw, compliance.map(c => `${c.standard_id}: ${c.selected ? c.coverage || 'N/A' : 'not selected'}`).join('\n'));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* List external tools recommended for selected or specified standards.
|
|
546
|
+
* @param {string} cwd - Working directory
|
|
547
|
+
* @param {string} standardId - Optional specific standard ID
|
|
548
|
+
* @param {boolean} raw - If true, output raw value
|
|
549
|
+
*/
|
|
550
|
+
function cmdStandardsTools(cwd, standardId, raw) {
|
|
551
|
+
let targetIds;
|
|
552
|
+
|
|
553
|
+
if (standardId) {
|
|
554
|
+
if (!STANDARDS_CATALOG[standardId]) {
|
|
555
|
+
error('Unknown standard: ' + standardId + '. Run "standards list" to see available.');
|
|
556
|
+
}
|
|
557
|
+
targetIds = [standardId];
|
|
558
|
+
} else {
|
|
559
|
+
// Use project's selected standards
|
|
560
|
+
const stdPath = path.join(planningPath(cwd), STANDARDS_FILE);
|
|
561
|
+
const content = safeReadFile(stdPath);
|
|
562
|
+
if (!content) {
|
|
563
|
+
error('No standards.md found. Select standards first or specify a standard ID.');
|
|
564
|
+
}
|
|
565
|
+
targetIds = parseStandardsFile(content);
|
|
566
|
+
if (targetIds.length === 0) {
|
|
567
|
+
error('No standards in standards.md. Run "standards select <id>" first.');
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const recommendations = targetIds.map(id => {
|
|
572
|
+
const s = STANDARDS_CATALOG[id];
|
|
573
|
+
const tools = STANDARDS_EXTERNAL_TOOLS[id] || [];
|
|
574
|
+
return {
|
|
575
|
+
standard_id: id,
|
|
576
|
+
standard_name: s.name,
|
|
577
|
+
tools,
|
|
578
|
+
};
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const allTools = new Map();
|
|
582
|
+
for (const rec of recommendations) {
|
|
583
|
+
for (const tool of rec.tools) {
|
|
584
|
+
if (!allTools.has(tool.name)) allTools.set(tool.name, { ...tool, standards: [] });
|
|
585
|
+
allTools.get(tool.name).standards.push(rec.standard_id);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
output({
|
|
590
|
+
standards_queried: targetIds,
|
|
591
|
+
recommendations,
|
|
592
|
+
unique_tools: Array.from(allTools.values()),
|
|
593
|
+
}, raw, Array.from(allTools.values()).map(t => `${t.name} — ${t.description} (${t.standards.join(', ')})`).join('\n'));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
module.exports = {
|
|
597
|
+
cmdConfigEnsureSection,
|
|
598
|
+
cmdConfigSet,
|
|
599
|
+
cmdConfigGet,
|
|
600
|
+
cmdStandardsList,
|
|
601
|
+
cmdStandardsSelect,
|
|
602
|
+
cmdStandardsRemove,
|
|
603
|
+
cmdStandardsStatus,
|
|
604
|
+
cmdStandardsRecommend,
|
|
605
|
+
cmdStandardsPhaseTrack,
|
|
606
|
+
cmdStandardsTools,
|
|
607
|
+
// Internal helpers exported for testing
|
|
608
|
+
parseStandardsFile,
|
|
609
|
+
renderStandardsMd,
|
|
610
|
+
detectStandardsFromContent,
|
|
611
|
+
};
|