pan-wizard 3.33.0 → 3.34.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/README.md +23 -20
- package/agents/pan-conductor.md +17 -16
- package/agents/pan-counterfactual.md +2 -2
- package/agents/pan-debugger.md +27 -8
- package/agents/pan-design-checker.md +1 -1
- package/agents/pan-designer.md +2 -2
- package/agents/pan-distiller.md +3 -3
- package/agents/pan-document_code.md +14 -14
- package/agents/pan-executor.md +12 -8
- package/agents/pan-experiment-runner.md +8 -7
- package/agents/pan-integration-checker.md +2 -2
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +3 -3
- package/agents/pan-optimizer.md +39 -43
- package/agents/pan-phase-researcher.md +8 -8
- package/agents/pan-plan-checker.md +30 -28
- package/agents/pan-planner.md +54 -43
- package/agents/pan-previewer.md +1 -1
- package/agents/pan-project-researcher.md +11 -11
- package/agents/pan-release.md +1 -1
- package/agents/pan-research-synthesizer.md +2 -2
- package/agents/pan-reviewer.md +24 -2
- package/agents/pan-roadmapper.md +15 -14
- package/agents/pan-verifier.md +32 -24
- package/bin/install-lib.cjs +58 -23
- package/bin/install.js +47 -1
- package/commands/pan/army.md +15 -11
- package/commands/pan/assumptions.md +2 -2
- package/commands/pan/audit-deployment.md +11 -12
- package/commands/pan/cost.md +8 -4
- package/commands/pan/debug.md +10 -7
- package/commands/pan/design-phase.md +3 -3
- package/commands/pan/discuss-phase.md +2 -0
- package/commands/pan/exec-phase.md +21 -37
- package/commands/pan/experiment.md +9 -8
- package/commands/pan/focus-auto.md +22 -20
- package/commands/pan/focus-design.md +7 -6
- package/commands/pan/focus-doc-audit.md +4 -3
- package/commands/pan/focus-drift-walking.md +4 -3
- package/commands/pan/focus-exec.md +6 -6
- package/commands/pan/focus-plan.md +12 -11
- package/commands/pan/focus-scan.md +5 -3
- package/commands/pan/focus-sync.md +3 -2
- package/commands/pan/git.md +8 -8
- package/commands/pan/health.md +2 -2
- package/commands/pan/hud.md +3 -2
- package/commands/pan/hygiene.md +14 -7
- package/commands/pan/knowledge.md +9 -9
- package/commands/pan/learn.md +10 -7
- package/commands/pan/links.md +4 -1
- package/commands/pan/map-codebase.md +1 -1
- package/commands/pan/mcp-bridge.md +18 -18
- package/commands/pan/milestone-done.md +6 -3
- package/commands/pan/milestone-new.md +1 -1
- package/commands/pan/new-project.md +10 -10
- package/commands/pan/optimize.md +16 -14
- package/commands/pan/patches.md +2 -6
- package/commands/pan/pause.md +1 -1
- package/commands/pan/phase-tests.md +2 -1
- package/commands/pan/plan-phase.md +9 -19
- package/commands/pan/preview.md +1 -1
- package/commands/pan/profile.md +2 -2
- package/commands/pan/quick.md +1 -1
- package/commands/pan/remove-phase.md +1 -1
- package/commands/pan/report.md +2 -2
- package/commands/pan/research-phase.md +12 -11
- package/commands/pan/resume.md +9 -11
- package/commands/pan/retro.md +5 -3
- package/commands/pan/review-deep.md +7 -7
- package/commands/pan/settings.md +1 -1
- package/commands/pan/verify-phase.md +0 -11
- package/commands/pan/what-if.md +2 -2
- package/hooks/dist/pan-context-monitor.js +343 -44
- package/hooks/dist/pan-cost-logger.js +30 -0
- package/hooks/dist/pan-statusline.js +68 -22
- package/hooks/dist/pan-stop-guard.js +7 -2
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +44 -6
- package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
- package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
- package/pan-wizard-core/bin/lib/commands.cjs +33 -4
- package/pan-wizard-core/bin/lib/config.cjs +2 -1
- package/pan-wizard-core/bin/lib/constants.cjs +18 -6
- package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
- package/pan-wizard-core/bin/lib/core.cjs +53 -19
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
- package/pan-wizard-core/bin/lib/cost.cjs +142 -13
- package/pan-wizard-core/bin/lib/distill.cjs +6 -3
- package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
- package/pan-wizard-core/bin/lib/findings.cjs +9 -2
- package/pan-wizard-core/bin/lib/focus.cjs +13 -4
- package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
- package/pan-wizard-core/bin/lib/hygiene.cjs +178 -17
- package/pan-wizard-core/bin/lib/init.cjs +7 -4
- package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
- package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
- package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
- package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
- package/pan-wizard-core/bin/lib/memory.cjs +508 -54
- package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
- package/pan-wizard-core/bin/lib/optimize.cjs +84 -16
- package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
- package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
- package/pan-wizard-core/bin/lib/phase.cjs +15 -13
- package/pan-wizard-core/bin/lib/preview.cjs +3 -1
- package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
- package/pan-wizard-core/bin/lib/runner.cjs +2 -1
- package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
- package/pan-wizard-core/bin/lib/state.cjs +43 -28
- package/pan-wizard-core/bin/lib/template.cjs +11 -4
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
- package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
- package/pan-wizard-core/bin/lib/verify-scope.cjs +5 -2
- package/pan-wizard-core/bin/lib/verify.cjs +30 -28
- package/pan-wizard-core/bin/lib/whatif.cjs +5 -3
- package/pan-wizard-core/bin/pan-tools.cjs +101 -15
- package/pan-wizard-core/learnings/index.json +5 -5
- package/pan-wizard-core/opencode/pan-wizard.js +111 -0
- package/pan-wizard-core/references/checkpoints.md +3 -3
- package/pan-wizard-core/references/continuation-format.md +3 -3
- package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
- package/pan-wizard-core/references/design-methodology.md +1 -1
- package/pan-wizard-core/references/git-integration.md +4 -6
- package/pan-wizard-core/references/git-planning-commit.md +2 -2
- package/pan-wizard-core/references/guardrails.md +2 -2
- package/pan-wizard-core/references/handoff-decisions.md +3 -3
- package/pan-wizard-core/references/model-profile-resolution.md +5 -5
- package/pan-wizard-core/references/model-profiles.md +14 -9
- package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
- package/pan-wizard-core/references/planning-config.md +4 -4
- package/pan-wizard-core/references/tdd.md +10 -1
- package/pan-wizard-core/references/verification-patterns.md +5 -5
- package/pan-wizard-core/templates/codebase/architecture.md +2 -2
- package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
- package/pan-wizard-core/templates/codebase/concerns.md +3 -3
- package/pan-wizard-core/templates/codebase/integrations.md +6 -6
- package/pan-wizard-core/templates/codebase/relationships.md +2 -2
- package/pan-wizard-core/templates/codebase/stack.md +2 -2
- package/pan-wizard-core/templates/codebase/structure.md +4 -4
- package/pan-wizard-core/templates/codebase/testing.md +4 -4
- package/pan-wizard-core/templates/continue-here.md +1 -1
- package/pan-wizard-core/templates/debug-subagent-prompt.md +3 -2
- package/pan-wizard-core/templates/debug.md +15 -1
- package/pan-wizard-core/templates/milestone-archive.md +6 -6
- package/pan-wizard-core/templates/milestone.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +29 -17
- package/pan-wizard-core/templates/planner-subagent-prompt.md +3 -5
- package/pan-wizard-core/templates/research-project/architecture.md +3 -3
- package/pan-wizard-core/templates/research-project/features.md +1 -1
- package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
- package/pan-wizard-core/templates/research-project/stack.md +3 -3
- package/pan-wizard-core/templates/research.md +5 -5
- package/pan-wizard-core/templates/retrospective.md +1 -1
- package/pan-wizard-core/templates/roadmap.md +15 -15
- package/pan-wizard-core/templates/summary-complex.md +1 -0
- package/pan-wizard-core/templates/summary-minimal.md +1 -0
- package/pan-wizard-core/templates/summary-standard.md +2 -1
- package/pan-wizard-core/templates/summary.md +18 -2
- package/pan-wizard-core/templates/uat.md +3 -3
- package/pan-wizard-core/templates/user-setup.md +8 -8
- package/pan-wizard-core/templates/verification-report.md +53 -8
- package/pan-wizard-core/workflows/add-phase.md +1 -1
- package/pan-wizard-core/workflows/assumptions.md +1 -1
- package/pan-wizard-core/workflows/discuss-phase.md +9 -8
- package/pan-wizard-core/workflows/exec-phase.md +26 -57
- package/pan-wizard-core/workflows/execute-plan.md +8 -7
- package/pan-wizard-core/workflows/health.md +13 -12
- package/pan-wizard-core/workflows/help.md +8 -9
- package/pan-wizard-core/workflows/learn.md +13 -11
- package/pan-wizard-core/workflows/map-codebase.md +10 -10
- package/pan-wizard-core/workflows/milestone-audit.md +10 -10
- package/pan-wizard-core/workflows/milestone-gaps.md +17 -24
- package/pan-wizard-core/workflows/milestone-new.md +7 -6
- package/pan-wizard-core/workflows/new-project.md +9 -9
- package/pan-wizard-core/workflows/optimize.md +11 -9
- package/pan-wizard-core/workflows/pause.md +6 -1
- package/pan-wizard-core/workflows/phase-tests.md +4 -4
- package/pan-wizard-core/workflows/plan-phase.md +31 -34
- package/pan-wizard-core/workflows/profile.md +2 -2
- package/pan-wizard-core/workflows/progress.md +5 -7
- package/pan-wizard-core/workflows/quick.md +5 -3
- package/pan-wizard-core/workflows/remove-phase.md +1 -1
- package/pan-wizard-core/workflows/research-phase.md +9 -5
- package/pan-wizard-core/workflows/resume-project.md +4 -6
- package/pan-wizard-core/workflows/retro.md +3 -1
- package/pan-wizard-core/workflows/settings.md +16 -24
- package/pan-wizard-core/workflows/todo-check.md +1 -1
- package/pan-wizard-core/workflows/transition.md +12 -12
- package/pan-wizard-core/workflows/update.md +25 -17
- package/pan-wizard-core/workflows/verify-phase.md +17 -15
- package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
- package/pan-zcode/README.md +7 -7
- package/pan-zcode/bin/install-zcode.js +1 -1
- package/scripts/build-plugin.js +3 -1
- package/scripts/generate-skills-docs.py +13 -4
|
@@ -294,22 +294,22 @@ function cmdStateRecordMetric(cwd, options, raw) {
|
|
|
294
294
|
return;
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
//
|
|
298
|
-
|
|
297
|
+
// The Performance Metrics section's first table: its rows only, then an optional
|
|
298
|
+
// "None yet" line. Taking the rest of the section as the table body put every row
|
|
299
|
+
// below the template's "Recent Trend" and footer lines, outside the table. The
|
|
300
|
+
// separator is matched on one line: `[-|\s]+` ran on into a `| - | - |` row.
|
|
301
|
+
const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|: \t]+\r?\n)((?:\|[^\n]*(?:\n|$))*)((?:[^\S\n]*None yet\.?[^\S\n]*(?:\n|$))?)/i;
|
|
299
302
|
const metricsMatch = content.match(metricsPattern);
|
|
300
303
|
|
|
301
304
|
if (metricsMatch) {
|
|
302
|
-
let tableBody = metricsMatch[2].trimEnd();
|
|
303
305
|
const cell = escapeTableCell;
|
|
304
306
|
const newRow = `| Phase ${cell(phase)} P${cell(plan)} | ${cell(duration)} | ${cell(tasks || '-')} tasks | ${cell(files || '-')} files |`;
|
|
307
|
+
// A template placeholder row (`| - | - | - | - |`, `| None yet | | | |`) gives way.
|
|
308
|
+
const isPlaceholder = (row) => /None yet/i.test(row) || row.split('|').slice(1, -1).every((c) => /^\s*-?\s*$/.test(c));
|
|
309
|
+
const rows = metricsMatch[2].split('\n').map((r) => r.replace(/\r$/, '')).filter((r) => r.trim() && !isPlaceholder(r));
|
|
310
|
+
rows.push(newRow);
|
|
305
311
|
|
|
306
|
-
|
|
307
|
-
tableBody = newRow;
|
|
308
|
-
} else {
|
|
309
|
-
tableBody = tableBody + '\n' + newRow;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
|
|
312
|
+
content = content.replace(metricsPattern, (_match, header) => `${header}${rows.join('\n')}\n`);
|
|
313
313
|
writeStateMd(statePath, content, cwd);
|
|
314
314
|
output({ recorded: true, phase, plan, duration }, raw, 'true');
|
|
315
315
|
} else {
|
|
@@ -432,7 +432,8 @@ function cmdStateAddBlocker(cwd, text, raw) {
|
|
|
432
432
|
|
|
433
433
|
if (match) {
|
|
434
434
|
let sectionBody = match[2];
|
|
435
|
-
|
|
435
|
+
// "None yet" first: stripping "None" first left a stray "yet." on the template.
|
|
436
|
+
sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, '').replace(/None\.?\s*\n?/gi, '');
|
|
436
437
|
sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
|
|
437
438
|
content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
|
|
438
439
|
writeStateMd(statePath, content, cwd);
|
|
@@ -538,14 +539,23 @@ function cmdStateRecordSession(cwd, options, raw) {
|
|
|
538
539
|
// --- Snapshot Parsers --------------------------------------------------------
|
|
539
540
|
|
|
540
541
|
/**
|
|
541
|
-
* Parse the
|
|
542
|
-
*
|
|
543
|
-
*
|
|
542
|
+
* Parse the decisions from state.md content: the `- [Phase N]: summary — rationale`
|
|
543
|
+
* bullets `state add-decision` appends under `### Decisions` (the template's
|
|
544
|
+
* heading), or else an older "## Decisions Made" table. Reading only the table
|
|
545
|
+
* left `decisions` empty on every state.md PAN itself writes.
|
|
544
546
|
* @param {string} content - Full state.md content
|
|
545
547
|
* @returns {Array<{phase: string, summary: string, rationale: string}>}
|
|
546
548
|
*/
|
|
547
549
|
function parseDecisionsFromState(content) {
|
|
548
550
|
const decisions = [];
|
|
551
|
+
const section = content.match(/###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
|
|
552
|
+
for (const line of section ? section[1].split('\n') : []) {
|
|
553
|
+
const m = line.match(/^-\s+\[Phase\s+([^\]]*)\]:\s*(.+?)\s*$/i);
|
|
554
|
+
if (!m) continue;
|
|
555
|
+
const [summary, ...rest] = m[2].split(' — ');
|
|
556
|
+
decisions.push({ phase: m[1].trim(), summary: summary.trim(), rationale: rest.join(' — ').trim() });
|
|
557
|
+
}
|
|
558
|
+
if (decisions.length > 0) return decisions;
|
|
549
559
|
// Match the decisions table body after header row and separator row
|
|
550
560
|
const decisionsMatch = content.match(/##\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n([\s\S]*?)(?=\n##|\n$|$)/i);
|
|
551
561
|
if (decisionsMatch) {
|
|
@@ -567,14 +577,15 @@ function parseDecisionsFromState(content) {
|
|
|
567
577
|
|
|
568
578
|
/**
|
|
569
579
|
* Parse the Blockers section from state.md content.
|
|
570
|
-
* Extracts bullet-point items (lines starting with "- ") from under
|
|
571
|
-
*
|
|
580
|
+
* Extracts bullet-point items (lines starting with "- ") from under the heading
|
|
581
|
+
* `state add-blocker` writes to: `### Blockers/Concerns` in the template, or
|
|
582
|
+
* `Blockers` / `Concerns`. Matching only "## Blockers" left the list empty.
|
|
572
583
|
* @param {string} content - Full state.md content
|
|
573
584
|
* @returns {string[]} Array of blocker text strings
|
|
574
585
|
*/
|
|
575
586
|
function parseBlockersFromState(content) {
|
|
576
587
|
const blockers = [];
|
|
577
|
-
const blockersMatch = content.match(
|
|
588
|
+
const blockersMatch = content.match(/###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
|
|
578
589
|
if (blockersMatch) {
|
|
579
590
|
const blockersSection = blockersMatch[1];
|
|
580
591
|
const items = blockersSection.match(/^-\s+(.+)$/gm) || [];
|
|
@@ -585,6 +596,18 @@ function parseBlockersFromState(content) {
|
|
|
585
596
|
return blockers;
|
|
586
597
|
}
|
|
587
598
|
|
|
599
|
+
/**
|
|
600
|
+
* The blockers that are still open: the section's bullets minus a "None" placeholder.
|
|
601
|
+
* `preflight` and `dashboard` read a `## Blockers` heading of their own, which the
|
|
602
|
+
* state template never writes (it writes `### Blockers/Concerns`, where
|
|
603
|
+
* `state add-blocker` puts its bullets), so both reported no blockers with one open.
|
|
604
|
+
* @param {string} content - Full state.md content
|
|
605
|
+
* @returns {string[]}
|
|
606
|
+
*/
|
|
607
|
+
function activeBlockersFromState(content) {
|
|
608
|
+
return parseBlockersFromState(content).filter((b) => b && !/^none(\s+yet)?\.?$/i.test(b));
|
|
609
|
+
}
|
|
610
|
+
|
|
588
611
|
/**
|
|
589
612
|
* Parse the Session section from state.md content.
|
|
590
613
|
* Extracts **Last Date:**, **Stopped At:**, and **Resume File:** bold-field
|
|
@@ -1029,17 +1052,8 @@ function cmdDashboard(cwd, raw) {
|
|
|
1029
1052
|
lastActivity = stateExtractField(stateContent, 'Last Activity');
|
|
1030
1053
|
lastActivityDesc = stateExtractField(stateContent, 'Last Activity Description');
|
|
1031
1054
|
|
|
1032
|
-
// Parse blockers
|
|
1033
|
-
|
|
1034
|
-
if (blockersMatch) {
|
|
1035
|
-
const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
|
|
1036
|
-
for (const item of items) {
|
|
1037
|
-
const text = item.replace(/^-\s+/, '').trim();
|
|
1038
|
-
if (text && !/^none$/i.test(text)) {
|
|
1039
|
-
activeBlockers.push(text);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1055
|
+
// Parse blockers (the template's `### Blockers/Concerns`, or a legacy `## Blockers`)
|
|
1056
|
+
activeBlockers.push(...activeBlockersFromState(stateContent));
|
|
1043
1057
|
blockerCount = activeBlockers.length;
|
|
1044
1058
|
}
|
|
1045
1059
|
|
|
@@ -1111,6 +1125,7 @@ function cmdDashboard(cwd, raw) {
|
|
|
1111
1125
|
|
|
1112
1126
|
module.exports = {
|
|
1113
1127
|
readStateSafe: safeReadFile,
|
|
1128
|
+
activeBlockersFromState,
|
|
1114
1129
|
stateExtractField,
|
|
1115
1130
|
stateReplaceField,
|
|
1116
1131
|
writeStateMd,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const {
|
|
8
|
-
PHASES_DIR, PLAN_SUFFIX, SUMMARY_SUFFIX, VERIFICATION_SUFFIX,
|
|
8
|
+
PHASES_DIR, PLAN_SUFFIX, SUMMARY_SUFFIX, VERIFICATION_SUFFIX, ROADMAP_SLICE_SUFFIX,
|
|
9
9
|
SIMPLE_TASK_THRESHOLD, SIMPLE_FILE_THRESHOLD, COMPLEX_TASK_THRESHOLD, COMPLEX_FILE_THRESHOLD,
|
|
10
10
|
} = require('./constants.cjs');
|
|
11
11
|
const { planningPath, phasesPath } = require('./utils.cjs');
|
|
@@ -86,7 +86,7 @@ function cmdTemplateSelect(cwd, planPath, raw) {
|
|
|
86
86
|
* @param {string} phaseName - Human-readable phase name
|
|
87
87
|
* @param {string} phaseSlug - Phase slug for directory naming
|
|
88
88
|
* @param {string} phaseNum - Raw phase number from options
|
|
89
|
-
* @param {Object} options - Additional options (type, wave, fields)
|
|
89
|
+
* @param {Object} options - Additional options (type, wave, fields, slicePath)
|
|
90
90
|
* @returns {{frontmatter: Object, body: string, fileName: string}}
|
|
91
91
|
*/
|
|
92
92
|
function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, options) {
|
|
@@ -94,6 +94,10 @@ function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, o
|
|
|
94
94
|
const wave = parseInt(options.wave, 10) || 1;
|
|
95
95
|
const fields = options.fields || {};
|
|
96
96
|
const padded = phaseId.split('-')[0]; // e.g., "01" from "01-setup-auth"
|
|
97
|
+
// The <context> names the phase's roadmap slice, as templates/phase-prompt.md
|
|
98
|
+
// does (O2): `@.planning/roadmap.md` handed every executor the whole roadmap.
|
|
99
|
+
// cmdTemplateFill passes the phase's real directory.
|
|
100
|
+
const slicePath = options.slicePath || `.planning/phases/${phaseId}/${padded}${ROADMAP_SLICE_SUFFIX}`;
|
|
97
101
|
|
|
98
102
|
const frontmatter = {
|
|
99
103
|
phase: phaseId,
|
|
@@ -121,7 +125,7 @@ function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, o
|
|
|
121
125
|
'',
|
|
122
126
|
'## Context',
|
|
123
127
|
'@.planning/project.md',
|
|
124
|
-
|
|
128
|
+
`@${slicePath}`,
|
|
125
129
|
'@.planning/state.md',
|
|
126
130
|
'',
|
|
127
131
|
'## Tasks',
|
|
@@ -286,7 +290,10 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
|
286
290
|
generated = generateSummaryTemplate(phaseId, planNum, phaseName, options.phase, fields);
|
|
287
291
|
break;
|
|
288
292
|
case 'plan':
|
|
289
|
-
generated = generatePlanTemplate(phaseId, planNum, phaseName, phaseSlug, options.phase,
|
|
293
|
+
generated = generatePlanTemplate(phaseId, planNum, phaseName, phaseSlug, options.phase, {
|
|
294
|
+
...options,
|
|
295
|
+
slicePath: toPosix(path.join(phaseInfo.directory, `${padded}${ROADMAP_SLICE_SUFFIX}`)),
|
|
296
|
+
});
|
|
290
297
|
break;
|
|
291
298
|
case 'verification':
|
|
292
299
|
generated = generateVerificationTemplate(phaseId, phaseName, options.phase, fields);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const { safeReadFile, execGit, findPhaseInternal, output, normalizePhaseName } = require('./core.cjs');
|
|
10
|
-
const { readStateSafe } = require('./state.cjs');
|
|
10
|
+
const { readStateSafe, activeBlockersFromState } = require('./state.cjs');
|
|
11
11
|
const {
|
|
12
12
|
STATE_FILE, ROADMAP_FILE, CONFIG_FILE, PATTERNS_FILE, PHASE_DIR_RE,
|
|
13
13
|
} = require('./constants.cjs');
|
|
@@ -44,19 +44,10 @@ function cmdPreflight(cwd, target, raw) {
|
|
|
44
44
|
blockers.push('state.md not found — run /pan:new-project');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// Check 3: no unresolved blockers in state.md
|
|
47
|
+
// Check 3: no unresolved blockers in state.md (the template's `### Blockers/Concerns`,
|
|
48
|
+
// where `state add-blocker` writes, or a legacy `## Blockers`)
|
|
48
49
|
if (stateContent) {
|
|
49
|
-
const
|
|
50
|
-
const activeBlockers = [];
|
|
51
|
-
if (blockersMatch) {
|
|
52
|
-
const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
|
|
53
|
-
for (const item of items) {
|
|
54
|
-
const text = item.replace(/^-\s+/, '').trim();
|
|
55
|
-
if (text && !/^none$/i.test(text)) {
|
|
56
|
-
activeBlockers.push(text);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
50
|
+
const activeBlockers = activeBlockersFromState(stateContent);
|
|
60
51
|
if (activeBlockers.length === 0) {
|
|
61
52
|
checks.push({ name: 'no_blockers', passed: true });
|
|
62
53
|
} else {
|
|
@@ -122,9 +122,11 @@ function cmdRetro(cwd, raw, args) {
|
|
|
122
122
|
common_gap_patterns: gapGroups,
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
-
// E-4: optional memory write. Top gap patterns become lessons
|
|
126
|
-
// (they surface what plans routinely miss)
|
|
127
|
-
// pan-verifier
|
|
125
|
+
// E-4: optional memory write. Top gap patterns become lessons in pan-planner's
|
|
126
|
+
// log (they surface what plans routinely miss), first-try rate deltas in
|
|
127
|
+
// pan-verifier's. Stored for /pan:knowledge: PAN's workflows no longer load agent
|
|
128
|
+
// memory into agents (ADR-0036, amended 2026-10-04), and /pan:army no longer
|
|
129
|
+
// passes the flag.
|
|
128
130
|
const argsList = Array.isArray(args) ? args : [];
|
|
129
131
|
if (argsList.includes('--write-memory')) {
|
|
130
132
|
const { appendMemory } = require('./memory.cjs');
|
|
@@ -27,8 +27,11 @@ const LOCKFILES = new Set([
|
|
|
27
27
|
]);
|
|
28
28
|
// PAN's own installed trees: a phase commit that touches them is PAN bookkeeping,
|
|
29
29
|
// never the project's scope (pan-reviewer skips them for the same reason).
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// A Copilot install shares `.github/` with the project: only what the installer writes as
|
|
31
|
+
// its own is listed, so the project's workflows, hooks and Copilot instructions stay in scope,
|
|
32
|
+
// and so do the files PAN merges into (`mcp.json`, `copilot/settings.json`).
|
|
33
|
+
const PAN_OWNED_PREFIXES = ['.claude/', '.codex/', '.gemini/', '.opencode/', '.agents/', '.github/pan-wizard-core/', '.github/pan-local-patches/', '.github/agents/pan-', '.github/skills/pan-', '.github/hooks/pan-'];
|
|
34
|
+
const PAN_OWNED_FILES = new Set(['.github/hooks/pan.json', '.github/package.json', '.github/pan-file-manifest.json']);
|
|
32
35
|
const TEST_PATH_RE = /(^|\/)(tests?|__tests__|specs?)\//i;
|
|
33
36
|
const TEST_FILE_RE = /\.(test|spec)\.[a-z0-9]+$/i;
|
|
34
37
|
const COMMIT_SCAN_LIMIT = 2000;
|
|
@@ -25,6 +25,7 @@ const { detectInstalledRuntimes, validateRuntimeInstall, cmdValidateDeployment }
|
|
|
25
25
|
const { cmdPreflight, cmdDepsValidate } = require('./verify-preflight.cjs');
|
|
26
26
|
const { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale } = require('./verify-scope.cjs');
|
|
27
27
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
28
|
+
const { roadmapRequirementIds } = require('./roadmap.cjs');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Spot-check files mentioned in summary content.
|
|
@@ -439,11 +440,10 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
439
440
|
// artifacts: [], key_links: [] }` (pan-wizard-core/templates/phase-prompt.md,
|
|
440
441
|
// template.cjs generatePlanTemplate), so an empty block is the documented default
|
|
441
442
|
// state of a scaffolded plan; presence of the block is enforced by
|
|
442
|
-
// `verify plan-structure`, not here. (2) parseMustHavesBlock
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
// this as a failure requires fixing that indentation mismatch first.
|
|
443
|
+
// `verify plan-structure`, not here. (2) parseMustHavesBlock once required a
|
|
444
|
+
// 4-space indent while every shipped template and agent writes 2, so this branch
|
|
445
|
+
// fired for every plan in PAN's own format. It reads relative indent now
|
|
446
|
+
// (frontmatter.cjs), so reaching it means the plan declares no artifacts.
|
|
447
447
|
output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
@@ -508,8 +508,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
508
508
|
const r = checkKeyLinks(cwd, content);
|
|
509
509
|
if (r.total === 0) {
|
|
510
510
|
// EXIT_OK: same reasoning as cmdVerifyArtifacts above — an empty key_links block is
|
|
511
|
-
// the shipped template default,
|
|
512
|
-
// this branch fires for every plan written in PAN's own format.
|
|
511
|
+
// the shipped template default, so a plan that declares none is a result, not a failure.
|
|
513
512
|
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
514
513
|
return;
|
|
515
514
|
}
|
|
@@ -550,10 +549,19 @@ function reconcilePhase(cwd, phaseNum) {
|
|
|
550
549
|
const sm = verRaw.match(/^status:\s*([A-Za-z_-]+)/m);
|
|
551
550
|
const status = sm ? sm[1].toLowerCase() : 'unknown';
|
|
552
551
|
const claimsPass = /^(pass|passed|verified|complete|verified_pass)$/.test(status);
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const artifacts =
|
|
556
|
-
const keyLinks =
|
|
552
|
+
// Every plan's must_haves. Reading only the first plan let a later plan's failing
|
|
553
|
+
// artifact through under a passing verdict — the rubber stamp this check exists for.
|
|
554
|
+
const artifacts = { all_passed: true, passed: 0, total: 0, artifacts: [] };
|
|
555
|
+
const keyLinks = { all_verified: true, verified: 0, total: 0, links: [] };
|
|
556
|
+
for (const planFile of files.filter(f => isPlanFile(f)).sort()) {
|
|
557
|
+
const planContent = safeReadFile(path.join(dir, planFile)) || '';
|
|
558
|
+
const a = checkArtifacts(cwd, planContent);
|
|
559
|
+
const k = checkKeyLinks(cwd, planContent);
|
|
560
|
+
artifacts.passed += a.passed; artifacts.total += a.total; artifacts.artifacts.push(...a.artifacts.map(x => ({ plan: planFile, ...x })));
|
|
561
|
+
keyLinks.verified += k.verified; keyLinks.total += k.total; keyLinks.links.push(...k.links.map(x => ({ plan: planFile, ...x })));
|
|
562
|
+
}
|
|
563
|
+
artifacts.all_passed = artifacts.passed === artifacts.total;
|
|
564
|
+
keyLinks.all_verified = keyLinks.verified === keyLinks.total;
|
|
557
565
|
const signals = artifacts.total + keyLinks.total;
|
|
558
566
|
const contradictions = [];
|
|
559
567
|
if (claimsPass) {
|
|
@@ -1236,11 +1244,10 @@ function syncRequirementCheckboxes(cwd) {
|
|
|
1236
1244
|
// For each completed phase, find linked requirement IDs and check their boxes
|
|
1237
1245
|
let fixed = 0;
|
|
1238
1246
|
for (const phaseNum of completedPhases) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(id => id.trim()).filter(Boolean);
|
|
1247
|
+
// The phase's own requirements, as `phase complete` reads them: a bare "Phase N …
|
|
1248
|
+
// Requirements" search took the first section's line for every phase when the
|
|
1249
|
+
// checklist sits above the sections, as templates/roadmap.md lays it out.
|
|
1250
|
+
const reqIds = roadmapRequirementIds(roadmapContent, phaseNum);
|
|
1244
1251
|
for (const reqId of reqIds) {
|
|
1245
1252
|
const escaped = escapeRegex(reqId);
|
|
1246
1253
|
const re = new RegExp(`(- \\[) (\\]\\s*\\*\\*${escaped}\\*\\*)`, 'gi');
|
|
@@ -1461,7 +1468,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1461
1468
|
// Check 10 (optional): full validation — run tests and build
|
|
1462
1469
|
let testStatus;
|
|
1463
1470
|
let buildStatus;
|
|
1464
|
-
let memoryBudget;
|
|
1465
1471
|
if (options.full) {
|
|
1466
1472
|
testStatus = runFullTestCheck(cwd);
|
|
1467
1473
|
buildStatus = runFullBuildCheck(cwd);
|
|
@@ -1471,14 +1477,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1471
1477
|
if (buildStatus.pass === false) {
|
|
1472
1478
|
addIssue('error', 'BUILD_FAIL', `Build failed (exit code ${buildStatus.exitCode})`, 'Fix build errors');
|
|
1473
1479
|
}
|
|
1474
|
-
//
|
|
1475
|
-
//
|
|
1476
|
-
|
|
1477
|
-
if (memoryBudget.status === 'critical') {
|
|
1478
|
-
addIssue('warning', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1479
|
-
} else if (memoryBudget.status === 'warning') {
|
|
1480
|
-
addIssue('info', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1481
|
-
}
|
|
1480
|
+
// No memory-load check: PAN's workflows no longer load agent memory into
|
|
1481
|
+
// agents (ADR-0036, amended 2026-10-04), so its size costs no agent context.
|
|
1482
|
+
// `pan-tools memory budget` still sizes the store on request.
|
|
1482
1483
|
}
|
|
1483
1484
|
|
|
1484
1485
|
// Check 11 (optional): drift analysis
|
|
@@ -1539,7 +1540,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1539
1540
|
if (options.full) {
|
|
1540
1541
|
result.test_status = testStatus;
|
|
1541
1542
|
result.build_status = buildStatus;
|
|
1542
|
-
result.memory_budget = memoryBudget;
|
|
1543
1543
|
}
|
|
1544
1544
|
if (options.drift) {
|
|
1545
1545
|
result.drift_status = driftResult;
|
|
@@ -1549,8 +1549,10 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
1551
|
// Explicit verdict exit: `broken` → 1; `degraded` and `healthy` → 0 (warnings are
|
|
1552
|
-
// not failures).
|
|
1553
|
-
//
|
|
1552
|
+
// not failures). The issue lists were collected before --repair ran and a repair
|
|
1553
|
+
// removes nothing from them, so the status and this code describe the tree as it
|
|
1554
|
+
// was before the repairs; `repairs_performed` says what changed, and a second run
|
|
1555
|
+
// shows the result. See the note at the early-return site above.
|
|
1554
1556
|
output(result, raw, undefined, status === HEALTH_STATUS.BROKEN ? 1 : 0);
|
|
1555
1557
|
}
|
|
1556
1558
|
|
|
@@ -187,10 +187,12 @@ function createWorktree(cwd, phaseNum, scenario, opts) {
|
|
|
187
187
|
return { error: 'Not a git repo — what-if requires git worktree support' };
|
|
188
188
|
}
|
|
189
189
|
const slug = scenarioSlug(scenario);
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
// To the millisecond: cut to `slice(0, 15)` it had ten-minute resolution, so a repeat
|
|
191
|
+
// of the same phase and scenario within ten minutes failed on an existing branch.
|
|
192
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23);
|
|
193
|
+
const branch = `${BRANCH_PREFIX}${phaseNum}-${slug}-${ts}`;
|
|
192
194
|
const worktreeRoot = opts?.worktree_root
|
|
193
|
-
|| path.join(path.dirname(cwd), `pan-whatif-${phaseNum}-${slug}-${ts
|
|
195
|
+
|| path.join(path.dirname(cwd), `pan-whatif-${phaseNum}-${slug}-${ts}`);
|
|
194
196
|
|
|
195
197
|
// Base ref: current HEAD by default. Callers can override (e.g. to branch
|
|
196
198
|
// off main for a clean comparison).
|
|
@@ -187,6 +187,35 @@
|
|
|
187
187
|
|
|
188
188
|
const fs = require('fs');
|
|
189
189
|
const path = require('path');
|
|
190
|
+
|
|
191
|
+
// Compile cache (Node 22.1+; a no-op before): V8 reuses the compiled code of pan-tools
|
|
192
|
+
// and its modules across processes, which workflows start many times per phase — a
|
|
193
|
+
// `version` call went from 100 to 75 ms median (measured 2026-10-10, bare node 44 ms).
|
|
194
|
+
// The cache sits in the per-user 0700 directory the hooks already use, never Node's
|
|
195
|
+
// default under the shared system temp directory, where another user could plant
|
|
196
|
+
// compiled code. A directory that is a symlink, not ours, or open to group or other
|
|
197
|
+
// disables it. NODE_COMPILE_CACHE, when set, is Node's own choice and wins; any failure
|
|
198
|
+
// runs without a cache.
|
|
199
|
+
function enableCompileCache() {
|
|
200
|
+
try {
|
|
201
|
+
const mod = require('module');
|
|
202
|
+
if (typeof mod.enableCompileCache !== 'function' || process.env.NODE_COMPILE_CACHE) return;
|
|
203
|
+
const os = require('os');
|
|
204
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : (process.env.USERNAME || 'win');
|
|
205
|
+
const parent = path.join(os.tmpdir(), `pan-hooks-${uid}`);
|
|
206
|
+
const dir = path.join(parent, 'compile-cache');
|
|
207
|
+
for (const d of [parent, dir]) {
|
|
208
|
+
fs.mkdirSync(d, { recursive: true, mode: 0o700 });
|
|
209
|
+
const st = fs.lstatSync(d);
|
|
210
|
+
if (st.isSymbolicLink()) return;
|
|
211
|
+
// POSIX only: Windows reports synthetic mode bits.
|
|
212
|
+
if (typeof process.getuid === 'function' && (st.uid !== process.getuid() || (st.mode & 0o077) !== 0)) return;
|
|
213
|
+
}
|
|
214
|
+
mod.enableCompileCache(dir);
|
|
215
|
+
} catch { /* best effort: run uncached */ }
|
|
216
|
+
}
|
|
217
|
+
enableCompileCache();
|
|
218
|
+
|
|
190
219
|
const { error, output, buildCachedContext } = require('./lib/core.cjs');
|
|
191
220
|
const state = require('./lib/state.cjs');
|
|
192
221
|
const phase = require('./lib/phase.cjs');
|
|
@@ -333,7 +362,7 @@ async function main() {
|
|
|
333
362
|
+ ' --planning-dir <path> act on an arbitrary project-relative planning tree\n'
|
|
334
363
|
+ ' --all-tracks (hygiene) act on the root tree AND every discovered track\n'
|
|
335
364
|
+ ' env: PAN_TRACK, PAN_PLANNING_DIR (flags win)\n'
|
|
336
|
-
+ '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links';
|
|
365
|
+
+ '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links, version';
|
|
337
366
|
|
|
338
367
|
if (!command) {
|
|
339
368
|
error(USAGE);
|
|
@@ -538,9 +567,16 @@ async function main() {
|
|
|
538
567
|
const force = args.includes('--force');
|
|
539
568
|
const failOnError = args.includes('--fail-on-error');
|
|
540
569
|
const message = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
541
|
-
// Parse --files flag
|
|
570
|
+
// Parse --files flag: collect args after --files, stopping at the next flag. A
|
|
571
|
+
// filter here took every later bare argument, so `--files f --type docs` staged `docs`.
|
|
542
572
|
const filesIndex = args.indexOf('--files');
|
|
543
|
-
const files =
|
|
573
|
+
const files = [];
|
|
574
|
+
if (filesIndex !== -1) {
|
|
575
|
+
for (const a of args.slice(filesIndex + 1)) {
|
|
576
|
+
if (a.startsWith('--')) break;
|
|
577
|
+
files.push(a);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
544
580
|
const commitType = getArgValue(args, '--type');
|
|
545
581
|
commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force, failOnError });
|
|
546
582
|
break;
|
|
@@ -689,8 +725,16 @@ async function main() {
|
|
|
689
725
|
} else if (subcommand === 'update-plan-progress') {
|
|
690
726
|
if (!args[2]) error('roadmap update-plan-progress requires a phase number');
|
|
691
727
|
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
|
728
|
+
} else if (subcommand === 'slice') {
|
|
729
|
+
if (!args[2]) error('roadmap slice requires a phase number');
|
|
730
|
+
roadmap.cmdRoadmapSlice(cwd, args[2], { write: args.includes('--write') }, raw);
|
|
731
|
+
} else if (subcommand === 'compact') {
|
|
732
|
+
const keepIdx = args.indexOf('--keep');
|
|
733
|
+
const keep = keepIdx !== -1 ? Number(args[keepIdx + 1]) : undefined;
|
|
734
|
+
if (keepIdx !== -1 && !(Number.isInteger(keep) && keep >= 0)) error('roadmap compact --keep needs a whole number of phases (0 or more)');
|
|
735
|
+
require('./lib/roadmap-compact.cjs').cmdRoadmapCompact(cwd, { apply: args.includes('--apply'), keep }, raw);
|
|
692
736
|
} else {
|
|
693
|
-
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
|
737
|
+
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, slice, compact');
|
|
694
738
|
}
|
|
695
739
|
break;
|
|
696
740
|
}
|
|
@@ -1038,7 +1082,12 @@ async function main() {
|
|
|
1038
1082
|
if (subcommand === 'read') {
|
|
1039
1083
|
memory.cmdMemoryRead(cwd, args[2], raw);
|
|
1040
1084
|
} else if (subcommand === 'append') {
|
|
1041
|
-
|
|
1085
|
+
// `--cites <path[#symbol]>,...` is taken out before the entry words are joined.
|
|
1086
|
+
const rest = args.slice(3);
|
|
1087
|
+
const ci = rest.indexOf('--cites');
|
|
1088
|
+
const cites = ci === -1 ? undefined : rest[ci + 1];
|
|
1089
|
+
if (ci !== -1) rest.splice(ci, 2);
|
|
1090
|
+
memory.cmdMemoryAppend(cwd, args[2], rest.join(' '), raw, { cites });
|
|
1042
1091
|
} else if (subcommand === 'list') {
|
|
1043
1092
|
memory.cmdMemoryList(cwd, raw);
|
|
1044
1093
|
} else if (subcommand === 'compact') {
|
|
@@ -1048,7 +1097,22 @@ async function main() {
|
|
|
1048
1097
|
cue: getArgValue(args, '--cue'),
|
|
1049
1098
|
tokenBudget: getArgValue(args, '--token-budget'),
|
|
1050
1099
|
recencyFloor: getArgValue(args, '--recency-floor'),
|
|
1100
|
+
all: args.includes('--all'),
|
|
1101
|
+
markUsed: args.includes('--mark-used'),
|
|
1102
|
+
expireDays: getArgValue(args, '--days'),
|
|
1103
|
+
}, raw);
|
|
1104
|
+
} else if (subcommand === 'record') {
|
|
1105
|
+
memory.cmdMemoryRecord(cwd, args[2] && !args[2].startsWith('--') ? args[2] : null, {
|
|
1106
|
+
lesson: getArgValue(args, '--lesson'),
|
|
1107
|
+
finding: getArgValue(args, '--finding'),
|
|
1108
|
+
trace: getArgValue(args, '--trace'),
|
|
1109
|
+
cites: getArgValue(args, '--cites'),
|
|
1051
1110
|
}, raw);
|
|
1111
|
+
} else if (subcommand === 'prune') {
|
|
1112
|
+
const daysArg = getArgValue(args, '--days');
|
|
1113
|
+
if (daysArg !== null && !/^\d+$/.test(daysArg)) error('memory prune --days needs a whole number of days (0 turns expiry off)');
|
|
1114
|
+
const target = args[2] && !args[2].startsWith('--') ? args[2] : null;
|
|
1115
|
+
memory.cmdMemoryPrune(cwd, target, { apply: args.includes('--apply'), days: daysArg === null ? undefined : Number(daysArg) }, raw);
|
|
1052
1116
|
} else if (subcommand === 'budget') {
|
|
1053
1117
|
memory.cmdMemoryBudget(cwd, raw);
|
|
1054
1118
|
} else if (subcommand === 'optimize') {
|
|
@@ -1062,7 +1126,7 @@ async function main() {
|
|
|
1062
1126
|
apply: args.includes('--apply'),
|
|
1063
1127
|
}, raw);
|
|
1064
1128
|
} else {
|
|
1065
|
-
error('Unknown memory subcommand. Available: read, append, list, compact, select, budget, optimize, rebuild');
|
|
1129
|
+
error('Unknown memory subcommand. Available: read, append, list, compact, select, record, prune, budget, optimize, rebuild');
|
|
1066
1130
|
}
|
|
1067
1131
|
break;
|
|
1068
1132
|
}
|
|
@@ -1117,7 +1181,14 @@ async function main() {
|
|
|
1117
1181
|
case 'knowledge': {
|
|
1118
1182
|
const subcommand = args[1];
|
|
1119
1183
|
if (subcommand === 'ask') {
|
|
1120
|
-
|
|
1184
|
+
// The question is every word after `ask` that is neither a flag nor a flag's
|
|
1185
|
+
// value: `--recall-cue "session tokens" --max-sources 3` used to join it.
|
|
1186
|
+
const words = [];
|
|
1187
|
+
for (let i = 2; i < args.length; i++) {
|
|
1188
|
+
if (args[i] === '--max-sources' || args[i] === '--recall-cue') { i++; continue; }
|
|
1189
|
+
if (!args[i].startsWith('--')) words.push(args[i]);
|
|
1190
|
+
}
|
|
1191
|
+
const question = words.join(' ');
|
|
1121
1192
|
const maxSources = getArgValue(args, '--max-sources');
|
|
1122
1193
|
knowledge.cmdKnowledgeAsk(cwd, question, {
|
|
1123
1194
|
max_sources: maxSources ? Number(maxSources) : undefined,
|
|
@@ -1249,7 +1320,8 @@ async function main() {
|
|
|
1249
1320
|
|
|
1250
1321
|
case 'cost': {
|
|
1251
1322
|
const subcommand = args[1];
|
|
1252
|
-
|
|
1323
|
+
// `report` is the default, so a leading flag (`cost --format chart`) means report, as for `links`.
|
|
1324
|
+
if (subcommand === 'report' || !subcommand || subcommand.startsWith('--')) {
|
|
1253
1325
|
const format = getArgValue(args, '--format', 'json');
|
|
1254
1326
|
const since = getArgValue(args, '--since');
|
|
1255
1327
|
const until = getArgValue(args, '--until');
|
|
@@ -1276,8 +1348,10 @@ async function main() {
|
|
|
1276
1348
|
mainThread: !args.includes('--no-main-thread'),
|
|
1277
1349
|
claudeDir: getArgValue(args, '--claude-dir'),
|
|
1278
1350
|
}, raw);
|
|
1351
|
+
} else if (subcommand === 'limits') {
|
|
1352
|
+
cost.cmdCostLimits(cwd, raw);
|
|
1279
1353
|
} else {
|
|
1280
|
-
error('Unknown cost subcommand. Available: report, append, clear, rebuild');
|
|
1354
|
+
error('Unknown cost subcommand. Available: report, append, clear, rebuild, limits');
|
|
1281
1355
|
}
|
|
1282
1356
|
break;
|
|
1283
1357
|
}
|
|
@@ -1330,7 +1404,7 @@ async function main() {
|
|
|
1330
1404
|
campaign.cmdCampaignSchedule(cwd, {
|
|
1331
1405
|
goal: getArgValue(args, '--goal'),
|
|
1332
1406
|
source: getArgValue(args, '--source'),
|
|
1333
|
-
cadence: getArgValue(args, '--cadence'
|
|
1407
|
+
cadence: getArgValue(args, '--cadence'),
|
|
1334
1408
|
daily_budget: budget != null ? Number(budget) : undefined,
|
|
1335
1409
|
enabled: args.includes('--disable') ? false : undefined,
|
|
1336
1410
|
paused: args.includes('--pause') ? true : (args.includes('--resume') ? false : undefined),
|
|
@@ -1539,9 +1613,11 @@ async function main() {
|
|
|
1539
1613
|
? sourceExpsCsv.split(',').map(s => s.trim()).filter(Boolean)
|
|
1540
1614
|
: [];
|
|
1541
1615
|
const sourceRoot = getArgValue(args, '--source-root') || learnLint.resolveLearningsRoot();
|
|
1616
|
+
const citesCsv = getArgValue(args, '--cites') || '';
|
|
1617
|
+
const cites = citesCsv ? citesCsv.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
1542
1618
|
|
|
1543
1619
|
const result = optimize.promotePattern(
|
|
1544
|
-
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments },
|
|
1620
|
+
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments, ...(cites.length ? { cites } : {}) },
|
|
1545
1621
|
{ scope, topic, sourceRoot }
|
|
1546
1622
|
);
|
|
1547
1623
|
output(result, raw);
|
|
@@ -1589,9 +1665,13 @@ async function main() {
|
|
|
1589
1665
|
if (!agent) { error('learn topics-for requires --agent <name>'); }
|
|
1590
1666
|
const minRelevance = getArgValue(args, '--min-relevance', 'medium');
|
|
1591
1667
|
const tokenBudget = parseInt(getArgValue(args, '--token-budget', '5000'), 10);
|
|
1592
|
-
const
|
|
1668
|
+
const cue = getArgValue(args, '--cue') || undefined;
|
|
1669
|
+
const result = learnIndex.cmdTopicsFor(sourceRoot, { agent, minRelevance, tokenBudget, cue });
|
|
1593
1670
|
if (raw) {
|
|
1594
|
-
const
|
|
1671
|
+
const head = result.mode === 'cue'
|
|
1672
|
+
? `Topics for "${agent}" matching the cue (budget ${tokenBudget}):`
|
|
1673
|
+
: `Topics for "${agent}" (min ${minRelevance}, budget ${tokenBudget})${cue ? ' — nothing matched the cue, so by role' : ''}:`;
|
|
1674
|
+
const lines = [head, ``];
|
|
1595
1675
|
for (const t of result.selected) {
|
|
1596
1676
|
lines.push(` [${t.relevance.padEnd(6)}] ${t.scope}/${t.name.padEnd(22)} ${t.tokens.toString().padStart(5)}t ${t.patterns.join(', ')}`);
|
|
1597
1677
|
}
|
|
@@ -1646,18 +1726,24 @@ async function main() {
|
|
|
1646
1726
|
// and to the suggestion index that is now parsed from those strings.
|
|
1647
1727
|
// Publishing it fixes the error AND feeds the suggester, with no second
|
|
1648
1728
|
// list to maintain.
|
|
1649
|
-
|
|
1729
|
+
//
|
|
1730
|
+
// A flag is not a subcommand: `learn --session <id>` and `learn --sessions N`
|
|
1731
|
+
// take the alias with `optimize learn`'s flags (the guard used to refuse both).
|
|
1732
|
+
if (subcommand && !subcommand.startsWith('--')) {
|
|
1650
1733
|
error('Unknown learn subcommand. Available: promote, unpromote, list-promoted, build-index, topics-for, lint');
|
|
1651
1734
|
}
|
|
1652
1735
|
optimize.cmdOptimizeLearn(cwd, {
|
|
1653
1736
|
sessionId: getArgValue(args, '--session'),
|
|
1737
|
+
sessions: getArgValue(args, '--sessions'),
|
|
1654
1738
|
}, raw);
|
|
1655
1739
|
break;
|
|
1656
1740
|
}
|
|
1657
1741
|
|
|
1658
1742
|
case 'links': {
|
|
1659
1743
|
const subcommand = args[1];
|
|
1660
|
-
|
|
1744
|
+
// `validate` is the default, flags and all: `links --strict` used to be refused
|
|
1745
|
+
// as an unknown subcommand while bare `links` validated.
|
|
1746
|
+
if (subcommand === 'validate' || !subcommand || subcommand.startsWith('--')) {
|
|
1661
1747
|
const collectMulti = (flag) => {
|
|
1662
1748
|
const vals = [];
|
|
1663
1749
|
for (let i = 0; i < args.length; i++) {
|