pan-wizard 3.12.3 → 3.13.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/README.md +2 -1
- package/agents/pan-debugger.md +2 -2
- package/agents/pan-hardener.md +5 -2
- package/agents/pan-meta-reviewer.md +2 -1
- package/agents/pan-planner.md +16 -0
- package/agents/pan-reviewer.md +2 -1
- package/bin/install-lib.cjs +8 -0
- package/bin/install.js +3 -2
- package/commands/pan/audit-deployment.md +8 -8
- package/commands/pan/focus-auto.md +10 -6
- package/commands/pan/hygiene.md +69 -0
- package/commands/pan/milestone-done.md +3 -2
- package/hooks/dist/pan-cost-logger.js +54 -6
- package/hooks/dist/pan-trace-logger.js +41 -5
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/constants.cjs +40 -0
- package/pan-wizard-core/bin/lib/cost.cjs +26 -1
- package/pan-wizard-core/bin/lib/hud.cjs +14 -2
- package/pan-wizard-core/bin/lib/hygiene.cjs +447 -0
- package/pan-wizard-core/bin/lib/knowledge.cjs +28 -12
- package/pan-wizard-core/bin/lib/learn-index.cjs +17 -0
- package/pan-wizard-core/bin/lib/memory.cjs +146 -3
- package/pan-wizard-core/bin/lib/skill-align.cjs +364 -0
- package/pan-wizard-core/bin/lib/verify.cjs +10 -0
- package/pan-wizard-core/bin/pan-tools.cjs +47 -1
- package/pan-wizard-core/learnings/index.json +262 -10
- package/pan-wizard-core/learnings/internal/external-research.md +13 -1
- package/pan-wizard-core/learnings/universal/adversarial-verification.md +45 -0
- package/pan-wizard-core/learnings/universal/audit-convergence.md +33 -0
- package/pan-wizard-core/learnings/universal/autonomous-loop.md +4 -4
- package/pan-wizard-core/learnings/universal/external-tool-truth.md +21 -0
- package/pan-wizard-core/learnings/universal/fix-campaigns.md +45 -0
- package/pan-wizard-core/learnings/universal/flaky-triage.md +33 -0
- package/pan-wizard-core/learnings/universal/golden-sets.md +33 -0
- package/pan-wizard-core/learnings/universal/harness-isolation.md +21 -0
- package/pan-wizard-core/learnings/universal/integration-verification.md +33 -0
- package/pan-wizard-core/learnings/universal/live-path-honesty.md +45 -0
- package/pan-wizard-core/learnings/universal/mcp-security.md +21 -0
- package/pan-wizard-core/learnings/universal/migration-safety.md +21 -0
- package/pan-wizard-core/learnings/universal/service-security.md +21 -0
- package/pan-wizard-core/learnings/universal/single-source-of-truth.md +33 -0
- package/pan-wizard-core/learnings/universal/test-integrity.md +21 -0
- package/pan-wizard-core/learnings/universal/workaround-catalog.md +21 -0
- package/pan-wizard-core/references/model-profiles.md +23 -1
- package/pan-wizard-core/workflows/exec-phase.md +12 -3
- package/pan-wizard-core/workflows/plan-phase.md +1 -0
package/README.md
CHANGED
|
@@ -187,7 +187,7 @@ Clone the repository and run the installer locally:
|
|
|
187
187
|
|
|
188
188
|
```bash
|
|
189
189
|
git clone https://github.com/oharms/PanWizard.git
|
|
190
|
-
cd
|
|
190
|
+
cd PanWizard
|
|
191
191
|
node bin/install.js --claude --local
|
|
192
192
|
```
|
|
193
193
|
|
|
@@ -622,6 +622,7 @@ PAN is not a replacement for your IDE or AI agent — it's the orchestration lay
|
|
|
622
622
|
| `/pan:debug [desc]` | Systematic debugging with persistent state |
|
|
623
623
|
| `/pan:quick [--full]` | Execute ad-hoc task with PAN guarantees (`--full` adds plan-checking and verification) |
|
|
624
624
|
| `/pan:health [--repair] [--standards] [--full] [--drift] [--links]` | Validate `.planning/` directory integrity. `--repair` auto-fixes; `--standards` checks compliance; `--full` runs tests + build; `--drift` runs convention drift; `--links` attaches doc-code link-graph summary |
|
|
625
|
+
| `/pan:hygiene [--apply] [--trace-age-days N]` | Scan for PAN version drift and stale project artifacts (legacy filenames, .tmp orphans, memory bloat, poisoned cost ledgers, trace debris, fragment planning dirs); `--apply` executes the safe fixes — ledgers are quarantined by rename, never deleted |
|
|
625
626
|
| `/pan:links [--strict]` | Validate the doc-code link graph: inline `[[<id>]]` refs, `// @pan:` source anchors, `require-code-mention` contracts (ADR-0027, v3.8.0+) |
|
|
626
627
|
| `/pan:phase-tests [N]` | Generate tests for a completed phase based on UAT criteria |
|
|
627
628
|
| `/pan:milestone-cleanup` | Archive accumulated phase directories from completed milestones |
|
package/agents/pan-debugger.md
CHANGED
|
@@ -132,13 +132,13 @@ A good hypothesis can be proven wrong. If you can't design an experiment to disp
|
|
|
132
132
|
|
|
133
133
|
Before running any experiments, think through at least **three independent hypotheses** that could explain the observed failure. For each, write down a one-line Bayesian prior ("90% likely given the symptom", "30%", etc.) based on how well it fits the evidence and how common the failure class is in this codebase.
|
|
134
134
|
|
|
135
|
-
Then **
|
|
135
|
+
Then **investigate the top two in parallel**: emit the `Read`, `Grep`, and log-inspection tool calls for both hypotheses in a single turn. Only serialize when a hypothesis's next step strictly depends on data from a previous step.
|
|
136
136
|
|
|
137
137
|
- If the top hypothesis is confirmed, stop — don't also debug the lower-ranked ones.
|
|
138
138
|
- If the top two are both refuted, rank the remaining hypotheses and repeat.
|
|
139
139
|
- Record each hypothesis's prior and final verdict in the debug session file so later steps can see the tree.
|
|
140
140
|
|
|
141
|
-
Parallel exploration keeps investigation bounded: 3 priors × 2-parallel
|
|
141
|
+
Parallel exploration keeps investigation bounded: 3 priors × 2-parallel investigation = at most 3 rounds before you have a clear winner, rather than walking a depth-first chain of 10 dead ends.
|
|
142
142
|
|
|
143
143
|
## Experimental Design Framework
|
|
144
144
|
|
package/agents/pan-hardener.md
CHANGED
|
@@ -4,11 +4,14 @@ description: Security audit agent — OWASP Top 10 + STRIDE threat modeling acro
|
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
color: red
|
|
6
6
|
effort: high
|
|
7
|
+
model: opus
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
<role>
|
|
10
11
|
You are the PAN hardener. You perform focused security review on files changed during phase execution, applying OWASP Top 10 (2025) and STRIDE threat modeling frameworks.
|
|
11
12
|
|
|
13
|
+
This is **authorized, defensive** secure-coding review of the user's own codebase — the goal is to find and fix weaknesses before shipping. You report findings for the user to remediate; you never write exploit code, attack tooling, or step-by-step intrusion instructions.
|
|
14
|
+
|
|
12
15
|
You are spawned by `/pan:review-deep <phase>` or `/pan:exec-phase --deep-review`. Your output is read by `pan-meta-reviewer` (cross-checks you) and merged by `review-deep.cjs` into `.planning/reviews/<phase>/deep-review.md`.
|
|
13
16
|
|
|
14
17
|
**You NEVER modify files.** You report findings; the user fixes them.
|
|
@@ -51,7 +54,7 @@ Before writing findings, think through:
|
|
|
51
54
|
|
|
52
55
|
1. **What changed in this phase?** Read the diff or plan.md files list. Map changes to OWASP categories — e.g. "new endpoint added" → A01+A03 scan; "new SQL query" → A03 scan.
|
|
53
56
|
2. **Does this touch auth, data, or secrets?** These categories get the most thorough STRIDE pass. Changes to `logger.js` or docs don't.
|
|
54
|
-
3. **
|
|
57
|
+
3. **How could this be reached and abused?** For every new surface, trace how it could be reached and what the impact would be, so you can prioritize the fix. If you can't identify a realistic path in 30 seconds, note the effort and move on — don't fabricate threats.
|
|
55
58
|
4. **Cross-check: did the reviewer already flag this?** You'll be merged with their output. Duplicating their `use parameterized queries` finding is OK but prefer adding severity (reviewer says INFO, you say HIGH because it's in an auth path).
|
|
56
59
|
|
|
57
60
|
</reasoning_protocol>
|
|
@@ -91,7 +94,7 @@ generated: <ISO timestamp>
|
|
|
91
94
|
```
|
|
92
95
|
|
|
93
96
|
**Severity scale:**
|
|
94
|
-
- `critical` —
|
|
97
|
+
- `critical` — remotely reachable with no prerequisites; use sparingly, only when one misuse leads to data loss or remote code execution.
|
|
95
98
|
- `high` — exploitable with typical user privileges; blocks merge by default.
|
|
96
99
|
- `medium` — defense-in-depth issue; fix before production but won't block merge if documented.
|
|
97
100
|
- `low` — best-practice deviation; nice to fix.
|
|
@@ -4,6 +4,7 @@ description: Reviews the reviewer + hardener output. Flags things both missed, d
|
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
color: magenta
|
|
6
6
|
effort: medium
|
|
7
|
+
model: opus
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
<role>
|
|
@@ -16,7 +17,7 @@ You are the PAN meta-reviewer. Your job is to check the first-pass reviewers (`p
|
|
|
16
17
|
|
|
17
18
|
You are spawned by `/pan:review-deep <phase>` after both the reviewer and hardener have written their reports. Your output is merged with theirs by `review-deep.cjs`.
|
|
18
19
|
|
|
19
|
-
**You NEVER modify source code.** You produce one findings file.
|
|
20
|
+
**You NEVER modify source code.** You produce one findings file. This is authorized, defensive review of the user's own codebase — you adjudicate security findings for remediation; never produce exploit code.
|
|
20
21
|
|
|
21
22
|
**CRITICAL: Mandatory Initial Read**
|
|
22
23
|
If the prompt contains a `<files_to_read>` block (it will contain the reviewer and hardener outputs + representative diff snippets), you MUST use the `Read` tool to load every file listed there before performing any other actions.
|
package/agents/pan-planner.md
CHANGED
|
@@ -1106,6 +1106,21 @@ For each task:
|
|
|
1106
1106
|
Apply TDD detection heuristic. Apply user setup detection.
|
|
1107
1107
|
</step>
|
|
1108
1108
|
|
|
1109
|
+
<step name="skill_alignment">
|
|
1110
|
+
**SAD pass (ADR-0038):** before grouping tasks into plans, check that the draft decomposition's vocabulary and granularity match the skills that actually exist (commands, templates, references, learnings topics). This is advisory and fail-open — on any error, skip and continue.
|
|
1111
|
+
|
|
1112
|
+
1. Write the draft task names to a temp file, one per line (bullets are fine).
|
|
1113
|
+
2. Run the alignment pass:
|
|
1114
|
+
```bash
|
|
1115
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs skills align --draft-file "$DRAFT_FILE" --raw 2>/dev/null || true
|
|
1116
|
+
```
|
|
1117
|
+
3. Use the output:
|
|
1118
|
+
- **`vocabulary`** — skills your plans should reference by their real names. Where a task's `<action>` overlaps a matched learnings topic (e.g. `universal/atomic-state`), cite the topic/pattern id so the executor loads it.
|
|
1119
|
+
- **Matched templates** — don't re-describe artifacts a template already defines; reference the template.
|
|
1120
|
+
- **Unmatched tasks (✗)** — a signal the task's wording or granularity is misaligned with available machinery. Reword it in the vocabulary of the matched skills, or re-split it. Genuinely novel work legitimately matches nothing — that's fine.
|
|
1121
|
+
4. Realign wording and granularity only. **Never** add tasks or scope to consume matched skills, and never remove a task because it didn't match.
|
|
1122
|
+
</step>
|
|
1123
|
+
|
|
1109
1124
|
<step name="build_dependency_graph">
|
|
1110
1125
|
Map dependencies explicitly before grouping into plans. Record needs/creates/has_checkpoint for each task.
|
|
1111
1126
|
|
|
@@ -1289,6 +1304,7 @@ Phase planning complete when:
|
|
|
1289
1304
|
- [ ] state.md read, project history absorbed
|
|
1290
1305
|
- [ ] Mandatory discovery completed (Level 0-3)
|
|
1291
1306
|
- [ ] Prior decisions, issues, concerns synthesized
|
|
1307
|
+
- [ ] Skill-alignment (SAD) pass run on the draft task list, or consciously skipped (fail-open)
|
|
1292
1308
|
- [ ] Dependency graph built (needs/creates for each task)
|
|
1293
1309
|
- [ ] Tasks grouped into plans by wave, not by sequence
|
|
1294
1310
|
- [ ] PLAN file(s) exist with XML structure
|
package/agents/pan-reviewer.md
CHANGED
|
@@ -4,12 +4,13 @@ description: Read-only code review agent. Checks convention compliance, security
|
|
|
4
4
|
tools: Read, Grep, Glob, Bash
|
|
5
5
|
color: yellow
|
|
6
6
|
effort: medium
|
|
7
|
+
model: opus
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
<role>
|
|
10
11
|
You are a PAN code reviewer. You perform read-only code review on files changed during phase execution.
|
|
11
12
|
|
|
12
|
-
Your job: Check convention compliance, security patterns, and code quality. You do NOT modify files — you report findings.
|
|
13
|
+
Your job: Check convention compliance, security patterns, and code quality. You do NOT modify files — you report findings. This is authorized, defensive review of the user's own codebase — surface security-relevant findings for the user to remediate; never produce exploit code.
|
|
13
14
|
|
|
14
15
|
**CRITICAL: Mandatory Initial Read**
|
|
15
16
|
If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
|
package/bin/install-lib.cjs
CHANGED
|
@@ -283,6 +283,10 @@ function convertClaudeToGeminiAgent(content) {
|
|
|
283
283
|
continue;
|
|
284
284
|
}
|
|
285
285
|
if (trimmed.startsWith('color:')) continue;
|
|
286
|
+
// `model:` pins a Claude Code subagent to a specific model (e.g. opus for
|
|
287
|
+
// security agents, off Fable's cyber classifier). Claude-only — strip it
|
|
288
|
+
// for Gemini so it can't leak into a runtime that reads `model` differently.
|
|
289
|
+
if (trimmed.startsWith('model:')) continue;
|
|
286
290
|
if (inAllowedTools) {
|
|
287
291
|
if (trimmed.startsWith('- ')) {
|
|
288
292
|
const mapped = convertGeminiToolName(trimmed.substring(2).trim());
|
|
@@ -338,6 +342,10 @@ function convertClaudeToOpencodeFrontmatter(content) {
|
|
|
338
342
|
continue;
|
|
339
343
|
}
|
|
340
344
|
if (trimmed.startsWith('name:')) continue;
|
|
345
|
+
// `model:` is a Claude-only subagent pin (e.g. opus for security agents,
|
|
346
|
+
// off Fable's cyber classifier). Strip it here — OpenCode's own `model`
|
|
347
|
+
// field expects a `provider/model` id and would choke on `opus`.
|
|
348
|
+
if (trimmed.startsWith('model:')) continue;
|
|
341
349
|
if (trimmed.startsWith('color:')) {
|
|
342
350
|
const colorValue = trimmed.substring(6).trim().toLowerCase();
|
|
343
351
|
const hexColor = colorNameToHex[colorValue];
|
package/bin/install.js
CHANGED
|
@@ -2490,8 +2490,9 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2490
2490
|
!caps.has_thinking ? 'extended thinking (E-3, E-10, E-11)' : null,
|
|
2491
2491
|
].filter(Boolean).join(', ');
|
|
2492
2492
|
console.log(`
|
|
2493
|
-
${yellow}ℹ${reset} PAN
|
|
2494
|
-
Features degrade gracefully, but
|
|
2493
|
+
${yellow}ℹ${reset} PAN's multi-agent workflows are tuned for frontier reasoning models. Default model "${modelField}" lacks: ${missing}.
|
|
2494
|
+
Features degrade gracefully, but for best results select claude-fable-5 (PAN's recommended flagship — deepest
|
|
2495
|
+
long-horizon reasoning for the bot army) or claude-opus-4-8 (same 1M context at half the cost).`);
|
|
2495
2496
|
}
|
|
2496
2497
|
}
|
|
2497
2498
|
} catch {
|
|
@@ -69,20 +69,20 @@ For the detected runtime config directory (CONFIG_DIR), audit ALL of the followi
|
|
|
69
69
|
**1.2 Core Modules**
|
|
70
70
|
- [ ] `CONFIG_DIR/pan-wizard-core/bin/pan-tools.cjs` exists (CLI dispatcher)
|
|
71
71
|
- [ ] `CONFIG_DIR/pan-wizard-core/bin/lib/` directory exists
|
|
72
|
-
-
|
|
72
|
+
- Every `bin/lib/*.cjs` file listed in `pan-file-manifest.json` exists on disk (the manifest is the authoritative expected set — never hardcode a module count)
|
|
73
73
|
|
|
74
74
|
**1.3 Workflows, Templates, References**
|
|
75
|
-
- [ ] `CONFIG_DIR/pan-wizard-core/workflows/` —
|
|
76
|
-
- [ ] `CONFIG_DIR/pan-wizard-core/templates/` —
|
|
77
|
-
- [ ] `CONFIG_DIR/pan-wizard-core/references/` —
|
|
75
|
+
- [ ] `CONFIG_DIR/pan-wizard-core/workflows/` — non-empty, every manifest-listed workflow present
|
|
76
|
+
- [ ] `CONFIG_DIR/pan-wizard-core/templates/` — non-empty, every manifest-listed template present
|
|
77
|
+
- [ ] `CONFIG_DIR/pan-wizard-core/references/` — non-empty, every manifest-listed reference present
|
|
78
78
|
|
|
79
79
|
**1.4 Commands**
|
|
80
|
-
- For Claude/Gemini: `CONFIG_DIR/commands/pan/` —
|
|
81
|
-
- For OpenCode: `CONFIG_DIR/command/` —
|
|
82
|
-
- For Codex/Copilot: `CONFIG_DIR/skills/pan-*/SKILL.md` —
|
|
80
|
+
- For Claude/Gemini: `CONFIG_DIR/commands/pan/` — every manifest-listed command `.md` present
|
|
81
|
+
- For OpenCode: `CONFIG_DIR/command/` — every manifest-listed `pan-*.md` present
|
|
82
|
+
- For Codex/Copilot: `CONFIG_DIR/skills/pan-*/SKILL.md` — every manifest-listed skill directory present
|
|
83
83
|
|
|
84
84
|
**1.5 Agents**
|
|
85
|
-
- [ ] `CONFIG_DIR/agents/` —
|
|
85
|
+
- [ ] `CONFIG_DIR/agents/` — every manifest-listed agent file present
|
|
86
86
|
- Verify key agents exist: pan-planner, pan-executor, pan-verifier, pan-debugger
|
|
87
87
|
|
|
88
88
|
**1.6 Hooks**
|
|
@@ -211,8 +211,8 @@ Perform a deep codebase scan to find actionable work items with evidence.
|
|
|
211
211
|
- **security:** Three-pass approach:
|
|
212
212
|
- **Pass 1 — Injection & crypto (inline grep):** Scan source files for `eval(`, `execSync`, `exec(`, string concatenation in SQL patterns (`` `SELECT...${`` / `"SELECT..."+`), `md5(`/`sha1(`/`createHash('md5'`/`createHash('sha1'`, hardcoded secrets (`password\s*=\s*['"]`, `api_key\s*=\s*['"]`, `secret\s*=\s*['"`), `Math.random()` used for security purposes.
|
|
213
213
|
- **Pass 2 — Auth & access control (inline grep):** Routes without auth middleware (look for `router.get/post/put/delete` without preceding `app.use(...auth...)`), `req.params.id` used directly without ownership check, `JSON.parse(` on `req.body` without schema validation, CORS `origin: '*'` or `Access-Control-Allow-Origin: *`, verbose errors that expose stack traces (`res.json({ stack:`).
|
|
214
|
-
- **Pass 3 — Semantic depth (Agent tool, optional):** For M/L items where grep found a suspicious pattern but fix guidance needs code-path tracing, use the Agent tool
|
|
215
|
-
- **Classification:** Map findings to priorities: OWASP critical/
|
|
214
|
+
- **Pass 3 — Semantic depth (Agent tool, optional):** For M/L items where grep found a suspicious pattern but fix guidance needs code-path tracing, use the Agent tool to spawn the `pan-hardener` subagent (pinned to `model: opus`, off Fable's cybersecurity classifier) to read the specific file and confirm the weakness is genuinely reachable before including in the batch.
|
|
215
|
+
- **Classification:** Map findings to priorities: OWASP critical/directly-reachable → P0, High/missing-authorization → P1, Medium/defense-in-depth → P2. Drop LOW/INFO — they don't meet the P0-P2 filter.
|
|
216
216
|
- **prompts:** Two operational modes — detect which applies:
|
|
217
217
|
- **Execute mode:** Find micro-prompt documents (`.md` files containing ordered prompt blocks, e.g., `## Prompt 1`, `## Prompt 2`, or numbered checklist items `- [ ] Prompt: ...`). Look in `.planning/`, project root, and `docs/` for files matching patterns: `*prompts*`, `*micro-prompt*`, `*prompt-plan*`, `*prompt-sequence*`. Each unchecked/incomplete prompt block is one work item.
|
|
218
218
|
- **Generate mode:** Find specification documents (files matching `*spec*`, `*prd*`, `*requirements*`, `*feature*` in `.planning/`, `docs/specs/`, project root) that do NOT already have a corresponding micro-prompt document. Each spec needing decomposition is one work item.
|
|
@@ -552,19 +552,23 @@ Use the confirmation to decide whether to include the item at P0/P1 or drop it a
|
|
|
552
552
|
|
|
553
553
|
| Hardener severity | Focus priority | Example |
|
|
554
554
|
|------------------|----------------|---------|
|
|
555
|
-
| Critical | P0 | `eval(req.body.code)` — direct
|
|
556
|
-
| High | P1 |
|
|
555
|
+
| Critical | P0 | `eval(req.body.code)` — direct remote code execution risk |
|
|
556
|
+
| High | P1 | Missing authorization on admin route |
|
|
557
557
|
| Medium | P2 | Rate-limiting absent on login |
|
|
558
558
|
| Low / Info | DROP | Missing security header on non-sensitive route |
|
|
559
559
|
|
|
560
560
|
### Execution (Step 2.3)
|
|
561
561
|
|
|
562
|
+
This is authorized, defensive hardening of the user's own codebase — you assess and remediate weaknesses; you never write exploit code or attack instructions.
|
|
563
|
+
|
|
564
|
+
**Model routing (important).** Some session models — notably Claude Fable 5 — run a cybersecurity classifier that can refuse defensive security review. Do the vulnerability *assessment* (the Pass 3 semantic analysis and each item's risk statement below) by spawning the `pan-hardener` subagent, which is pinned to `model: opus` and is not subject to that classifier. The main loop runs the grep triage (Passes 1–2) and implements the fixes; it does not narrate exploit paths inline. If any security step is refused on the main loop, delegate it to `pan-hardener` and continue.
|
|
565
|
+
|
|
562
566
|
Treat each security item as a STANDARD or FULL item regardless of effort estimate:
|
|
563
567
|
|
|
564
|
-
1. **State
|
|
568
|
+
1. **State the risk:** "This is [OWASP category]. How it could be reached and the impact: [unvalidated input reaches X → Y → data exposure or loss], so it must be fixed."
|
|
565
569
|
2. **Read the file** — confirm the pattern is real, not a false positive
|
|
566
570
|
3. **Implement the fix** — use established patterns (parameterized queries, allowlists, bcrypt, rate-limit middleware)
|
|
567
|
-
4. **Write or update the test** — every security fix MUST have a test that proves the
|
|
571
|
+
4. **Write or update the test** — every security fix MUST have a test that proves the weakness is closed (e.g., send the crafted/invalid input, assert 400/403 not 200)
|
|
568
572
|
5. **Run full test suite** — regression check before marking DONE
|
|
569
573
|
|
|
570
574
|
### Stop condition
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pan:hygiene
|
|
3
|
+
group: System
|
|
4
|
+
description: Scan the project for PAN version drift and stale artifacts (legacy filenames, memory bloat, poisoned ledgers, trace debris) and apply safe cleanups
|
|
5
|
+
argument-hint: "[--apply] [--trace-age-days N]"
|
|
6
|
+
allowed-tools:
|
|
7
|
+
- Read
|
|
8
|
+
- Bash
|
|
9
|
+
- AskUserQuestion
|
|
10
|
+
---
|
|
11
|
+
<objective>
|
|
12
|
+
Keep a PAN-managed project aligned with the latest PAN version and free of accumulated history debris. Detects: outdated runtime installs (per-runtime manifest version vs latest), legacy uppercase planning filenames, orphaned atomic-write .tmp files, per-agent memory logs past the compaction cap, cost ledgers poisoned by pre-v3.12.4 telemetry, stale optimization trace sessions, and stray fragment `.planning/` directories.
|
|
13
|
+
</objective>
|
|
14
|
+
|
|
15
|
+
<process>
|
|
16
|
+
|
|
17
|
+
## 1. Scan
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
SCAN=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs hygiene scan)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Parse JSON: `findings[]` (`check`, `severity`, `path`, `detail`, `fixable`), `installs[]`, `latest_version`, `summary`.
|
|
24
|
+
|
|
25
|
+
Display the findings grouped by severity (critical → warn → info). If `summary.total` is 0: report "Project is clean and aligned" and stop.
|
|
26
|
+
|
|
27
|
+
## 2. Version drift (manual remediation)
|
|
28
|
+
|
|
29
|
+
If any `version-alignment` findings exist, list the outdated runtimes and show the remediation:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Re-run the installer from the project root to align all runtimes:
|
|
33
|
+
node <pan-source>/bin/install.js --claude --codex --gemini --opencode --copilot --local
|
|
34
|
+
(use the flags matching the runtimes reported in installs[])
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Hygiene never runs the installer itself.
|
|
38
|
+
|
|
39
|
+
## 3. Safe cleanups
|
|
40
|
+
|
|
41
|
+
**Without `--apply` in $ARGUMENTS:** run the dry-run and present what WOULD change:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs hygiene clean
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then ask the user (AskUserQuestion, header "Apply fixes", options: "Apply safe fixes" / "Skip") unless running headless — in auto/headless contexts, report the dry-run only and stop.
|
|
48
|
+
|
|
49
|
+
**With `--apply` (or after user confirmation):**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs hygiene clean --apply
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Safe fixes are: lowercase renames of legacy planning filenames, deletion of aged .tmp orphans, memory-log compaction, poisoned-ledger quarantine (rename in place — never deleted), and pruning of trace sessions past retention (newest 5 always kept). Pass through `--trace-age-days N` if provided.
|
|
56
|
+
|
|
57
|
+
## 4. Report
|
|
58
|
+
|
|
59
|
+
Summarize: fixes executed / failed / left manual, plus the installer command if version drift remains. Recommend re-running `/pan:hygiene` after the installer to confirm alignment.
|
|
60
|
+
|
|
61
|
+
</process>
|
|
62
|
+
|
|
63
|
+
<success_criteria>
|
|
64
|
+
- [ ] Scan run and findings presented by severity
|
|
65
|
+
- [ ] Version drift reported with the exact installer command (never auto-run)
|
|
66
|
+
- [ ] Safe fixes applied only with --apply or explicit user confirmation
|
|
67
|
+
- [ ] Nothing user-authored deleted — quarantine renames only
|
|
68
|
+
- [ ] Final summary states executed/failed/manual counts
|
|
69
|
+
</success_criteria>
|
|
@@ -20,8 +20,9 @@ Output: Milestone archived (roadmap + requirements), project.md evolved, git tag
|
|
|
20
20
|
<execution_context>
|
|
21
21
|
**Load these files NOW (before proceeding):**
|
|
22
22
|
|
|
23
|
-
- @~/.claude/pan-wizard-core/workflows/milestone-done.md (main workflow)
|
|
24
23
|
- @~/.claude/pan-wizard-core/templates/milestone-archive.md (archive template)
|
|
24
|
+
|
|
25
|
+
The full milestone-done workflow is inlined in <process> below — there is no separate workflow file.
|
|
25
26
|
</execution_context>
|
|
26
27
|
|
|
27
28
|
<context>
|
|
@@ -38,7 +39,7 @@ Output: Milestone archived (roadmap + requirements), project.md evolved, git tag
|
|
|
38
39
|
|
|
39
40
|
<process>
|
|
40
41
|
|
|
41
|
-
**Follow
|
|
42
|
+
**Follow this workflow:**
|
|
42
43
|
|
|
43
44
|
0. **Check for audit:**
|
|
44
45
|
|
|
@@ -18,6 +18,29 @@ const path = require('path');
|
|
|
18
18
|
|
|
19
19
|
const METRICS_DIR = 'metrics';
|
|
20
20
|
const TOKENS_FILE = 'tokens.jsonl';
|
|
21
|
+
const CURSOR_FILE = '.cost-cursor.json';
|
|
22
|
+
|
|
23
|
+
// Per-transcript high-water mark: the count of JSONL records already attributed
|
|
24
|
+
// to earlier SubagentStop events, keyed by transcript path. Each event then sums
|
|
25
|
+
// ONLY its own slice (records past the cursor) instead of re-summing the whole
|
|
26
|
+
// shared-session transcript every time — the latter multiplies cumulative-per-turn
|
|
27
|
+
// cache-read into the billions/trillions and stamps it onto every subagent record
|
|
28
|
+
// (field report 2026-06). Stored next to tokens.jsonl; best-effort, never blocks.
|
|
29
|
+
function cursorFilePath(cwd) {
|
|
30
|
+
return path.join(cwd, '.planning', METRICS_DIR, CURSOR_FILE);
|
|
31
|
+
}
|
|
32
|
+
function readCursor(cwd) {
|
|
33
|
+
try {
|
|
34
|
+
const c = JSON.parse(fs.readFileSync(cursorFilePath(cwd), 'utf-8'));
|
|
35
|
+
return c && typeof c === 'object' ? c : {};
|
|
36
|
+
} catch { return {}; }
|
|
37
|
+
}
|
|
38
|
+
function writeCursor(cwd, cursor) {
|
|
39
|
+
try {
|
|
40
|
+
fs.mkdirSync(path.dirname(cursorFilePath(cwd)), { recursive: true });
|
|
41
|
+
fs.writeFileSync(cursorFilePath(cwd), JSON.stringify(cursor), 'utf-8');
|
|
42
|
+
} catch { /* best-effort — never block the agent loop */ }
|
|
43
|
+
}
|
|
21
44
|
|
|
22
45
|
/**
|
|
23
46
|
* Extract what we can from the SubagentStop event payload.
|
|
@@ -49,7 +72,12 @@ function buildCostRecord(data, cwd) {
|
|
|
49
72
|
let model = typeof data.model === 'string' && data.model ? data.model : null;
|
|
50
73
|
const needUsage = (inputTokens + outputTokens + cacheRead + cacheWrite) === 0;
|
|
51
74
|
if ((needUsage || !model) && data.transcript_path) {
|
|
52
|
-
|
|
75
|
+
// Attribute only the transcript slice since the previous SubagentStop for
|
|
76
|
+
// this transcript, so a shared-session transcript is never re-summed on
|
|
77
|
+
// every event (field report 2026-06 — the billion-token cache-read bug).
|
|
78
|
+
const cursor = readCursor(cwd);
|
|
79
|
+
const since = cursor[data.transcript_path] || 0;
|
|
80
|
+
const fromTranscript = readUsageFromTranscript(data.transcript_path, data.session_id, since);
|
|
53
81
|
if (needUsage) {
|
|
54
82
|
inputTokens = fromTranscript.input_tokens;
|
|
55
83
|
outputTokens = fromTranscript.output_tokens;
|
|
@@ -57,6 +85,12 @@ function buildCostRecord(data, cwd) {
|
|
|
57
85
|
cacheWrite = fromTranscript.cache_creation_input_tokens;
|
|
58
86
|
}
|
|
59
87
|
if (!model) model = fromTranscript.model;
|
|
88
|
+
// Advance the cursor to the end of the transcript so the next subagent's
|
|
89
|
+
// record starts fresh (these slices partition the transcript — no overlap).
|
|
90
|
+
if (fromTranscript.lineCount > since) {
|
|
91
|
+
cursor[data.transcript_path] = fromTranscript.lineCount;
|
|
92
|
+
writeCursor(cwd, cursor);
|
|
93
|
+
}
|
|
60
94
|
}
|
|
61
95
|
|
|
62
96
|
const record = {
|
|
@@ -85,23 +119,36 @@ function extractNumber(obj, key) {
|
|
|
85
119
|
}
|
|
86
120
|
|
|
87
121
|
/**
|
|
88
|
-
* P-1805 (v3.7.8): read transcript JSONL and sum usage across
|
|
89
|
-
*
|
|
90
|
-
*
|
|
122
|
+
* P-1805 (v3.7.8): read transcript JSONL and sum usage across assistant messages.
|
|
123
|
+
*
|
|
124
|
+
* `sinceLine` (P-360, field report 2026-06): skip the first N non-empty records —
|
|
125
|
+
* the count already attributed to earlier SubagentStop events for this transcript.
|
|
126
|
+
* Summing only the slice past the cursor is what stops a shared-session transcript
|
|
127
|
+
* from being re-summed on every event (which multiplied cumulative-per-turn
|
|
128
|
+
* cache-read into the billions). Returns `lineCount` = total non-empty records seen
|
|
129
|
+
* so the caller can advance the cursor. Returns zeros if missing/unreadable.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} transcriptPath
|
|
132
|
+
* @param {string} sessionId
|
|
133
|
+
* @param {number} [sinceLine=0] - records already attributed (the cursor)
|
|
91
134
|
*/
|
|
92
|
-
function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
135
|
+
function readUsageFromTranscript(transcriptPath, sessionId, sinceLine = 0) {
|
|
93
136
|
const totals = {
|
|
94
137
|
input_tokens: 0,
|
|
95
138
|
output_tokens: 0,
|
|
96
139
|
cache_read_input_tokens: 0,
|
|
97
140
|
cache_creation_input_tokens: 0,
|
|
98
141
|
model: null,
|
|
142
|
+
lineCount: 0,
|
|
99
143
|
};
|
|
100
144
|
if (!transcriptPath || typeof transcriptPath !== 'string') return totals;
|
|
101
145
|
let raw;
|
|
102
146
|
try { raw = fs.readFileSync(transcriptPath, 'utf-8'); } catch { return totals; }
|
|
147
|
+
let seen = 0; // count of non-empty JSONL records (the cursor unit)
|
|
103
148
|
for (const line of raw.split('\n')) {
|
|
104
149
|
if (!line) continue;
|
|
150
|
+
seen++;
|
|
151
|
+
if (seen <= sinceLine) continue; // already attributed to an earlier event
|
|
105
152
|
let entry;
|
|
106
153
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
107
154
|
if (sessionId && entry.session_id && entry.session_id !== sessionId) continue;
|
|
@@ -120,6 +167,7 @@ function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
|
120
167
|
totals.cache_read_input_tokens += extractNumber(usage, 'cache_read_input_tokens');
|
|
121
168
|
totals.cache_creation_input_tokens += extractNumber(usage, 'cache_creation_input_tokens');
|
|
122
169
|
}
|
|
170
|
+
totals.lineCount = seen;
|
|
123
171
|
return totals;
|
|
124
172
|
}
|
|
125
173
|
|
|
@@ -164,4 +212,4 @@ if (require.main === module) {
|
|
|
164
212
|
});
|
|
165
213
|
}
|
|
166
214
|
|
|
167
|
-
module.exports = { buildCostRecord, appendRecord, readUsageFromTranscript, METRICS_DIR, TOKENS_FILE };
|
|
215
|
+
module.exports = { buildCostRecord, appendRecord, readUsageFromTranscript, readCursor, writeCursor, METRICS_DIR, TOKENS_FILE, CURSOR_FILE };
|
|
@@ -39,6 +39,24 @@ function getCurrentSessionId(cwd) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
const TRACE_CURSOR_FILE = '.trace-cursor.json';
|
|
43
|
+
|
|
44
|
+
// Per-transcript high-water mark (see pan-cost-logger.js for the full rationale):
|
|
45
|
+
// sum only the transcript slice since this hook's previous SubagentStop, so a
|
|
46
|
+
// shared-session transcript isn't re-summed on every event (which inflates
|
|
47
|
+
// cumulative-per-turn cache-read into the billions — field report 2026-06).
|
|
48
|
+
// Trace-logger keeps its OWN cursor: cost-logger fires on the same event and the
|
|
49
|
+
// two must not consume each other's slice.
|
|
50
|
+
function traceCursorPath(cwd) { return path.join(getOptimizeDir(cwd), TRACE_CURSOR_FILE); }
|
|
51
|
+
function readTraceCursor(cwd) {
|
|
52
|
+
try { const c = JSON.parse(fs.readFileSync(traceCursorPath(cwd), 'utf-8')); return c && typeof c === 'object' ? c : {}; }
|
|
53
|
+
catch { return {}; }
|
|
54
|
+
}
|
|
55
|
+
function writeTraceCursor(cwd, cursor) {
|
|
56
|
+
try { fs.mkdirSync(path.dirname(traceCursorPath(cwd)), { recursive: true }); fs.writeFileSync(traceCursorPath(cwd), JSON.stringify(cursor), 'utf-8'); }
|
|
57
|
+
catch { /* best-effort — never block the agent loop */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
/**
|
|
43
61
|
* Ensure a trace session exists. If none is active, create a day-scoped
|
|
44
62
|
* auto-session so tracing works across the whole flow without manual init.
|
|
@@ -99,12 +117,13 @@ function extractNumber(obj, key) {
|
|
|
99
117
|
* @param {string} [sessionId] - Optional subagent session_id to filter on
|
|
100
118
|
* @returns {Object} usage totals object
|
|
101
119
|
*/
|
|
102
|
-
function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
120
|
+
function readUsageFromTranscript(transcriptPath, sessionId, sinceLine = 0) {
|
|
103
121
|
const totals = {
|
|
104
122
|
input_tokens: 0,
|
|
105
123
|
output_tokens: 0,
|
|
106
124
|
cache_read_input_tokens: 0,
|
|
107
125
|
cache_creation_input_tokens: 0,
|
|
126
|
+
lineCount: 0,
|
|
108
127
|
};
|
|
109
128
|
if (!transcriptPath || typeof transcriptPath !== 'string') return totals;
|
|
110
129
|
let raw;
|
|
@@ -113,8 +132,11 @@ function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
|
113
132
|
} catch {
|
|
114
133
|
return totals;
|
|
115
134
|
}
|
|
135
|
+
let seen = 0; // count of non-empty JSONL records (the cursor unit)
|
|
116
136
|
for (const line of raw.split('\n')) {
|
|
117
137
|
if (!line) continue;
|
|
138
|
+
seen++;
|
|
139
|
+
if (seen <= sinceLine) continue; // already attributed to an earlier event
|
|
118
140
|
let entry;
|
|
119
141
|
try {
|
|
120
142
|
entry = JSON.parse(line);
|
|
@@ -136,17 +158,25 @@ function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
|
136
158
|
totals.cache_read_input_tokens += extractNumber(usage, 'cache_read_input_tokens');
|
|
137
159
|
totals.cache_creation_input_tokens += extractNumber(usage, 'cache_creation_input_tokens');
|
|
138
160
|
}
|
|
161
|
+
totals.lineCount = seen;
|
|
139
162
|
return totals;
|
|
140
163
|
}
|
|
141
164
|
|
|
142
165
|
/**
|
|
143
166
|
* Build trace event(s) from a SubagentStop payload.
|
|
144
|
-
*
|
|
167
|
+
*
|
|
168
|
+
* When the payload lacks usage and `cwd` is supplied, this advances a
|
|
169
|
+
* per-transcript cursor (its only side effect) so each event is attributed
|
|
170
|
+
* just its own transcript slice — never the whole shared-session transcript
|
|
171
|
+
* re-summed every event (field report 2026-06). Without `cwd` it falls back to
|
|
172
|
+
* the legacy whole-transcript read (used only when a transcript_path is given).
|
|
145
173
|
*
|
|
146
174
|
* @param {Object} data - SubagentStop event payload
|
|
175
|
+
* @param {string} sessionId - active trace session id
|
|
176
|
+
* @param {string} [cwd] - project root, enables per-transcript delta attribution
|
|
147
177
|
* @returns {Object[]} Array of trace event records
|
|
148
178
|
*/
|
|
149
|
-
function buildTraceEvents(data, sessionId) {
|
|
179
|
+
function buildTraceEvents(data, sessionId, cwd) {
|
|
150
180
|
if (!data || typeof data !== 'object') return [];
|
|
151
181
|
if (data.hook_event_name && data.hook_event_name !== 'SubagentStop') return [];
|
|
152
182
|
|
|
@@ -160,10 +190,16 @@ function buildTraceEvents(data, sessionId) {
|
|
|
160
190
|
let outputTokens = extractNumber(data.usage, 'output_tokens');
|
|
161
191
|
let cacheRead = extractNumber(data.usage, 'cache_read_input_tokens');
|
|
162
192
|
if ((inputTokens + outputTokens + cacheRead) === 0 && data.transcript_path) {
|
|
163
|
-
const
|
|
193
|
+
const cursor = readTraceCursor(cwd);
|
|
194
|
+
const since = cursor[data.transcript_path] || 0;
|
|
195
|
+
const fromTranscript = readUsageFromTranscript(data.transcript_path, data.session_id, since);
|
|
164
196
|
inputTokens = fromTranscript.input_tokens;
|
|
165
197
|
outputTokens = fromTranscript.output_tokens;
|
|
166
198
|
cacheRead = fromTranscript.cache_read_input_tokens;
|
|
199
|
+
if (cwd && fromTranscript.lineCount > since) {
|
|
200
|
+
cursor[data.transcript_path] = fromTranscript.lineCount;
|
|
201
|
+
writeTraceCursor(cwd, cursor);
|
|
202
|
+
}
|
|
167
203
|
}
|
|
168
204
|
const totalTokens = inputTokens + outputTokens;
|
|
169
205
|
|
|
@@ -245,7 +281,7 @@ if (require.main === module) {
|
|
|
245
281
|
const cwd = data.cwd || data.workspace?.current_dir || process.cwd();
|
|
246
282
|
// Always ensure a session exists — creates a day-scoped auto-session if needed
|
|
247
283
|
const sessionId = ensureSessionId(cwd);
|
|
248
|
-
const events = buildTraceEvents(data, sessionId);
|
|
284
|
+
const events = buildTraceEvents(data, sessionId, cwd);
|
|
249
285
|
appendTraceEvents(cwd, events, sessionId);
|
|
250
286
|
} catch {
|
|
251
287
|
// Silent fail
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.1",
|
|
4
4
|
"description": "Command a bot army for your codebase: an Opus Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pan-wizard": "bin/install.js"
|
|
@@ -558,6 +558,28 @@ const MAX_CYCLES_MAX = 50;
|
|
|
558
558
|
const TOTAL_BUDGET_MIN = 5;
|
|
559
559
|
const TOTAL_BUDGET_MAX = 5000;
|
|
560
560
|
|
|
561
|
+
// Memory read/budget (ADR-0036 FW-2/FW-3): distill-and-select on the memory axis.
|
|
562
|
+
const MEMORY_SELECT_BUDGET_TOKENS = 2000; // per-agent cap for cue-scoped memory injection
|
|
563
|
+
const MEMORY_RECENCY_FLOOR = 5; // always keep this many newest entries (recall never empty)
|
|
564
|
+
const MEMORY_SOFT_CAP_MULT = 2; // soft auto-compaction trigger = DEFAULT_MAX_ENTRIES × this
|
|
565
|
+
const MEMORY_LOAD_WARN_TOKENS = 4000; // memory-budget telemetry: warn threshold (absolute tokens)
|
|
566
|
+
const MEMORY_LOAD_CRIT_TOKENS = 8000; // memory-budget telemetry: critical threshold (absolute tokens)
|
|
567
|
+
const MEMORY_LOAD_MAX_FRACTION = 0.15; // memory-budget telemetry: max fraction of median agent input
|
|
568
|
+
|
|
569
|
+
// Hygiene — project cleanup + version alignment (docs/FIELD-HARVEST-2026-07.md follow-ups).
|
|
570
|
+
const HYGIENE_TRACE_RETENTION_DAYS = 30; // trace sessions older than this are prunable…
|
|
571
|
+
const HYGIENE_TRACE_KEEP_MIN = 5; // …but always keep this many newest sessions
|
|
572
|
+
const HYGIENE_LEDGER_SUSPECT_RATIO = 0.5; // ledger "poisoned" when suspect fraction ≥ this…
|
|
573
|
+
const HYGIENE_LEDGER_MIN_RECORDS = 20; // …and it has at least this many records
|
|
574
|
+
const HYGIENE_TMP_AGE_MS = 60 * 60 * 1000; // .tmp orphans older than 1h are deletable
|
|
575
|
+
|
|
576
|
+
// Skill-Aligned Decomposition pass (ADR-0038): planner draft ↔ skill-surface alignment.
|
|
577
|
+
const SKILL_ALIGN_TOP_K = 3; // matches returned per draft task
|
|
578
|
+
const SKILL_ALIGN_MIN_SCORE = 1; // minimum keyword-overlap score to count as a match
|
|
579
|
+
const SKILL_ALIGN_VOCAB_BUDGET_TOKENS = 1500; // cap on the deduped vocabulary hint payload
|
|
580
|
+
const SKILL_ALIGN_MAX_TASKS = 50; // larger drafts are a planning smell — split the phase
|
|
581
|
+
const SKILL_ALIGN_CONTENT_CAP = 700; // chars of file head scored (≈ objective paragraph)
|
|
582
|
+
|
|
561
583
|
/** Valid conventional commit types */
|
|
562
584
|
const VALID_COMMIT_TYPES = ['feat', 'fix', 'docs', 'test', 'refactor', 'chore'];
|
|
563
585
|
|
|
@@ -734,6 +756,24 @@ module.exports = {
|
|
|
734
756
|
MAX_CYCLES_MAX,
|
|
735
757
|
TOTAL_BUDGET_MIN,
|
|
736
758
|
TOTAL_BUDGET_MAX,
|
|
759
|
+
MEMORY_SELECT_BUDGET_TOKENS,
|
|
760
|
+
MEMORY_RECENCY_FLOOR,
|
|
761
|
+
MEMORY_SOFT_CAP_MULT,
|
|
762
|
+
MEMORY_LOAD_WARN_TOKENS,
|
|
763
|
+
MEMORY_LOAD_CRIT_TOKENS,
|
|
764
|
+
MEMORY_LOAD_MAX_FRACTION,
|
|
765
|
+
// Hygiene
|
|
766
|
+
HYGIENE_TRACE_RETENTION_DAYS,
|
|
767
|
+
HYGIENE_TRACE_KEEP_MIN,
|
|
768
|
+
HYGIENE_LEDGER_SUSPECT_RATIO,
|
|
769
|
+
HYGIENE_LEDGER_MIN_RECORDS,
|
|
770
|
+
HYGIENE_TMP_AGE_MS,
|
|
771
|
+
// Skill-Aligned Decomposition (ADR-0038)
|
|
772
|
+
SKILL_ALIGN_TOP_K,
|
|
773
|
+
SKILL_ALIGN_MIN_SCORE,
|
|
774
|
+
SKILL_ALIGN_VOCAB_BUDGET_TOKENS,
|
|
775
|
+
SKILL_ALIGN_MAX_TASKS,
|
|
776
|
+
SKILL_ALIGN_CONTENT_CAP,
|
|
737
777
|
// Commit
|
|
738
778
|
VALID_COMMIT_TYPES,
|
|
739
779
|
DEFAULT_SENSITIVE_PATTERNS,
|