@planu/cli 3.9.8 → 3.9.10
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/dist/engine/handoff-packager.js +3 -6
- package/dist/engine/host-rules-templates/templates.js +1 -1
- package/dist/tools/init-project/conventions-writer.js +1 -1
- package/dist/tools/init-project/helpers.js +1 -1
- package/dist/tools/package-handoff.js +2 -2
- package/dist/types/data/estimation.d.ts +147 -0
- package/dist/types/data/estimation.js +2 -0
- package/dist/types/data/index.d.ts +5 -0
- package/dist/types/data/index.js +6 -0
- package/dist/types/data/velocity.d.ts +168 -0
- package/dist/types/data/velocity.js +4 -0
- package/package.json +7 -7
|
@@ -175,7 +175,7 @@ function extractOutOfScope(huContent, spec) {
|
|
|
175
175
|
return [
|
|
176
176
|
`Changes beyond the ${spec.target} target layer`,
|
|
177
177
|
'Refactors not explicitly requested in acceptance criteria',
|
|
178
|
-
'New dependencies not listed in
|
|
178
|
+
'New dependencies not listed in spec.md ## Technical',
|
|
179
179
|
];
|
|
180
180
|
}
|
|
181
181
|
return outOfScopeItems;
|
|
@@ -203,12 +203,9 @@ function buildConstraints(knowledge) {
|
|
|
203
203
|
export async function packageHandoff(spec, knowledge) {
|
|
204
204
|
const warnings = [];
|
|
205
205
|
const huContent = await safeReadFile(spec.specPath);
|
|
206
|
-
const fichaContent =
|
|
206
|
+
const fichaContent = huContent;
|
|
207
207
|
if (!huContent) {
|
|
208
|
-
warnings.push('
|
|
209
|
-
}
|
|
210
|
-
if (!fichaContent) {
|
|
211
|
-
warnings.push('FICHA-TECNICA.md not found — file lists may be incomplete. Run create_spec_tech to generate it.');
|
|
208
|
+
warnings.push('spec.md not found — using minimal package. Run create_spec to generate it.');
|
|
212
209
|
}
|
|
213
210
|
const objective = extractObjective(huContent, spec);
|
|
214
211
|
const criteria = extractCriteria(huContent);
|
|
@@ -64,7 +64,7 @@ function buildSharedRules() {
|
|
|
64
64
|
'- Every tool result ends with what was DONE, not what SHOULD be done',
|
|
65
65
|
'',
|
|
66
66
|
'### Spec docs — mandatory format',
|
|
67
|
-
"- **Language**: `spec.md`
|
|
67
|
+
"- **Language**: `spec.md` must always be in **English**, no exceptions. Tool response narratives adapt to the user's locale; spec docs do not.",
|
|
68
68
|
'- **Clean title**: never include the spec ID in the title — it is already in the `id` field.',
|
|
69
69
|
'',
|
|
70
70
|
'### Output quality',
|
|
@@ -26,7 +26,7 @@ function buildConventionsContent(knowledge) {
|
|
|
26
26
|
if (knowledge.projectPath) {
|
|
27
27
|
lines.push(`- Project root: \`${knowledge.projectPath}\``);
|
|
28
28
|
}
|
|
29
|
-
lines.push('- Source: `src/`', '- Tests: `tests/` (mirrors `src/` structure)', '- Specs: `planu/specs/`', '', '## Code Style', '', '- Prefer explicit return types on all functions', '- No magic numbers — use named constants', '- Error handling: return `ToolResult` with `isError: true`, never throw from handlers', '', '## Spec Conventions', '', '- One spec per feature/bugfix', '- ACs must be testable: GIVEN/WHEN/THEN or measurable condition', '- spec.md
|
|
29
|
+
lines.push('- Source: `src/`', '- Tests: `tests/` (mirrors `src/` structure)', '- Specs: `planu/specs/`', '', '## Code Style', '', '- Prefer explicit return types on all functions', '- No magic numbers — use named constants', '- Error handling: return `ToolResult` with `isError: true`, never throw from handlers', '', '## Spec Conventions', '', '- One spec per feature/bugfix', '- ACs must be testable: GIVEN/WHEN/THEN or measurable condition', '- spec.md always in English', '- Technical planning belongs in `spec.md` under `## Technical`; do not create `technical.md`');
|
|
30
30
|
return lines.join('\n') + '\n';
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
@@ -206,7 +206,7 @@ This project uses **Spec-Driven Development** managed by [Planu](https://planu.d
|
|
|
206
206
|
|
|
207
207
|
**Key commands:** \`init_project\` → \`create_spec\` → (user approves) → implement → \`validate\`
|
|
208
208
|
|
|
209
|
-
Specs live in \`planu/specs/SPEC-XXX-name
|
|
209
|
+
Specs live in \`planu/specs/SPEC-XXX-name/spec.md\`. Technical planning, file ownership, and progress live inline in \`## Technical\`, \`## Files\`, and \`## Progress\` sections.
|
|
210
210
|
${SDD_MARKER}
|
|
211
211
|
`.trimStart();
|
|
212
212
|
/**
|
|
@@ -31,7 +31,7 @@ function formatHandoff(pkg) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
|
-
lines.push('_(No criteria found — check
|
|
34
|
+
lines.push('_(No criteria found — check spec.md acceptance criteria)_');
|
|
35
35
|
}
|
|
36
36
|
lines.push('');
|
|
37
37
|
lines.push('## Files to Modify');
|
|
@@ -42,7 +42,7 @@ function formatHandoff(pkg) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
lines.push('_(No files identified — check
|
|
45
|
+
lines.push('_(No files identified — check spec.md ## Technical / ## Files)_');
|
|
46
46
|
}
|
|
47
47
|
lines.push('');
|
|
48
48
|
lines.push('## Files to Create');
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ExecutionMode, Difficulty } from '../common/index.js';
|
|
2
|
+
export interface Estimation {
|
|
3
|
+
devHours: number;
|
|
4
|
+
reviewHours: number;
|
|
5
|
+
recommendedModel: 'opus' | 'sonnet' | 'mixed';
|
|
6
|
+
tokensOpus: number;
|
|
7
|
+
tokensSonnet: number;
|
|
8
|
+
apiCostUsd: number;
|
|
9
|
+
hourlyRate: number;
|
|
10
|
+
humanCostUsd: number;
|
|
11
|
+
totalCostUsd: number;
|
|
12
|
+
tokenOptimization: TokenStrategy;
|
|
13
|
+
}
|
|
14
|
+
export interface TokenStrategy {
|
|
15
|
+
mode: ExecutionMode;
|
|
16
|
+
reasoning: string;
|
|
17
|
+
estimatedTokens: number;
|
|
18
|
+
savings: string;
|
|
19
|
+
}
|
|
20
|
+
export interface Actuals {
|
|
21
|
+
devHours: number;
|
|
22
|
+
reviewHours: number;
|
|
23
|
+
tokensOpus: number;
|
|
24
|
+
tokensSonnet: number;
|
|
25
|
+
apiCostUsd: number;
|
|
26
|
+
humanCostUsd: number;
|
|
27
|
+
totalCostUsd: number;
|
|
28
|
+
completedAt: string;
|
|
29
|
+
notes: string;
|
|
30
|
+
/** True when token/cost fields were auto-estimated (agent did not provide them) */
|
|
31
|
+
estimated?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ImpactAnalysis {
|
|
34
|
+
affectedModules: string[];
|
|
35
|
+
affectedFiles: string[];
|
|
36
|
+
breakingChanges: boolean;
|
|
37
|
+
requiresMigration: boolean;
|
|
38
|
+
migrationReversible: boolean;
|
|
39
|
+
requiresFeatureFlag: boolean;
|
|
40
|
+
rollbackPlan: string;
|
|
41
|
+
testingStrategy: TestStrategy;
|
|
42
|
+
environments: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface TestStrategy {
|
|
45
|
+
unitTests: string[];
|
|
46
|
+
integrationTests: string[];
|
|
47
|
+
e2eTests: string[];
|
|
48
|
+
manualTests: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface EstimateInput {
|
|
51
|
+
specId: string;
|
|
52
|
+
projectId: string;
|
|
53
|
+
}
|
|
54
|
+
export interface ReverseEngineerInput {
|
|
55
|
+
path: string;
|
|
56
|
+
projectId: string;
|
|
57
|
+
depth?: 'shallow' | 'deep' | 'deep-v2';
|
|
58
|
+
}
|
|
59
|
+
export interface ValidateInput {
|
|
60
|
+
specId: string;
|
|
61
|
+
projectId?: string;
|
|
62
|
+
projectPath?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface EstimateResult {
|
|
65
|
+
difficulty: Difficulty;
|
|
66
|
+
estimation: Estimation;
|
|
67
|
+
confidence: 'low' | 'medium' | 'high';
|
|
68
|
+
reasoning: string;
|
|
69
|
+
similarSpecs: string[];
|
|
70
|
+
}
|
|
71
|
+
export interface ReverseEngineerResult {
|
|
72
|
+
specId: string;
|
|
73
|
+
specPath: string;
|
|
74
|
+
technicalPath: string;
|
|
75
|
+
analysis: {
|
|
76
|
+
filesAnalyzed: number;
|
|
77
|
+
interfacesDetected: string[];
|
|
78
|
+
dependenciesFound: string[];
|
|
79
|
+
layersIdentified: string[];
|
|
80
|
+
};
|
|
81
|
+
message: string;
|
|
82
|
+
}
|
|
83
|
+
export interface TimeRecord {
|
|
84
|
+
specId: string;
|
|
85
|
+
implementingStartedAt?: string;
|
|
86
|
+
doneAt?: string;
|
|
87
|
+
actualHours?: number;
|
|
88
|
+
debugHours?: number;
|
|
89
|
+
scopeCreepCriteria?: number;
|
|
90
|
+
validateFailures?: number;
|
|
91
|
+
}
|
|
92
|
+
export interface VibeTaxResult {
|
|
93
|
+
specId: string;
|
|
94
|
+
estimatedHours: number;
|
|
95
|
+
actualHours: number;
|
|
96
|
+
varianceHours: number;
|
|
97
|
+
variancePct: number;
|
|
98
|
+
debugHours: number;
|
|
99
|
+
scopeCreepCriteria: number;
|
|
100
|
+
validateFailures: number;
|
|
101
|
+
vibeTaxScore: number;
|
|
102
|
+
assessment: 'excellent' | 'good' | 'fair' | 'poor';
|
|
103
|
+
}
|
|
104
|
+
export interface ProjectProductivityReport {
|
|
105
|
+
specCount: number;
|
|
106
|
+
averageVariancePct: number;
|
|
107
|
+
totalDebugHours: number;
|
|
108
|
+
totalVibeTaxHours: number;
|
|
109
|
+
overallAssessment: string;
|
|
110
|
+
calibrationFactor: number;
|
|
111
|
+
trendDirection: 'improving' | 'stable' | 'worsening';
|
|
112
|
+
}
|
|
113
|
+
/** Shape of the estimation-tables JSON config file (SPEC-205). */
|
|
114
|
+
export interface EstimationTablesConfig {
|
|
115
|
+
defaultConfig: {
|
|
116
|
+
defaultLocale: string;
|
|
117
|
+
defaultExperienceLevel: string;
|
|
118
|
+
hourlyRate: number;
|
|
119
|
+
pricingOpusPerMToken: number;
|
|
120
|
+
pricingSonnetPerMToken: number;
|
|
121
|
+
};
|
|
122
|
+
baseHoursByType: {
|
|
123
|
+
id: string;
|
|
124
|
+
hours: number;
|
|
125
|
+
}[];
|
|
126
|
+
scopeMultiplier: {
|
|
127
|
+
id: string;
|
|
128
|
+
multiplier: number;
|
|
129
|
+
}[];
|
|
130
|
+
difficultyMultiplier: {
|
|
131
|
+
id: string;
|
|
132
|
+
multiplier: number;
|
|
133
|
+
}[];
|
|
134
|
+
reviewRatio: {
|
|
135
|
+
id: string;
|
|
136
|
+
ratio: number;
|
|
137
|
+
}[];
|
|
138
|
+
tokensPerDevHour: {
|
|
139
|
+
opus: number;
|
|
140
|
+
sonnet: number;
|
|
141
|
+
};
|
|
142
|
+
modeReduction: {
|
|
143
|
+
id: string;
|
|
144
|
+
factor: number;
|
|
145
|
+
}[];
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=estimation.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { SpecScope, SpecType } from '../common/primitives.js';
|
|
2
|
+
/**
|
|
3
|
+
* A single week's throughput data point.
|
|
4
|
+
* weekLabel: ISO week string, e.g. "2026-W12"
|
|
5
|
+
*/
|
|
6
|
+
export interface ThroughputPoint {
|
|
7
|
+
weekLabel: string;
|
|
8
|
+
weekStart: string;
|
|
9
|
+
weekEnd: string;
|
|
10
|
+
specsCompleted: number;
|
|
11
|
+
specIds: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Lead time statistics grouped by scope.
|
|
15
|
+
* Lead time = done timestamp - created timestamp (in hours).
|
|
16
|
+
*/
|
|
17
|
+
export interface LeadTimeStats {
|
|
18
|
+
avgHours: number;
|
|
19
|
+
medianHours: number;
|
|
20
|
+
minHours: number;
|
|
21
|
+
maxHours: number;
|
|
22
|
+
/** Number of specs used to compute these stats */
|
|
23
|
+
sampleSize: number;
|
|
24
|
+
/** Breakdown by scope */
|
|
25
|
+
byScope: Record<SpecScope, LeadTimeScopeStats>;
|
|
26
|
+
}
|
|
27
|
+
export interface LeadTimeScopeStats {
|
|
28
|
+
avgHours: number;
|
|
29
|
+
sampleSize: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cycle time statistics.
|
|
33
|
+
* Cycle time = done timestamp - implementing timestamp (in hours).
|
|
34
|
+
*/
|
|
35
|
+
export interface CycleTimeStats {
|
|
36
|
+
avgHours: number;
|
|
37
|
+
medianHours: number;
|
|
38
|
+
sampleSize: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A single burndown data point for one week.
|
|
42
|
+
*/
|
|
43
|
+
export interface BurndownPoint {
|
|
44
|
+
weekLabel: string;
|
|
45
|
+
weekStart: string;
|
|
46
|
+
weekEnd: string;
|
|
47
|
+
/** Specs completed (done) up to and including this week */
|
|
48
|
+
completedCumulative: number;
|
|
49
|
+
/** Specs completed in this specific week */
|
|
50
|
+
completedThisWeek: number;
|
|
51
|
+
/** Specs created up to and including this week */
|
|
52
|
+
createdCumulative: number;
|
|
53
|
+
/** Backlog: total created - total completed */
|
|
54
|
+
backlog: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Flow efficiency: time spent in active states vs total lead time.
|
|
58
|
+
*/
|
|
59
|
+
export interface FlowEfficiencyStats {
|
|
60
|
+
/** 0.0-1.0 ratio of active time to total time */
|
|
61
|
+
efficiency: number;
|
|
62
|
+
/** Average hours spent in active states (implementing, review) */
|
|
63
|
+
avgActiveHours: number;
|
|
64
|
+
/** Average total lead time hours */
|
|
65
|
+
avgTotalHours: number;
|
|
66
|
+
sampleSize: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* WIP (Work In Progress) snapshot.
|
|
70
|
+
*/
|
|
71
|
+
export interface WipSnapshot {
|
|
72
|
+
current: number;
|
|
73
|
+
specIds: string[];
|
|
74
|
+
avgAgeHours: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Full velocity report for a project.
|
|
78
|
+
*/
|
|
79
|
+
export interface VelocityReport {
|
|
80
|
+
projectId: string;
|
|
81
|
+
projectPath: string;
|
|
82
|
+
generatedAt: string;
|
|
83
|
+
/** Number of weeks analyzed */
|
|
84
|
+
weeks: number;
|
|
85
|
+
/** Applied filters */
|
|
86
|
+
filters: VelocityFilters;
|
|
87
|
+
/** Average specs completed per week */
|
|
88
|
+
avgThroughputPerWeek: number;
|
|
89
|
+
throughput: ThroughputPoint[];
|
|
90
|
+
leadTime: LeadTimeStats | null;
|
|
91
|
+
cycleTime: CycleTimeStats | null;
|
|
92
|
+
wip: WipSnapshot;
|
|
93
|
+
flowEfficiency: FlowEfficiencyStats | null;
|
|
94
|
+
burndown: BurndownPoint[];
|
|
95
|
+
/** Human-readable summary line */
|
|
96
|
+
summary: string;
|
|
97
|
+
/** Total specs done in the period */
|
|
98
|
+
totalDone: number;
|
|
99
|
+
/** Total specs in the project */
|
|
100
|
+
totalSpecs: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Trend comparison: last N weeks vs previous N weeks.
|
|
104
|
+
*/
|
|
105
|
+
export interface VelocityTrend {
|
|
106
|
+
projectId: string;
|
|
107
|
+
projectPath: string;
|
|
108
|
+
generatedAt: string;
|
|
109
|
+
compareWeeks: number;
|
|
110
|
+
recent: VelocityPeriodStats;
|
|
111
|
+
previous: VelocityPeriodStats;
|
|
112
|
+
throughputDelta: number;
|
|
113
|
+
leadTimeDelta: number | null;
|
|
114
|
+
trend: 'improving' | 'stable' | 'declining';
|
|
115
|
+
summary: string;
|
|
116
|
+
}
|
|
117
|
+
export interface VelocityPeriodStats {
|
|
118
|
+
label: string;
|
|
119
|
+
fromDate: string;
|
|
120
|
+
toDate: string;
|
|
121
|
+
avgThroughputPerWeek: number;
|
|
122
|
+
avgLeadTimeHours: number | null;
|
|
123
|
+
totalDone: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Input filters for velocity tools.
|
|
127
|
+
*/
|
|
128
|
+
export interface VelocityFilters {
|
|
129
|
+
weeks: number;
|
|
130
|
+
scope?: SpecScope;
|
|
131
|
+
type?: SpecType;
|
|
132
|
+
}
|
|
133
|
+
/** Input args for velocity_report tool */
|
|
134
|
+
export interface VelocityReportInput {
|
|
135
|
+
projectPath: string;
|
|
136
|
+
weeks?: number;
|
|
137
|
+
scope?: SpecScope;
|
|
138
|
+
type?: SpecType;
|
|
139
|
+
}
|
|
140
|
+
/** Input args for velocity_trend tool */
|
|
141
|
+
export interface VelocityTrendInput {
|
|
142
|
+
projectPath: string;
|
|
143
|
+
compareWeeks?: number;
|
|
144
|
+
scope?: SpecScope;
|
|
145
|
+
type?: SpecType;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Velocity profile derived from recent spec actuals.
|
|
149
|
+
* Used to convert devHours estimates into real calendar days.
|
|
150
|
+
* SPEC-555
|
|
151
|
+
*/
|
|
152
|
+
export interface VelocityProfile {
|
|
153
|
+
/** SPECs completed per calendar day (in the window) */
|
|
154
|
+
specsPerDay: number;
|
|
155
|
+
/** Productive dev hours per calendar day */
|
|
156
|
+
hoursPerDay: number;
|
|
157
|
+
/** Average actual devHours by difficulty (1-5) */
|
|
158
|
+
avgHoursByDifficulty: Record<1 | 2 | 3 | 4 | 5, number>;
|
|
159
|
+
/** ISO date of last calculation */
|
|
160
|
+
calibrationDate: string;
|
|
161
|
+
/** Window used to calculate (default 30 days) */
|
|
162
|
+
windowDays: number;
|
|
163
|
+
/** Number of done specs used for calibration */
|
|
164
|
+
sampleSize: number;
|
|
165
|
+
/** Confidence based on sample size: high >20, medium 5-20, low <5 */
|
|
166
|
+
confidence: 'high' | 'medium' | 'low';
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=velocity.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.10",
|
|
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",
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"packageName": "@planu/core"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@planu/core-darwin-arm64": "3.9.
|
|
36
|
-
"@planu/core-darwin-x64": "3.9.
|
|
37
|
-
"@planu/core-linux-arm64-gnu": "3.9.
|
|
38
|
-
"@planu/core-linux-arm64-musl": "3.9.
|
|
39
|
-
"@planu/core-linux-x64-gnu": "3.9.
|
|
40
|
-
"@planu/core-linux-x64-musl": "3.9.
|
|
35
|
+
"@planu/core-darwin-arm64": "3.9.10",
|
|
36
|
+
"@planu/core-darwin-x64": "3.9.10",
|
|
37
|
+
"@planu/core-linux-arm64-gnu": "3.9.10",
|
|
38
|
+
"@planu/core-linux-arm64-musl": "3.9.10",
|
|
39
|
+
"@planu/core-linux-x64-gnu": "3.9.10",
|
|
40
|
+
"@planu/core-linux-x64-musl": "3.9.10"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=24.0.0"
|