@planu/cli 4.5.0 → 4.6.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/config/token-waste-autopilot.json +48 -0
  3. package/dist/engine/token-optimizer/autopilot.d.ts +5 -0
  4. package/dist/engine/token-optimizer/autopilot.js +93 -0
  5. package/dist/engine/token-optimizer/context-preflight.d.ts +3 -0
  6. package/dist/engine/token-optimizer/context-preflight.js +64 -0
  7. package/dist/engine/token-optimizer/index.d.ts +6 -0
  8. package/dist/engine/token-optimizer/index.js +6 -0
  9. package/dist/engine/token-optimizer/loop-detector.d.ts +3 -0
  10. package/dist/engine/token-optimizer/loop-detector.js +32 -0
  11. package/dist/engine/token-optimizer/output-filter.d.ts +3 -0
  12. package/dist/engine/token-optimizer/output-filter.js +67 -0
  13. package/dist/engine/token-optimizer/policy-loader.d.ts +3 -0
  14. package/dist/engine/token-optimizer/policy-loader.js +83 -0
  15. package/dist/engine/token-optimizer/tool-relevance.d.ts +6 -0
  16. package/dist/engine/token-optimizer/tool-relevance.js +57 -0
  17. package/dist/tools/create-spec/post-creation.d.ts +2 -1
  18. package/dist/tools/create-spec/post-creation.js +32 -0
  19. package/dist/tools/package-handoff.js +47 -1
  20. package/dist/tools/schemas/token-intelligence.d.ts +1 -0
  21. package/dist/tools/schemas/token-intelligence.js +3 -2
  22. package/dist/tools/status-handler.js +17 -2
  23. package/dist/tools/token-intelligence-handler.js +46 -1
  24. package/dist/types/index.d.ts +1 -0
  25. package/dist/types/index.js +1 -0
  26. package/dist/types/token-waste-autopilot.d.ts +142 -0
  27. package/dist/types/token-waste-autopilot.js +2 -0
  28. package/package.json +11 -11
  29. package/planu-native.json +1 -1
  30. package/planu-plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [4.6.0] - 2026-06-11
2
+
3
+ ### Features
4
+ - feat(SPEC-1084): add token waste autopilot
5
+
6
+ ### Chores
7
+ - chore(deps): sync lockfile
8
+ - chore(deps): update patch dependencies
9
+
10
+
1
11
  ## [4.5.0] - 2026-06-10
2
12
 
3
13
  ### Features
@@ -0,0 +1,48 @@
1
+ {
2
+ "version": 1,
3
+ "enabled": true,
4
+ "context": {
5
+ "generatedPatterns": ["dist/**", "coverage/**", ".next/**", "build/**"],
6
+ "stalePatterns": ["*.log", "*.bak", "*.tmp"],
7
+ "summarizePatterns": ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"],
8
+ "staleSessionMinutes": 60,
9
+ "safetyOverrideActions": ["release", "security", "license", "lockfile", "exact-reproduction"]
10
+ },
11
+ "outputs": {
12
+ "strategies": {
13
+ "test": { "maxLines": 40, "keepFailures": true },
14
+ "log": { "maxLines": 30, "uniqueOnly": true },
15
+ "json": { "maxLines": 40, "summarizeKeys": true },
16
+ "dependency": { "maxLines": 50, "keepFailures": true },
17
+ "generic": { "maxLines": 60 }
18
+ }
19
+ },
20
+ "tools": {
21
+ "groups": {
22
+ "spec": ["create_spec", "check_readiness", "challenge_spec", "update_status"],
23
+ "handoff": ["package_handoff", "generate_execution_plan"],
24
+ "status": ["planu_status", "session_checkpoint"],
25
+ "token": ["token_intelligence", "tokens", "context_budget"]
26
+ },
27
+ "maxRecommended": 8
28
+ },
29
+ "loops": {
30
+ "thresholds": {
31
+ "file_read": 3,
32
+ "search": 3,
33
+ "test_run": 2,
34
+ "failure": 2
35
+ },
36
+ "safetyOverrideActions": ["release", "security", "license"]
37
+ },
38
+ "modelEffort": {
39
+ "lowRisk": "low",
40
+ "default": "medium",
41
+ "highRisk": "high",
42
+ "maxRisk": "max"
43
+ },
44
+ "redaction": {
45
+ "maxSnippetChars": 240,
46
+ "redactPatterns": ["secret", "token", "password", "api_key"]
47
+ }
48
+ }
@@ -0,0 +1,5 @@
1
+ import type { TokenWasteAutopilotInput, TokenWasteReport } from '../../types/token-waste-autopilot.js';
2
+ export declare function redactSnippet(text: string, patterns: string[], maxChars: number): string;
3
+ export declare function buildTokenWasteReport(input: TokenWasteAutopilotInput): TokenWasteReport;
4
+ export declare function formatTokenWasteReport(report: TokenWasteReport): string;
5
+ //# sourceMappingURL=autopilot.d.ts.map
@@ -0,0 +1,93 @@
1
+ function collectEvidence(decisions) {
2
+ return decisions.flatMap((decision) => decision.evidence);
3
+ }
4
+ function modelAdvice(input) {
5
+ const risk = input.spec?.risk ?? 'medium';
6
+ const effort = risk === 'critical'
7
+ ? input.policy.modelEffort.maxRisk
8
+ : risk === 'high'
9
+ ? input.policy.modelEffort.highRisk
10
+ : risk === 'low'
11
+ ? input.policy.modelEffort.lowRisk
12
+ : input.policy.modelEffort.default;
13
+ return {
14
+ effort,
15
+ reason: `Selected ${effort} effort from spec risk ${risk}.`,
16
+ evidence: [
17
+ { source: 'spec', key: 'risk', value: risk },
18
+ { source: 'policy', key: 'modelEffort' },
19
+ ],
20
+ };
21
+ }
22
+ function escapeInert(text) {
23
+ return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
24
+ }
25
+ export function redactSnippet(text, patterns, maxChars) {
26
+ const truncated = text.slice(0, maxChars);
27
+ return patterns.reduce((current, pattern) => {
28
+ const re = new RegExp(pattern, 'gi');
29
+ return current.replace(re, '[redacted]');
30
+ }, truncated);
31
+ }
32
+ function sanitizeDecision(decision, patterns, maxChars) {
33
+ return {
34
+ ...decision,
35
+ target: redactSnippet(decision.target, patterns, maxChars),
36
+ reason: redactSnippet(decision.reason, patterns, maxChars),
37
+ evidence: decision.evidence.map((item) => ({
38
+ ...item,
39
+ ...(typeof item.value === 'string'
40
+ ? { value: redactSnippet(item.value, patterns, maxChars) }
41
+ : {}),
42
+ })),
43
+ };
44
+ }
45
+ function sanitizeDecisions(decisions, input) {
46
+ return decisions.map((decision) => sanitizeDecision(decision, input.policy.redaction.redactPatterns, input.policy.redaction.maxSnippetChars));
47
+ }
48
+ export function buildTokenWasteReport(input) {
49
+ const actionsTaken = sanitizeDecisions([
50
+ ...(input.context?.summarize ?? []),
51
+ ...(input.context?.exclude ?? []),
52
+ ...(input.output?.decisions ?? []),
53
+ ], input);
54
+ const recommendations = sanitizeDecisions([...(input.context?.include ?? []), ...(input.tools?.recommended ?? [])], input);
55
+ const risks = sanitizeDecisions([...(input.tools?.avoided ?? []), ...(input.loops?.warnings ?? [])], input);
56
+ const modelEffort = modelAdvice(input);
57
+ return {
58
+ actionsTaken,
59
+ recommendations,
60
+ risks,
61
+ evidence: [
62
+ ...collectEvidence(actionsTaken),
63
+ ...collectEvidence(recommendations),
64
+ ...collectEvidence(risks),
65
+ ...modelEffort.evidence,
66
+ ],
67
+ modelEffort,
68
+ };
69
+ }
70
+ export function formatTokenWasteReport(report) {
71
+ const lines = ['## Token Waste Autopilot', ''];
72
+ lines.push(`Model effort: ${escapeInert(report.modelEffort.effort)} — ${escapeInert(report.modelEffort.reason)}`);
73
+ if (report.actionsTaken.length > 0) {
74
+ lines.push('', 'Actions taken:');
75
+ for (const item of report.actionsTaken.slice(0, 6)) {
76
+ lines.push(`- ${escapeInert(item.decision)}: ${escapeInert(item.target)} — ${escapeInert(item.reason)}`);
77
+ }
78
+ }
79
+ if (report.recommendations.length > 0) {
80
+ lines.push('', 'Recommendations:');
81
+ for (const item of report.recommendations.slice(0, 6)) {
82
+ lines.push(`- ${escapeInert(item.target)}: ${escapeInert(item.reason)}`);
83
+ }
84
+ }
85
+ if (report.risks.length > 0) {
86
+ lines.push('', 'Risks:');
87
+ for (const item of report.risks.slice(0, 6)) {
88
+ lines.push(`- ${escapeInert(item.target)}: ${escapeInert(item.reason)}`);
89
+ }
90
+ }
91
+ return lines.join('\n');
92
+ }
93
+ //# sourceMappingURL=autopilot.js.map
@@ -0,0 +1,3 @@
1
+ import type { ContextPreflightInput, ContextPreflightResult } from '../../types/token-waste-autopilot.js';
2
+ export declare function analyzeContextPreflight(input: ContextPreflightInput): ContextPreflightResult;
3
+ //# sourceMappingURL=context-preflight.d.ts.map
@@ -0,0 +1,64 @@
1
+ function globToRegExp(pattern) {
2
+ const escaped = pattern
3
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
4
+ .replace(/\*\*/g, '::DOUBLE_STAR::')
5
+ .replace(/\*/g, '[^/]*')
6
+ .replace(/::DOUBLE_STAR::/g, '.*');
7
+ return new RegExp(`^${escaped}$`);
8
+ }
9
+ function matchesAny(path, patterns) {
10
+ return patterns.find((pattern) => globToRegExp(pattern).test(path));
11
+ }
12
+ function decision(kind, target, reason, key) {
13
+ return {
14
+ decision: kind,
15
+ target,
16
+ reason,
17
+ evidence: [{ source: 'policy', key }],
18
+ confidence: 'high',
19
+ };
20
+ }
21
+ function hasSafetyOverride(action, policy) {
22
+ return policy.context.safetyOverrideActions.includes(action);
23
+ }
24
+ function classifyCandidate(candidate, input) {
25
+ if (hasSafetyOverride(input.action, input.policy)) {
26
+ return decision('override', candidate.path, `Action ${input.action} requires exact context according to policy.`, 'context.safetyOverrideActions');
27
+ }
28
+ const generated = matchesAny(candidate.path, input.policy.context.generatedPatterns);
29
+ if (generated !== undefined) {
30
+ return decision('exclude', candidate.path, `Matches generated context pattern ${generated}.`, 'context.generatedPatterns');
31
+ }
32
+ const stale = matchesAny(candidate.path, input.policy.context.stalePatterns);
33
+ if (stale !== undefined) {
34
+ return decision('exclude', candidate.path, `Matches stale context pattern ${stale}.`, 'context.stalePatterns');
35
+ }
36
+ const summarize = matchesAny(candidate.path, input.policy.context.summarizePatterns);
37
+ if (summarize !== undefined) {
38
+ return decision('summarize', candidate.path, `Matches summarizable context pattern ${summarize}.`, 'context.summarizePatterns');
39
+ }
40
+ return {
41
+ decision: 'include',
42
+ target: candidate.path,
43
+ reason: candidate.reason ?? 'Candidate context is relevant and not excluded by policy.',
44
+ evidence: [{ source: 'runtime', key: 'candidate.path', value: candidate.path }],
45
+ confidence: 'medium',
46
+ };
47
+ }
48
+ export function analyzeContextPreflight(input) {
49
+ const result = { include: [], summarize: [], exclude: [] };
50
+ for (const candidate of input.candidates) {
51
+ const item = classifyCandidate(candidate, input);
52
+ if (item.decision === 'exclude') {
53
+ result.exclude.push(item);
54
+ }
55
+ else if (item.decision === 'summarize') {
56
+ result.summarize.push(item);
57
+ }
58
+ else {
59
+ result.include.push(item);
60
+ }
61
+ }
62
+ return result;
63
+ }
64
+ //# sourceMappingURL=context-preflight.js.map
@@ -8,5 +8,11 @@ export { BudgetManager } from './budget.js';
8
8
  export { OptimizationReporter } from './reporter.js';
9
9
  export { TokenOptimizer } from './optimizer.js';
10
10
  export { aggregateEntries, computeDailyBreakdown, detectTrend, detectAnomalies, computeBudgetStatus, getTopConsumers, computeOptimizationSavings, } from './analytics.js';
11
+ export { loadTokenWastePolicy } from './policy-loader.js';
12
+ export { analyzeContextPreflight } from './context-preflight.js';
13
+ export { filterVerboseOutput } from './output-filter.js';
14
+ export { recommendRelevantTools, toolsFromPolicyGroups } from './tool-relevance.js';
15
+ export { detectTokenWasteLoops } from './loop-detector.js';
16
+ export { buildTokenWasteReport, formatTokenWasteReport, redactSnippet } from './autopilot.js';
11
17
  export type { DailyBreakdown, TrendResult, Anomaly, TopConsumer, OptimizationSavings, } from './analytics.js';
12
18
  //# sourceMappingURL=index.d.ts.map
@@ -9,4 +9,10 @@ export { BudgetManager } from './budget.js';
9
9
  export { OptimizationReporter } from './reporter.js';
10
10
  export { TokenOptimizer } from './optimizer.js';
11
11
  export { aggregateEntries, computeDailyBreakdown, detectTrend, detectAnomalies, computeBudgetStatus, getTopConsumers, computeOptimizationSavings, } from './analytics.js';
12
+ export { loadTokenWastePolicy } from './policy-loader.js';
13
+ export { analyzeContextPreflight } from './context-preflight.js';
14
+ export { filterVerboseOutput } from './output-filter.js';
15
+ export { recommendRelevantTools, toolsFromPolicyGroups } from './tool-relevance.js';
16
+ export { detectTokenWasteLoops } from './loop-detector.js';
17
+ export { buildTokenWasteReport, formatTokenWasteReport, redactSnippet } from './autopilot.js';
12
18
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ import type { TokenWasteLoopResult, TokenWastePolicy, TokenWasteRuntimeEvent } from '../../types/token-waste-autopilot.js';
2
+ export declare function detectTokenWasteLoops(events: TokenWasteRuntimeEvent[], policy: TokenWastePolicy, action?: string): TokenWasteLoopResult;
3
+ //# sourceMappingURL=loop-detector.d.ts.map
@@ -0,0 +1,32 @@
1
+ export function detectTokenWasteLoops(events, policy, action = 'default') {
2
+ if (policy.loops.safetyOverrideActions.includes(action)) {
3
+ return { warnings: [] };
4
+ }
5
+ const counts = new Map();
6
+ for (const event of events) {
7
+ if (event.changedSincePrevious === true) {
8
+ continue;
9
+ }
10
+ const key = `${event.type}:${event.key}`;
11
+ counts.set(key, (counts.get(key) ?? 0) + 1);
12
+ }
13
+ const warnings = [];
14
+ for (const [key, count] of counts) {
15
+ const [type = 'unknown', target = key] = key.split(':');
16
+ const threshold = policy.loops.thresholds[type] ?? Number.POSITIVE_INFINITY;
17
+ if (count >= threshold) {
18
+ warnings.push({
19
+ decision: 'warn',
20
+ target,
21
+ reason: `Repeated ${type} operation detected ${String(count)} times without an intervening change.`,
22
+ evidence: [
23
+ { source: 'policy', key: `loops.thresholds.${type}`, value: threshold },
24
+ { source: 'runtime', key: 'event.count', value: count },
25
+ ],
26
+ confidence: 'high',
27
+ });
28
+ }
29
+ }
30
+ return { warnings };
31
+ }
32
+ //# sourceMappingURL=loop-detector.js.map
@@ -0,0 +1,3 @@
1
+ import type { VerboseOutputInput, VerboseOutputResult } from '../../types/token-waste-autopilot.js';
2
+ export declare function filterVerboseOutput(input: VerboseOutputInput): VerboseOutputResult;
3
+ //# sourceMappingURL=output-filter.d.ts.map
@@ -0,0 +1,67 @@
1
+ function escapeInert(text) {
2
+ return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
3
+ }
4
+ function redact(text, patterns) {
5
+ return patterns.reduce((current, pattern) => {
6
+ const re = new RegExp(pattern, 'gi');
7
+ return current.replace(re, '[redacted]');
8
+ }, text);
9
+ }
10
+ function uniqueLines(lines) {
11
+ return [...new Set(lines)];
12
+ }
13
+ function failureLines(lines) {
14
+ const failures = lines.filter((line) => /\b(error|fail|failed|exception|critical)\b/i.test(line));
15
+ return failures.length > 0 ? failures : lines;
16
+ }
17
+ function jsonSummary(text) {
18
+ try {
19
+ const parsed = JSON.parse(text);
20
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
21
+ return [`JSON object keys: ${Object.keys(parsed).join(', ')}`];
22
+ }
23
+ if (Array.isArray(parsed)) {
24
+ return [`JSON array length: ${String(parsed.length)}`];
25
+ }
26
+ }
27
+ catch {
28
+ // Fall through to normal line handling.
29
+ }
30
+ return text.split('\n');
31
+ }
32
+ function selectStrategy(input) {
33
+ const strategy = input.policy.outputs.strategies[input.kind] ?? input.policy.outputs.strategies.generic;
34
+ if (strategy === undefined) {
35
+ throw new Error('Token waste policy must define outputs.strategies.generic');
36
+ }
37
+ return strategy;
38
+ }
39
+ function compactLines(input, strategy) {
40
+ const rawLines = strategy.summarizeKeys === true ? jsonSummary(input.text) : input.text.split('\n');
41
+ const selected = strategy.keepFailures === true ? failureLines(rawLines) : rawLines;
42
+ const unique = strategy.uniqueOnly === true ? uniqueLines(selected) : selected;
43
+ return unique.slice(0, strategy.maxLines);
44
+ }
45
+ export function filterVerboseOutput(input) {
46
+ const strategy = selectStrategy(input);
47
+ const originalLines = input.text.split('\n').length;
48
+ const lines = compactLines(input, strategy);
49
+ const compact = redact(escapeInert(lines.join('\n')), input.policy.redaction.redactPatterns);
50
+ const decisions = [
51
+ {
52
+ decision: originalLines > lines.length ? 'summarize' : 'include',
53
+ target: input.kind,
54
+ reason: `Applied policy output strategy for ${input.kind}.`,
55
+ evidence: [{ source: 'policy', key: `outputs.strategies.${input.kind}` }],
56
+ confidence: input.policy.outputs.strategies[input.kind] !== undefined ? 'high' : 'medium',
57
+ },
58
+ ];
59
+ return {
60
+ text: compact,
61
+ originalLines,
62
+ returnedLines: lines.length,
63
+ ...(input.fullOutputRef !== undefined ? { fullOutputRef: input.fullOutputRef } : {}),
64
+ decisions,
65
+ };
66
+ }
67
+ //# sourceMappingURL=output-filter.js.map
@@ -0,0 +1,3 @@
1
+ import type { LoadedTokenWastePolicy, TokenWastePolicyLoadOptions } from '../../types/token-waste-autopilot.js';
2
+ export declare function loadTokenWastePolicy(projectPath?: string, options?: TokenWastePolicyLoadOptions): Promise<LoadedTokenWastePolicy>;
3
+ //# sourceMappingURL=policy-loader.d.ts.map
@@ -0,0 +1,83 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ function configPath() {
5
+ return join(dirname(fileURLToPath(import.meta.url)), '../../config/token-waste-autopilot.json');
6
+ }
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ function mergeDeep(base, override) {
11
+ const result = { ...base };
12
+ for (const [key, value] of Object.entries(override)) {
13
+ const current = result[key];
14
+ result[key] = isRecord(current) && isRecord(value) ? mergeDeep(current, value) : value;
15
+ }
16
+ return result;
17
+ }
18
+ function assertStringArray(value, path) {
19
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
20
+ throw new Error(`Invalid token waste policy at ${path}: expected string[]`);
21
+ }
22
+ }
23
+ function validatePolicy(value) {
24
+ if (!isRecord(value)) {
25
+ throw new Error('Invalid token waste policy: expected object');
26
+ }
27
+ if (value.version !== 1) {
28
+ throw new Error('Invalid token waste policy: version must be 1');
29
+ }
30
+ const policy = value;
31
+ assertStringArray(policy.context.generatedPatterns, 'context.generatedPatterns');
32
+ assertStringArray(policy.context.stalePatterns, 'context.stalePatterns');
33
+ assertStringArray(policy.context.summarizePatterns, 'context.summarizePatterns');
34
+ if (!Number.isFinite(policy.context.staleSessionMinutes) ||
35
+ policy.context.staleSessionMinutes < 1) {
36
+ throw new Error('Invalid token waste policy at context.staleSessionMinutes: expected positive number');
37
+ }
38
+ assertStringArray(policy.context.safetyOverrideActions, 'context.safetyOverrideActions');
39
+ if (!isRecord(policy.outputs.strategies)) {
40
+ throw new Error('Invalid token waste policy at outputs.strategies: expected object');
41
+ }
42
+ if (!isRecord(policy.tools.groups)) {
43
+ throw new Error('Invalid token waste policy at tools.groups: expected object');
44
+ }
45
+ for (const [group, tools] of Object.entries(policy.tools.groups)) {
46
+ assertStringArray(tools, `tools.groups.${group}`);
47
+ }
48
+ if (!Number.isFinite(policy.tools.maxRecommended) || policy.tools.maxRecommended < 1) {
49
+ throw new Error('Invalid token waste policy at tools.maxRecommended: expected positive number');
50
+ }
51
+ if (!isRecord(policy.loops.thresholds)) {
52
+ throw new Error('Invalid token waste policy at loops.thresholds: expected object');
53
+ }
54
+ assertStringArray(policy.loops.safetyOverrideActions, 'loops.safetyOverrideActions');
55
+ assertStringArray(policy.redaction.redactPatterns, 'redaction.redactPatterns');
56
+ return policy;
57
+ }
58
+ async function readJson(path) {
59
+ return JSON.parse(await readFile(path, 'utf-8'));
60
+ }
61
+ export async function loadTokenWastePolicy(projectPath, options = {}) {
62
+ const defaultPolicyPath = options.defaultPolicyPath ?? configPath();
63
+ const defaultPolicy = validatePolicy(await readJson(defaultPolicyPath));
64
+ const evidence = [
65
+ { source: 'policy', key: defaultPolicyPath },
66
+ ];
67
+ let merged = defaultPolicy;
68
+ if (projectPath !== undefined) {
69
+ const planuConfigPath = join(projectPath, 'planu.json');
70
+ try {
71
+ const config = await readJson(planuConfigPath);
72
+ if (isRecord(config) && isRecord(config.tokenWasteAutopilot)) {
73
+ merged = validatePolicy(mergeDeep(defaultPolicy, config.tokenWasteAutopilot));
74
+ evidence.push({ source: 'project-config', key: 'planu.json.tokenWasteAutopilot' });
75
+ }
76
+ }
77
+ catch {
78
+ // Missing or malformed project config does not block default policy loading.
79
+ }
80
+ }
81
+ return { policy: merged, evidence };
82
+ }
83
+ //# sourceMappingURL=policy-loader.js.map
@@ -0,0 +1,6 @@
1
+ import type { TokenWastePolicy, ToolRelevanceInput, ToolRelevanceResult } from '../../types/token-waste-autopilot.js';
2
+ export declare function toolsFromPolicyGroups(policy: TokenWastePolicy): {
3
+ name: string;
4
+ }[];
5
+ export declare function recommendRelevantTools(input: ToolRelevanceInput): ToolRelevanceResult;
6
+ //# sourceMappingURL=tool-relevance.d.ts.map
@@ -0,0 +1,57 @@
1
+ function normalized(text) {
2
+ return text.toLowerCase();
3
+ }
4
+ function matchesIntent(tool, signals) {
5
+ const haystack = normalized(`${tool.name} ${tool.description ?? ''}`);
6
+ return signals.some((signal) => haystack.includes(normalized(signal)));
7
+ }
8
+ function groupNamesForTool(toolName, groups) {
9
+ return Object.entries(groups)
10
+ .filter(([, tools]) => tools.includes(toolName))
11
+ .map(([group]) => group);
12
+ }
13
+ export function toolsFromPolicyGroups(policy) {
14
+ return [...new Set(Object.values(policy.tools.groups).flat())].map((name) => ({ name }));
15
+ }
16
+ export function recommendRelevantTools(input) {
17
+ const unsupported = new Set(input.hostUnsupportedTools ?? []);
18
+ const actionSignals = [input.action, ...input.intentSignals].map(normalized);
19
+ const recommended = [];
20
+ const avoided = [];
21
+ for (const tool of input.tools) {
22
+ const groups = groupNamesForTool(tool.name, input.policy.tools.groups);
23
+ const isUnsupported = unsupported.has(tool.name);
24
+ const isRelevant = !isUnsupported &&
25
+ (matchesIntent(tool, actionSignals) ||
26
+ groups.some((group) => actionSignals.includes(normalized(group))));
27
+ if (isRelevant && recommended.length < input.policy.tools.maxRecommended) {
28
+ recommended.push({
29
+ decision: 'recommend',
30
+ target: tool.name,
31
+ reason: 'Tool matches the current action, intent signals, or policy tool group.',
32
+ evidence: [
33
+ { source: 'policy', key: 'tools.groups', value: groups.join(',') },
34
+ { source: 'runtime', key: 'intentSignals', value: input.intentSignals.join(',') },
35
+ ],
36
+ confidence: groups.length > 0 ? 'high' : 'medium',
37
+ });
38
+ continue;
39
+ }
40
+ avoided.push({
41
+ decision: 'avoid',
42
+ target: tool.name,
43
+ reason: isUnsupported
44
+ ? 'Host capability marks this tool unsupported.'
45
+ : 'Tool does not match the current action or intent and can add avoidable tool metadata overhead.',
46
+ evidence: [
47
+ {
48
+ source: isUnsupported ? 'host-capability' : 'policy',
49
+ key: isUnsupported ? 'unsupportedTools' : 'tools.groups',
50
+ },
51
+ ],
52
+ confidence: isUnsupported ? 'high' : 'medium',
53
+ });
54
+ }
55
+ return { recommended, avoided };
56
+ }
57
+ //# sourceMappingURL=tool-relevance.js.map
@@ -1,5 +1,6 @@
1
- import type { Spec, PostCreationSuggestion, ProjectKnowledge } from '../../types/index.js';
1
+ import type { Spec, PostCreationSuggestion, ProjectKnowledge, TokenWasteReport } from '../../types/index.js';
2
2
  export declare function getAsyncAnalysisPath(projectPath: string, specId: string): string;
3
+ export declare function buildInitialTokenWasteMetadata(specId: string, projectPath: string, description: string): Promise<TokenWasteReport | null>;
3
4
  /** Auto-setup git branch (non-blocking). Returns branch info or undefined. */
4
5
  export declare function setupGitBranch(projectId: string, specId: string): Promise<{
5
6
  branch: string;
@@ -12,11 +12,42 @@ import { writeFile, mkdir } from 'node:fs/promises';
12
12
  import { join, dirname } from 'node:path';
13
13
  import { hashProjectPath, projectDataDir } from '../../storage/base-store.js';
14
14
  import { appendAutopilotLogEntry } from '../../storage/autopilot-log-store.js';
15
+ import { analyzeContextPreflight, buildTokenWasteReport, loadTokenWastePolicy, recommendRelevantTools, toolsFromPolicyGroups, } from '../../engine/token-optimizer/index.js';
15
16
  const ASYNC_ANALYSIS_HOOK = 'create-spec-async-analysis';
16
17
  export function getAsyncAnalysisPath(projectPath, specId) {
17
18
  const projectId = hashProjectPath(projectPath);
18
19
  return join(projectDataDir(projectId), 'analysis', `${specId}.json`);
19
20
  }
21
+ export async function buildInitialTokenWasteMetadata(specId, projectPath, description) {
22
+ try {
23
+ const { policy } = await loadTokenWastePolicy(projectPath);
24
+ if (!policy.enabled) {
25
+ return null;
26
+ }
27
+ const context = analyzeContextPreflight({
28
+ action: 'create_spec',
29
+ policy,
30
+ candidates: [{ path: 'planu/specs', reason: 'New spec workspace metadata.' }],
31
+ spec: { id: specId },
32
+ });
33
+ const tools = recommendRelevantTools({
34
+ action: 'create_spec',
35
+ intentSignals: ['spec', ...description.split(/\s+/).slice(0, 12)],
36
+ tools: toolsFromPolicyGroups(policy),
37
+ policy,
38
+ });
39
+ return buildTokenWasteReport({
40
+ action: 'create_spec',
41
+ policy,
42
+ context,
43
+ tools,
44
+ spec: { id: specId },
45
+ });
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
20
51
  /** Auto-setup git branch (non-blocking). Returns branch info or undefined. */
21
52
  export async function setupGitBranch(projectId, specId) {
22
53
  const result = await tryAutoSetupGit(projectId, specId);
@@ -182,6 +213,7 @@ export function runAutopilotAsync(specId, projectPath, _description) {
182
213
  specId,
183
214
  completedAt: new Date().toISOString(),
184
215
  pendingAnalysis: false,
216
+ tokenWasteAutopilot: await buildInitialTokenWasteMetadata(specId, projectPath, _description),
185
217
  };
186
218
  const analysisPath = getAsyncAnalysisPath(projectPath, specId);
187
219
  await mkdir(dirname(analysisPath), { recursive: true });
@@ -3,6 +3,7 @@ import { specStore, knowledgeStore } from '../storage/index.js';
3
3
  import { checkSpecReadiness } from '../engine/readiness-checker.js';
4
4
  import { packageHandoff } from '../engine/handoff-packager.js';
5
5
  import { detectParadigms } from '../engine/paradigm-detector.js';
6
+ import { analyzeContextPreflight, buildTokenWasteReport, formatTokenWasteReport, loadTokenWastePolicy, recommendRelevantTools, toolsFromPolicyGroups, } from '../engine/token-optimizer/index.js';
6
7
  import { appendTransitionEvent } from '../storage/transition-log.js';
7
8
  // ── Formatting helpers ───────────────────────────────────────────────────────
8
9
  function formatHandoff(pkg) {
@@ -134,6 +135,47 @@ function formatHandoff(pkg) {
134
135
  }
135
136
  return lines.join('\n');
136
137
  }
138
+ async function buildHandoffTokenWasteReport(pkg, knowledge, spec) {
139
+ try {
140
+ const { policy } = await loadTokenWastePolicy(knowledge.projectPath);
141
+ if (!policy.enabled) {
142
+ return null;
143
+ }
144
+ const context = analyzeContextPreflight({
145
+ action: 'package_handoff',
146
+ policy,
147
+ candidates: [...pkg.filesToModify, ...pkg.filesToCreate].map((path) => ({
148
+ path,
149
+ reason: 'Referenced by handoff package.',
150
+ })),
151
+ spec: {
152
+ id: spec.id,
153
+ risk: spec.risk,
154
+ target: spec.target,
155
+ },
156
+ });
157
+ const tools = recommendRelevantTools({
158
+ action: 'package_handoff',
159
+ intentSignals: ['handoff', spec.scope ?? '', spec.target ?? ''].filter(Boolean),
160
+ tools: toolsFromPolicyGroups(policy),
161
+ policy,
162
+ });
163
+ return buildTokenWasteReport({
164
+ action: 'package_handoff',
165
+ policy,
166
+ context,
167
+ tools,
168
+ spec: {
169
+ id: spec.id,
170
+ risk: spec.risk,
171
+ scope: spec.scope,
172
+ },
173
+ });
174
+ }
175
+ catch {
176
+ return null;
177
+ }
178
+ }
137
179
  // ── Handler ──────────────────────────────────────────────────────────────────
138
180
  export async function handlePackageHandoff(args) {
139
181
  const { projectId, specId } = args;
@@ -188,7 +230,10 @@ export async function handlePackageHandoff(args) {
188
230
  readinessScore: readinessReport.score,
189
231
  constraints: paradigmConstraints,
190
232
  };
191
- const formatted = formatHandoff(pkgWithScore);
233
+ const tokenWasteReport = await buildHandoffTokenWasteReport(pkgWithScore, knowledge, spec);
234
+ const formatted = tokenWasteReport !== null
235
+ ? `${formatHandoff(pkgWithScore)}\n${formatTokenWasteReport(tokenWasteReport)}`
236
+ : formatHandoff(pkgWithScore);
192
237
  void appendTransitionEvent({
193
238
  projectId,
194
239
  specId,
@@ -209,6 +254,7 @@ export async function handlePackageHandoff(args) {
209
254
  blockers: pkgWithScore.blockers,
210
255
  handoffPath: pkgWithScore.handoffPath,
211
256
  contextHash: pkgWithScore.contextHash,
257
+ ...(tokenWasteReport !== null ? { tokenWaste: tokenWasteReport } : {}),
212
258
  },
213
259
  };
214
260
  }
@@ -15,6 +15,7 @@ export declare const TokenIntelligenceViewEnum: z.ZodEnum<{
15
15
  summary: "summary";
16
16
  budget: "budget";
17
17
  detailed: "detailed";
18
+ autopilot: "autopilot";
18
19
  reconciliation: "reconciliation";
19
20
  trends: "trends";
20
21
  }>;
@@ -9,10 +9,11 @@ export const TokenIntelligenceGroupByEnum = z
9
9
  .describe('Group results by: tool (per MCP tool name), model (per AI model), ' +
10
10
  'spec (per spec ID), day (daily breakdown)');
11
11
  export const TokenIntelligenceViewEnum = z
12
- .enum(['summary', 'detailed', 'budget', 'reconciliation', 'trends'])
12
+ .enum(['summary', 'detailed', 'budget', 'reconciliation', 'trends', 'autopilot'])
13
13
  .describe('Dashboard view: summary (high-level overview with top consumers), ' +
14
14
  'detailed (full per-tool and per-model breakdown), ' +
15
15
  'budget (spending vs monthly limits with projections), ' +
16
16
  'reconciliation (estimated vs actual cost accuracy per spec), ' +
17
- 'trends (usage patterns over time, week-over-week changes, and anomaly alerts)');
17
+ 'trends (usage patterns over time, week-over-week changes, and anomaly alerts), ' +
18
+ 'autopilot (policy-driven recommendations to reduce wasted context, output, and tool usage)');
18
19
  //# sourceMappingURL=token-intelligence.js.map
@@ -17,6 +17,7 @@ import { calculateSLAStatus } from '../engine/approval-workflow.js';
17
17
  import { readPlanuConfig } from '../engine/planu-config-writer.js';
18
18
  import { computePlanuDriftScore } from '../engine/dashboard/drift-score.js';
19
19
  import { findStaleBranches, findStaleWorktrees, findStaleStashes, } from '../engine/housekeeping/index.js';
20
+ import { loadTokenWastePolicy } from '../engine/token-optimizer/index.js';
20
21
  // SPEC-1011 Bug G: fire-and-forget status.json reconciliation
21
22
  import { reconcileStatusFromDisk } from '../engine/status-reconciler/index.js';
22
23
  const execFile = promisify(execFileCb);
@@ -127,7 +128,7 @@ function buildSuggestion(snapshot) {
127
128
  // Output builder
128
129
  // ---------------------------------------------------------------------------
129
130
  function buildOutput(params) {
130
- const { snapshot, git, ci, slaBreaches, workMode, updateBanner, autoCompleted, sessionTip } = params;
131
+ const { snapshot, git, ci, slaBreaches, workMode, updateBanner, autoCompleted, sessionTip, tokenWasteHint, } = params;
131
132
  const lines = ['Planu Status', '━━━━━━━━━━━━━━━'];
132
133
  if (snapshot.active !== null) {
133
134
  const title = snapshot.active.title.slice(0, 40);
@@ -162,6 +163,9 @@ function buildOutput(params) {
162
163
  if (sessionTip !== undefined) {
163
164
  lines.push(`SESSION TIP ${sessionTip}`);
164
165
  }
166
+ if (tokenWasteHint !== undefined) {
167
+ lines.push(`TOKEN WASTE ${tokenWasteHint}`);
168
+ }
165
169
  lines.push(`SUGGEST ${buildSuggestion(snapshot)}`);
166
170
  if (workMode !== undefined) {
167
171
  lines.push(`WORK MODE ${workMode}`);
@@ -206,7 +210,7 @@ export async function handlePlanStatus(args) {
206
210
  /* best-effort */
207
211
  });
208
212
  }
209
- const [snapshot, git, ci, slaBreaches, planuConfig, autoCompleted, sessionState, driftScore, pendingCleanup,] = await Promise.all([
213
+ const [snapshot, git, ci, slaBreaches, planuConfig, autoCompleted, sessionState, driftScore, pendingCleanup, tokenWastePolicy,] = await Promise.all([
210
214
  getStatusSpecSnapshot(projectId),
211
215
  getGitState(args.projectPath),
212
216
  getCiState(args.projectPath),
@@ -237,6 +241,9 @@ export async function handlePlanStatus(args) {
237
241
  return { branches: 0, worktrees: 0, stashes: 0 };
238
242
  }
239
243
  })(),
244
+ loadTokenWastePolicy(args.projectPath)
245
+ .then((loaded) => loaded.policy)
246
+ .catch(() => null),
240
247
  ]);
241
248
  // SPEC-1012: Detect stale release status (best-effort, non-blocking)
242
249
  let staleStatusWarnings = [];
@@ -252,6 +259,12 @@ export async function handlePlanStatus(args) {
252
259
  const sessionTip = sessionAgeMinutes !== null && sessionAgeMinutes > SESSION_TIP_THRESHOLD_MIN
253
260
  ? `Last checkpoint ${String(sessionAgeMinutes)}min ago — run session_handoff then start a fresh session to cut token costs`
254
261
  : undefined;
262
+ const tokenWasteThresholdMin = tokenWastePolicy?.context.staleSessionMinutes;
263
+ const tokenWasteHint = tokenWasteThresholdMin !== undefined &&
264
+ sessionAgeMinutes !== null &&
265
+ sessionAgeMinutes > tokenWasteThresholdMin
266
+ ? `checkpoint is stale; package or hand off context before continuing`
267
+ : undefined;
255
268
  const workMode = planuConfig?.workMode;
256
269
  const updateBanner = consumeUpdateBanner() ?? undefined;
257
270
  const text = buildOutput({
@@ -263,6 +276,7 @@ export async function handlePlanStatus(args) {
263
276
  updateBanner,
264
277
  autoCompleted,
265
278
  sessionTip,
279
+ tokenWasteHint,
266
280
  });
267
281
  // SPEC-256: Always show /btw hint as educational tip (no context percent available from MCP layer)
268
282
  const DEMO_CONTEXT_PERCENT = 65;
@@ -286,6 +300,7 @@ export async function handlePlanStatus(args) {
286
300
  lastAuditAt: driftScore?.lastAuditAt ?? null,
287
301
  ...(autoCompleted.length > 0 ? { autoCompleted } : {}),
288
302
  ...(sessionTip !== undefined ? { sessionTip } : {}),
303
+ ...(tokenWasteHint !== undefined ? { tokenWasteHint } : {}),
289
304
  ...(btwHint !== undefined ? { btw_hint: btwHint } : {}),
290
305
  ...(updateBanner !== undefined ? { update_banner: updateBanner } : {}),
291
306
  // SPEC-751: pending cleanup counters
@@ -1,5 +1,6 @@
1
1
  import { getEntries, getAggregation } from '../storage/token-ledger-store.js';
2
2
  import { hashProjectPath } from '../storage/base-store.js';
3
+ import { buildTokenWasteReport, detectTokenWasteLoops, formatTokenWasteReport, loadTokenWastePolicy, recommendRelevantTools, } from '../engine/token-optimizer/index.js';
3
4
  import { join } from 'node:path';
4
5
  // ---------------------------------------------------------------------------
5
6
  // Constants
@@ -348,6 +349,47 @@ function renderTrends(entries, period) {
348
349
  }
349
350
  return lines.join('\n');
350
351
  }
352
+ async function renderAutopilot(entries, agg, period, projectPath) {
353
+ const { policy } = await loadTokenWastePolicy(projectPath);
354
+ if (!policy.enabled) {
355
+ return [
356
+ '# Token Intelligence Dashboard — Autopilot View',
357
+ '',
358
+ `**Period:** ${periodLabel(period)}`,
359
+ '',
360
+ 'Token Waste Autopilot is disabled by policy.',
361
+ '',
362
+ ].join('\n');
363
+ }
364
+ const loops = detectTokenWasteLoops(entries.map((entry) => ({
365
+ type: 'tool',
366
+ key: entry.toolName,
367
+ timestamp: entry.timestamp,
368
+ changedSincePrevious: false,
369
+ })), policy, 'token_intelligence');
370
+ const tools = recommendRelevantTools({
371
+ action: 'token_intelligence',
372
+ intentSignals: ['token', 'status', 'budget'],
373
+ tools: Object.keys(agg.tokensByTool).map((name) => ({ name })),
374
+ policy,
375
+ });
376
+ const report = buildTokenWasteReport({
377
+ action: 'token_intelligence',
378
+ policy,
379
+ loops,
380
+ tools,
381
+ });
382
+ return [
383
+ '# Token Intelligence Dashboard — Autopilot View',
384
+ '',
385
+ `**Period:** ${periodLabel(period)}`,
386
+ '',
387
+ `Analyzed ${String(entries.length)} ledger entries across ${String(agg.uniqueTools)} tools.`,
388
+ '',
389
+ formatTokenWasteReport(report),
390
+ '',
391
+ ].join('\n');
392
+ }
351
393
  // ---------------------------------------------------------------------------
352
394
  // Empty state
353
395
  // ---------------------------------------------------------------------------
@@ -387,7 +429,7 @@ export async function handleTokenIntelligence(input) {
387
429
  getEntries(projectHash, dataDir, filter),
388
430
  getAggregation(projectHash, dataDir, filter),
389
431
  ]);
390
- if (entries.length === 0 && view !== 'reconciliation') {
432
+ if (entries.length === 0 && view !== 'reconciliation' && view !== 'autopilot') {
391
433
  return { content: [{ type: 'text', text: renderEmpty(view, period) }] };
392
434
  }
393
435
  let text;
@@ -404,6 +446,9 @@ export async function handleTokenIntelligence(input) {
404
446
  case 'trends':
405
447
  text = renderTrends(entries, period);
406
448
  break;
449
+ case 'autopilot':
450
+ text = await renderAutopilot(entries, agg, period, projectPath);
451
+ break;
407
452
  default:
408
453
  text = renderSummary(agg, entries, period, groupBy);
409
454
  break;
@@ -79,6 +79,7 @@ export * from './runtime-security.js';
79
79
  export * from './workers.js';
80
80
  export * from './orchestration-runtime.js';
81
81
  export * from './token-optimization.js';
82
+ export * from './token-waste-autopilot.js';
82
83
  export * from './llm-providers.js';
83
84
  export * from './plugins.js';
84
85
  export * from './github.js';
@@ -80,6 +80,7 @@ export * from './runtime-security.js';
80
80
  export * from './workers.js';
81
81
  export * from './orchestration-runtime.js';
82
82
  export * from './token-optimization.js';
83
+ export * from './token-waste-autopilot.js';
83
84
  export * from './llm-providers.js';
84
85
  export * from './plugins.js';
85
86
  export * from './github.js';
@@ -0,0 +1,142 @@
1
+ export type TokenWasteDecisionKind = 'include' | 'summarize' | 'exclude' | 'recommend' | 'avoid' | 'warn' | 'override';
2
+ export type TokenWasteConfidence = 'high' | 'medium' | 'low';
3
+ export type TokenWasteAction = string;
4
+ export interface TokenWastePolicyLoadOptions {
5
+ defaultPolicyPath?: string;
6
+ }
7
+ export interface TokenWasteEvidence {
8
+ source: 'policy' | 'project-config' | 'runtime' | 'spec' | 'tool-registry' | 'host-capability';
9
+ key: string;
10
+ value?: string | number | boolean;
11
+ }
12
+ export interface TokenWasteDecision {
13
+ decision: TokenWasteDecisionKind;
14
+ target: string;
15
+ reason: string;
16
+ evidence: TokenWasteEvidence[];
17
+ confidence: TokenWasteConfidence;
18
+ }
19
+ export interface TokenWasteOutputStrategy {
20
+ maxLines: number;
21
+ keepFailures?: boolean;
22
+ uniqueOnly?: boolean;
23
+ summarizeKeys?: boolean;
24
+ }
25
+ export interface TokenWastePolicy {
26
+ version: 1;
27
+ enabled: boolean;
28
+ context: {
29
+ generatedPatterns: string[];
30
+ stalePatterns: string[];
31
+ summarizePatterns: string[];
32
+ staleSessionMinutes: number;
33
+ safetyOverrideActions: string[];
34
+ };
35
+ outputs: {
36
+ strategies: Record<string, TokenWasteOutputStrategy>;
37
+ };
38
+ tools: {
39
+ groups: Record<string, string[]>;
40
+ maxRecommended: number;
41
+ };
42
+ loops: {
43
+ thresholds: Record<string, number>;
44
+ safetyOverrideActions: string[];
45
+ };
46
+ modelEffort: {
47
+ lowRisk: string;
48
+ default: string;
49
+ highRisk: string;
50
+ maxRisk: string;
51
+ };
52
+ redaction: {
53
+ maxSnippetChars: number;
54
+ redactPatterns: string[];
55
+ };
56
+ }
57
+ export interface LoadedTokenWastePolicy {
58
+ policy: TokenWastePolicy;
59
+ evidence: TokenWasteEvidence[];
60
+ }
61
+ export interface ContextCandidate {
62
+ path: string;
63
+ reason?: string;
64
+ }
65
+ export interface ContextPreflightInput {
66
+ action: TokenWasteAction;
67
+ policy: TokenWastePolicy;
68
+ candidates: ContextCandidate[];
69
+ spec?: {
70
+ id?: string;
71
+ status?: string;
72
+ risk?: string;
73
+ target?: string;
74
+ };
75
+ }
76
+ export interface ContextPreflightResult {
77
+ include: TokenWasteDecision[];
78
+ summarize: TokenWasteDecision[];
79
+ exclude: TokenWasteDecision[];
80
+ }
81
+ export interface VerboseOutputInput {
82
+ kind: string;
83
+ text: string;
84
+ policy: TokenWastePolicy;
85
+ fullOutputRef?: string;
86
+ }
87
+ export interface VerboseOutputResult {
88
+ text: string;
89
+ originalLines: number;
90
+ returnedLines: number;
91
+ fullOutputRef?: string;
92
+ decisions: TokenWasteDecision[];
93
+ }
94
+ export interface ToolRelevanceInput {
95
+ action: TokenWasteAction;
96
+ intentSignals: string[];
97
+ tools: {
98
+ name: string;
99
+ description?: string;
100
+ }[];
101
+ policy: TokenWastePolicy;
102
+ hostUnsupportedTools?: string[];
103
+ }
104
+ export interface ToolRelevanceResult {
105
+ recommended: TokenWasteDecision[];
106
+ avoided: TokenWasteDecision[];
107
+ }
108
+ export interface TokenWasteRuntimeEvent {
109
+ type: string;
110
+ key: string;
111
+ timestamp?: string;
112
+ changedSincePrevious?: boolean;
113
+ }
114
+ export interface TokenWasteLoopResult {
115
+ warnings: TokenWasteDecision[];
116
+ }
117
+ export interface TokenWasteModelAdvice {
118
+ effort: string;
119
+ reason: string;
120
+ evidence: TokenWasteEvidence[];
121
+ }
122
+ export interface TokenWasteAutopilotInput {
123
+ action: TokenWasteAction;
124
+ policy: TokenWastePolicy;
125
+ context?: ContextPreflightResult;
126
+ output?: VerboseOutputResult;
127
+ tools?: ToolRelevanceResult;
128
+ loops?: TokenWasteLoopResult;
129
+ spec?: {
130
+ id?: string;
131
+ risk?: string;
132
+ scope?: string;
133
+ };
134
+ }
135
+ export interface TokenWasteReport {
136
+ actionsTaken: TokenWasteDecision[];
137
+ recommendations: TokenWasteDecision[];
138
+ risks: TokenWasteDecision[];
139
+ evidence: TokenWasteEvidence[];
140
+ modelEffort: TokenWasteModelAdvice;
141
+ }
142
+ //# sourceMappingURL=token-waste-autopilot.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=token-waste-autopilot.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.5.0",
3
+ "version": "4.6.0",
4
4
  "description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,14 +34,14 @@
34
34
  "packageName": "@planu/core"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@planu/core-darwin-arm64": "4.5.0",
38
- "@planu/core-darwin-x64": "4.5.0",
39
- "@planu/core-linux-arm64-gnu": "4.5.0",
40
- "@planu/core-linux-arm64-musl": "4.5.0",
41
- "@planu/core-linux-x64-gnu": "4.5.0",
42
- "@planu/core-linux-x64-musl": "4.5.0",
43
- "@planu/core-win32-arm64-msvc": "4.5.0",
44
- "@planu/core-win32-x64-msvc": "4.5.0"
37
+ "@planu/core-darwin-arm64": "4.6.0",
38
+ "@planu/core-darwin-x64": "4.6.0",
39
+ "@planu/core-linux-arm64-gnu": "4.6.0",
40
+ "@planu/core-linux-arm64-musl": "4.6.0",
41
+ "@planu/core-linux-x64-gnu": "4.6.0",
42
+ "@planu/core-linux-x64-musl": "4.6.0",
43
+ "@planu/core-win32-arm64-msvc": "4.6.0",
44
+ "@planu/core-win32-x64-msvc": "4.6.0"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -182,7 +182,7 @@
182
182
  "@stryker-mutator/core": "^9.6.1",
183
183
  "@stryker-mutator/vitest-runner": "^9.6.1",
184
184
  "@supabase/supabase-js": "^2.108.1",
185
- "@types/node": "^25.9.2",
185
+ "@types/node": "^25.9.3",
186
186
  "@vitejs/plugin-vue": "^6.0.7",
187
187
  "@vitest/coverage-v8": "^4.1.8",
188
188
  "@vue/test-utils": "^2.4.11",
@@ -205,6 +205,6 @@
205
205
  "typescript-eslint": "^8.61.0",
206
206
  "vite": "^8.0.16",
207
207
  "vitest": "^4.1.8",
208
- "vue": "^3.5.35"
208
+ "vue": "^3.5.38"
209
209
  }
210
210
  }
package/planu-native.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "4.5.0",
4
+ "version": "4.6.0",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
package/planu-plugin.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dev.planu.cli",
3
3
  "displayName": "Planu — Spec Driven Development",
4
4
  "description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
5
- "version": "4.5.0",
5
+ "version": "4.6.0",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": [
8
8
  "npx",