@planu/cli 5.3.62 → 5.3.63

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/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## [5.3.63] - 2026-08-27
2
+
3
+ ### Bug Fixes
4
+ - fix(SPEC-1644): stop emitting the default architecture rule and every line-cap instruction
5
+ - fix(SPEC-1643): remove the empty typescript-patterns builtin skill
6
+ - fix(SPEC-1371): compute branch cleanup against main with literal protected matching
7
+ - fix(SPEC-1646): discriminate contradiction findings by offending criterion
8
+ - fix(SPEC-1646): discriminate contradiction findings by offending criterion
9
+ - fix(SPEC-1645): widen the load-bearing separator class per dual review
10
+ - fix(SPEC-1645): stop treating load-bearing as a scale signal
11
+
12
+ ### Chores
13
+ - chore(planu): file goal stop-hook loop dogfood bug
14
+ - chore(SPEC-1644): record done transition
15
+ - chore(planu): file SPEC-1651 and SPEC-1652 dogfood bugs
16
+ - chore(SPEC-1644): record approved status
17
+ - chore(SPEC-1643): mark acceptance criteria done
18
+ - chore(SPEC-1643): lifecycle transition to approved
19
+ - chore(planu): record SPEC-1371 approval and SPEC-1649 filing
20
+ - chore(SPEC-1646): record approval transition
21
+ - chore(SPEC-1645): record done transition
22
+
23
+
1
24
  ## [5.3.62] - 2026-08-26
2
25
 
3
26
  ### Bug Fixes
@@ -1 +1 @@
1
- {"schemaVersion":1,"commit":"653317c22ebdf21ad6b7f82f5d86dd042b05aaab"}
1
+ {"schemaVersion":1,"commit":"ae29e281086a9333b51c28dc6fd1c297538efe4d"}
@@ -27,11 +27,9 @@ export const ARCHITECTURE_RULES = {
27
27
  ],
28
28
  conventions: [
29
29
  'Never put business logic in route handlers',
30
- 'Route handlers should be under 30 lines',
31
30
  'Services are framework-agnostic (no NextRequest/NextResponse)',
32
31
  'Repositories return plain objects, never ORM entities',
33
32
  ],
34
- maxLinesPerHandler: 30,
35
33
  },
36
34
  express: {
37
35
  title: 'Express Architecture',
@@ -58,7 +56,6 @@ export const ARCHITECTURE_RULES = {
58
56
  'Services are stateless',
59
57
  'One file per resource',
60
58
  ],
61
- maxLinesPerHandler: 40,
62
59
  },
63
60
  fastapi: {
64
61
  title: 'FastAPI Architecture',
@@ -85,34 +82,6 @@ export const ARCHITECTURE_RULES = {
85
82
  'Pydantic models for all I/O',
86
83
  'Async by default',
87
84
  ],
88
- maxLinesPerHandler: 30,
89
- },
90
- default: {
91
- title: 'Clean Architecture',
92
- description: 'Generic clean architecture rules',
93
- layers: [
94
- {
95
- name: 'Handler',
96
- path: 'src/handlers/',
97
- responsibility: 'Input/output boundary',
98
- },
99
- {
100
- name: 'Service',
101
- path: 'src/services/',
102
- responsibility: 'Business logic',
103
- },
104
- {
105
- name: 'Repository',
106
- path: 'src/repositories/',
107
- responsibility: 'Data access',
108
- },
109
- ],
110
- conventions: [
111
- 'Separate concerns by layer',
112
- 'Dependencies point inward',
113
- 'No framework code in business logic',
114
- ],
115
- maxLinesPerHandler: 50,
116
85
  },
117
86
  };
118
87
  /** Detect stack from project files. Returns a key into ARCHITECTURE_RULES. */
@@ -187,9 +156,6 @@ export function generateRulesContent(rule, tool) {
187
156
  '## Conventions',
188
157
  conventions,
189
158
  '',
190
- `## Handler Size Limit`,
191
- `Max ${rule.maxLinesPerHandler} lines per handler/controller.`,
192
- '',
193
159
  ].join('\n');
194
160
  }
195
161
  case 'copilot': {
@@ -205,9 +171,6 @@ export function generateRulesContent(rule, tool) {
205
171
  '## Coding Conventions',
206
172
  conventions,
207
173
  '',
208
- `## Size Constraints`,
209
- `- Max ${rule.maxLinesPerHandler} lines per handler`,
210
- '',
211
174
  ].join('\n');
212
175
  }
213
176
  case 'aider': {
@@ -223,9 +186,6 @@ export function generateRulesContent(rule, tool) {
223
186
  '## Conventions',
224
187
  conventions,
225
188
  '',
226
- `## Constraints`,
227
- `- Handlers/controllers: max ${rule.maxLinesPerHandler} lines`,
228
- '',
229
189
  ].join('\n');
230
190
  }
231
191
  case 'claude':
@@ -244,11 +204,6 @@ export function generateRulesContent(rule, tool) {
244
204
  '',
245
205
  conventions,
246
206
  '',
247
- '## Size Budget',
248
- '',
249
- `- Max **${rule.maxLinesPerHandler} lines** per handler/controller`,
250
- '- Extract helpers when exceeding this limit',
251
- '',
252
207
  ].join('\n');
253
208
  }
254
209
  }
@@ -273,8 +228,10 @@ function resolveRulesFilePath(projectPath, tool, stackKey) {
273
228
  /** Main entry: detect stack + tools, generate all rules objects. */
274
229
  export function generateProjectRules(projectPath) {
275
230
  const stackKey = detectArchitectureStack(projectPath);
276
- const defaultRule = ARCHITECTURE_RULES.default;
277
- const rule = ARCHITECTURE_RULES[stackKey] ?? defaultRule;
231
+ if (stackKey === 'default') {
232
+ return [];
233
+ }
234
+ const rule = ARCHITECTURE_RULES[stackKey];
278
235
  if (!rule) {
279
236
  return [];
280
237
  }
@@ -79,35 +79,24 @@ function buildFrontmatter(entry) {
79
79
  /** Prepare a built-in skill from the local catalog, generating a SKILL.md. */
80
80
  function prepareFromBuiltIn(skillName) {
81
81
  const entry = getBuiltInSkillEntry(skillName);
82
- let content;
83
- if (entry) {
84
- const frontmatter = buildFrontmatter(entry);
85
- content = [
86
- frontmatter,
87
- `# ${entry.name}`,
88
- ``,
89
- entry.description,
90
- ``,
91
- `## When to use`,
92
- ``,
93
- entry.trigger,
94
- ``,
95
- `## Tags`,
96
- ``,
97
- entry.tags.join(', '),
98
- ].join('\n');
99
- }
100
- else {
101
- content = [
102
- `# ${skillName}`,
103
- ``,
104
- `Built-in Planu skill.`,
105
- ``,
106
- `## When to use`,
107
- ``,
108
- `Consult the Planu documentation.`,
109
- ].join('\n');
82
+ if (!entry) {
83
+ throw new Error(`Built-in skill '${skillName}' has no catalog entry; refusing to install a placeholder`);
110
84
  }
85
+ const frontmatter = buildFrontmatter(entry);
86
+ const content = [
87
+ frontmatter,
88
+ `# ${entry.name}`,
89
+ ``,
90
+ entry.description,
91
+ ``,
92
+ `## When to use`,
93
+ ``,
94
+ entry.trigger,
95
+ ``,
96
+ `## Tags`,
97
+ ``,
98
+ entry.tags.join(', '),
99
+ ].join('\n');
111
100
  return { content, hasScripts: false };
112
101
  }
113
102
  /** Prepare a skill placeholder for skillssh/agentskill sources (no direct file API yet). */
@@ -89,15 +89,6 @@ async function fetchSkillsShCatalog(stack, language) {
89
89
  function buildFallbackSkills(knowledge) {
90
90
  const skills = [];
91
91
  const stackLower = knowledge.stack.map((s) => s.toLowerCase());
92
- const lang = knowledge.language.toLowerCase();
93
- if (lang === technologyValue('technology-typescript-969545') || lang === 'javascript') {
94
- skills.push({
95
- name: 'typescript-patterns',
96
- description: 'TypeScript best practices, strict mode patterns, and type safety conventions',
97
- installCommand: 'npx skills add typescript-patterns',
98
- justification: `Project uses ${knowledge.language}`,
99
- });
100
- }
101
92
  if (stackLower.some((s) => s.includes(technologyValue('technology-react-275976')))) {
102
93
  skills.push({
103
94
  name: 'react-patterns',
@@ -1,4 +1,3 @@
1
- import { technologyValue } from './technology-registry.js';
2
1
  // engine/skills-reconciler.ts — SPEC-189: Detect stack changes and reconcile skills
3
2
  // SPEC-395: autoFixSkills — install missing, remove stale, repair corrupt paths.
4
3
  // SPEC-607: Detect old-format skills and new agents, trigger regeneration.
@@ -32,7 +31,7 @@ export async function reconcileSkills(projectPath, knowledge) {
32
31
  const newSkillsDetected = recommendedSkills
33
32
  .filter((s) => !installedSet.has(s.name))
34
33
  .map((s) => s.name);
35
- const staleSkills = installedNames.filter((name) => !recommendedNames.has(name) && !isProtectedBuiltInSkill(name, knowledge));
34
+ const staleSkills = installedNames.filter((name) => !recommendedNames.has(name));
36
35
  const totalRelevant = recommendedNames.size;
37
36
  const alreadyInSync = installedNames.filter((name) => recommendedNames.has(name)).length;
38
37
  const healthScore = computeHealthScore(totalRelevant, alreadyInSync, staleSkills.length);
@@ -167,17 +166,6 @@ function computeHealthScore(totalRelevant, alreadyInSync, staleCount) {
167
166
  const penalty = staleCount * 5;
168
167
  return Math.max(0, base - penalty);
169
168
  }
170
- function isProtectedBuiltInSkill(name, knowledge) {
171
- if (name !== 'typescript-patterns') {
172
- return false;
173
- }
174
- const language = knowledge.language.toLowerCase();
175
- const stack = knowledge.stack.map((item) => item.toLowerCase());
176
- return (language === technologyValue('technology-typescript-969545') ||
177
- language === 'javascript' ||
178
- stack.some((item) => item.includes(technologyValue('technology-typescript-969545')) ||
179
- item.includes('javascript')));
180
- }
181
169
  /** Return a neutral reconciliation when prerequisites are missing. */
182
170
  function buildEmptyReconciliation(_reason) {
183
171
  return {
@@ -33,7 +33,7 @@ const CAPABILITY_SIGNALS = {
33
33
  /\bbatch\s+upload\b/i,
34
34
  ],
35
35
  scale: [
36
- /\b(?:traffic|load|throughput|requests?\s+per\s+second|concurrent\s+users?)\b/i,
36
+ /\b(?:traffic|load\b(?![\s‐-―-]+bearing)|throughput|requests?\s+per\s+second|concurrent\s+users?)\b/i,
37
37
  /\b(?:auto[ -]?scal|load\s+test|capacity\s+plan)/i,
38
38
  /\b(?:data\s+growth|growing\s+dataset|query\s+performance\s+at\s+scale)\b/i,
39
39
  ],
@@ -2,6 +2,7 @@
2
2
  // Analyzes a spec from adversarial perspectives: failure scenarios,
3
3
  // concurrency issues, scale limits, security holes, and data consistency.
4
4
  import { readFile } from 'node:fs/promises';
5
+ import { createHash } from 'node:crypto';
5
6
  import { specStore, knowledgeStore } from '../storage/index.js';
6
7
  // SPEC-1011 Bug F: fallback resolver using disk fingerprints
7
8
  import { resolveProjectFromPath } from '../storage/project-resolver.js';
@@ -29,6 +30,14 @@ const ALL_FOCUS_AREAS = [
29
30
  'security',
30
31
  'data-consistency',
31
32
  ];
33
+ function truncateCriterionDiscriminator(criterion) {
34
+ const codePoints = Array.from(criterion);
35
+ if (codePoints.length <= 80) {
36
+ return criterion;
37
+ }
38
+ const digest = createHash('sha256').update(criterion).digest('hex').slice(0, 8);
39
+ return `${codePoints.slice(0, 79).join('')}… [${digest}]`;
40
+ }
32
41
  function extractOutOfScopeSection(content) {
33
42
  const headingPattern = /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/;
34
43
  const fencePattern = /^[ \t]{0,3}(`{3,}|~{3,})/;
@@ -224,7 +233,7 @@ export async function handleChallengeSpec(args, server) {
224
233
  const contradictionResult = checkScopeContradictions(resolvedOutOfScope, criteriaTexts);
225
234
  for (const c of contradictionResult.contradictions) {
226
235
  failureScenarios.push({
227
- scenario: `Criterion contradicts outOfScope declaration: "${c.contradicts}"`,
236
+ scenario: `Criterion contradicts outOfScope declaration: "${c.contradicts}" (criterion: "${truncateCriterionDiscriminator(c.criterion)}")`,
228
237
  probability: 'medium',
229
238
  impact: 'low',
230
239
  currentHandling: `Criterion: "${c.criterion}"`,
@@ -209,15 +209,6 @@ async function fetchSkillsShCatalog(stack, language) {
209
209
  function buildFallbackSkills(knowledge) {
210
210
  const skills = [];
211
211
  const stackLower = knowledge.stack.map((s) => s.toLowerCase());
212
- const lang = knowledge.language.toLowerCase();
213
- if (lang === technologyValue('technology-typescript-969545') || lang === 'javascript') {
214
- skills.push({
215
- name: 'typescript-patterns',
216
- description: 'TypeScript best practices, strict mode patterns, and type safety conventions',
217
- installCommand: 'npx skills add typescript-patterns',
218
- justification: `Project uses ${knowledge.language}`,
219
- });
220
- }
221
212
  if (stackLower.some((s) => s.includes(technologyValue('technology-react-275976')))) {
222
213
  skills.push({
223
214
  name: 'react-patterns',
@@ -19,7 +19,6 @@ export interface ArchitectureRule {
19
19
  description: string;
20
20
  layers: ArchitectureLayer[];
21
21
  conventions: string[];
22
- maxLinesPerHandler: number;
23
22
  }
24
23
  export interface GeneratedAiToolRules {
25
24
  tool: AiToolDetection;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.3.62",
3
+ "version": "5.3.63",
4
4
  "description": "Planu — MCP Server for Spec Driven Development. Cross-platform (Linux/macOS/Windows, x64/arm64).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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": "5.3.62",
5
+ "version": "5.3.63",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",