@planu/cli 3.9.11 → 3.9.13

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 (85) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/config/criteria-injection-rules.json +1 -1
  3. package/dist/config/dor-dod-items.json +3 -3
  4. package/dist/config/hook-templates/planu-spec-sanctity.sh +14 -5
  5. package/dist/config/server-instructions.js +1 -1
  6. package/dist/config/server-instructions.ts +1 -1
  7. package/dist/config/skill-templates/planu-new-spec.md +1 -1
  8. package/dist/config/skill-templates/planu-resume-work.md +1 -1
  9. package/dist/config/subagent-templates/planu-readiness-auditor.md +3 -3
  10. package/dist/config/subagent-templates/planu-spec-implementer.md +1 -1
  11. package/dist/config/workflow-conventions-catalog.json +3 -3
  12. package/dist/core/spec-api.js +1 -1
  13. package/dist/engine/agent-generator/builders.js +1 -1
  14. package/dist/engine/autopilot/bootstrap.js +0 -138
  15. package/dist/engine/autopilot/handlers-b2.d.ts +2 -2
  16. package/dist/engine/autopilot/handlers-b2.js +15 -19
  17. package/dist/engine/ci-generator/local-script.js +4 -4
  18. package/dist/engine/ci-generator/planu-steps.js +4 -5
  19. package/dist/engine/compliance/auto-remediator.js +0 -1
  20. package/dist/engine/drift/violation-resolver.js +0 -1
  21. package/dist/engine/health/auto-fixer.js +1 -33
  22. package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.d.ts +2 -2
  23. package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.js +7 -28
  24. package/dist/engine/living-spec-analyzer.js +3 -34
  25. package/dist/engine/living-specs/index.d.ts +2 -2
  26. package/dist/engine/living-specs/index.js +10 -35
  27. package/dist/engine/planu-config-writer.js +1 -1
  28. package/dist/engine/progress-writer.d.ts +2 -2
  29. package/dist/engine/progress-writer.js +2 -2
  30. package/dist/engine/scan-project/index.js +2 -2
  31. package/dist/engine/skill-generator/skills-content.js +1 -1
  32. package/dist/engine/spec-format/lean-technical-generator.d.ts +1 -1
  33. package/dist/engine/spec-format/lean-technical-generator.js +2 -2
  34. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -1
  35. package/dist/engine/spec-format/technical-md-populator.js +2 -2
  36. package/dist/engine/spec-migrator/drift-detector.js +1 -1
  37. package/dist/engine/spec-migrator/lean-migration.js +5 -4
  38. package/dist/engine/spec-migrator/planu-root-cleaner.js +1 -1
  39. package/dist/engine/spec-registry/packager.d.ts +1 -1
  40. package/dist/engine/spec-registry/packager.js +2 -2
  41. package/dist/engine/spec-registry/validator.js +1 -2
  42. package/dist/engine/spec-splitter.js +2 -2
  43. package/dist/engine/spec-summary-html/report-renderer.d.ts +3 -4
  44. package/dist/engine/spec-summary-html/report-renderer.js +6 -135
  45. package/dist/engine/spec-summary-html.js +1 -1
  46. package/dist/engine/universal-rules/rules/planu-english-specs.js +4 -6
  47. package/dist/server/routes/specs.js +1 -1
  48. package/dist/tools/create-spec/autopilot-analyzer.js +1 -1
  49. package/dist/tools/create-spec/spec-builder.js +1 -1
  50. package/dist/tools/decompose-spec.js +1 -1
  51. package/dist/tools/git/pr-ops.js +2 -2
  52. package/dist/tools/git/sync-ops.js +1 -1
  53. package/dist/tools/reconcile-spec-living-handler.js +1 -1
  54. package/dist/tools/reconcile-spec.js +1 -1
  55. package/dist/tools/registry/install.js +27 -29
  56. package/dist/tools/reverse-engineer/handler.js +1 -1
  57. package/dist/tools/schemas/registry.js +1 -1
  58. package/dist/tools/spec-coverage.js +2 -2
  59. package/dist/tools/spec-templates.d.ts +1 -1
  60. package/dist/tools/spec-templates.js +17 -9
  61. package/dist/tools/tool-registry/group-infra.js +1 -1
  62. package/dist/tools/tool-registry/group-platform.js +1 -1
  63. package/dist/tools/update-status/index.js +1 -1
  64. package/dist/types/impact-detection.d.ts +6 -0
  65. package/dist/types/index.d.ts +0 -1
  66. package/dist/types/index.js +0 -1
  67. package/dist/types/spec-templates.d.ts +3 -5
  68. package/package.json +7 -7
  69. package/src/i18n/messages/en.json +3 -3
  70. package/src/i18n/messages/es.json +3 -3
  71. package/src/i18n/messages/pt.json +3 -3
  72. package/dist/engine/implementation-brief/convention-extractor.d.ts +0 -5
  73. package/dist/engine/implementation-brief/convention-extractor.js +0 -75
  74. package/dist/engine/implementation-brief/extension-points.d.ts +0 -2
  75. package/dist/engine/implementation-brief/extension-points.js +0 -32
  76. package/dist/engine/implementation-brief/generator.d.ts +0 -3
  77. package/dist/engine/implementation-brief/generator.js +0 -139
  78. package/dist/engine/implementation-brief/helpers-scanner.d.ts +0 -3
  79. package/dist/engine/implementation-brief/helpers-scanner.js +0 -163
  80. package/dist/engine/implementation-brief/test-pattern-matcher.d.ts +0 -3
  81. package/dist/engine/implementation-brief/test-pattern-matcher.js +0 -90
  82. package/dist/engine/risk-analyzer/risk-generator.d.ts +0 -6
  83. package/dist/engine/risk-analyzer/risk-generator.js +0 -94
  84. package/dist/types/implementation-brief.d.ts +0 -41
  85. package/dist/types/implementation-brief.js +0 -3
@@ -83,7 +83,7 @@ export async function handleCreateSpec(ctx) {
83
83
  createdAt: now,
84
84
  updatedAt: now,
85
85
  specPath: `planu/specs/${specId}-${slug}/spec.md`,
86
- technicalPath: `planu/specs/${specId}-${slug}/technical.md`,
86
+ technicalPath: `planu/specs/${specId}-${slug}/spec.md`,
87
87
  estimation: {
88
88
  devHours: 4,
89
89
  reviewHours: 1,
@@ -210,7 +210,7 @@ function generatePatternCriteria(patterns, _knowledge) {
210
210
  break;
211
211
  // SPEC-535: EU AI Act Article 53-55 compliance criteria for foundation model features
212
212
  case 'llm-feature':
213
- criteria.push('GIVEN the feature uses a foundation model WHEN the spec is implemented THEN technical.md documents: model name, provider (Anthropic/OpenAI/Google), and access date — required by EU AI Act Article 53(1)(a)');
213
+ criteria.push('GIVEN the feature uses a foundation model WHEN the spec is implemented THEN spec.md documents in its inline Technical section: model name, provider (Anthropic/OpenAI/Google), and access date — required by EU AI Act Article 53(1)(a)');
214
214
  criteria.push('GIVEN EU users interact with this feature WHEN the feature is deployed THEN an acceptable use policy is visible before the first AI interaction — required by EU AI Act Article 53(1)(c)');
215
215
  criteria.push("GIVEN the feature processes user-generated content via LLM WHEN any EU user's data is involved THEN the privacy notice explicitly states LLM processing and links to the model provider's data processing terms");
216
216
  break;
@@ -81,7 +81,7 @@ export async function buildSpecContext(params) {
81
81
  // Expected: directory doesn't exist yet — proceed
82
82
  }
83
83
  const specPath = join(specDir, fileNames.spec);
84
- const technicalPath = join(specDir, fileNames.technical);
84
+ const technicalPath = specPath;
85
85
  // Generate git branch name
86
86
  const gitBranch = generateBranchName(specId, slug, type);
87
87
  // SPEC-770: derive idempotency key from sha256(title + projectPath) to prevent duplicate specs
@@ -57,7 +57,7 @@ function buildNextSteps(orchestrationReady, taskCount) {
57
57
  if (!orchestrationReady) {
58
58
  return [
59
59
  'Review the warnings above — some tasks have no owned files.',
60
- 'Add file paths to the technical.md then re-run decompose_spec.',
60
+ 'Add file paths to the inline ## Files section in spec.md, then re-run decompose_spec.',
61
61
  ];
62
62
  }
63
63
  return [
@@ -48,7 +48,7 @@ export async function handleGeneratePr(projectId, specId) {
48
48
  }
49
49
  const typeLabel = spec.type.charAt(0).toUpperCase() + spec.type.slice(1);
50
50
  const prTitle = `[${specId}] ${spec.title}`;
51
- // Build acceptance criteria checklist from spec (criteria live in HU.md, not on spec object)
51
+ // Build acceptance criteria checklist from spec.md.
52
52
  const criteriaChecklist = buildCriteriaChecklist();
53
53
  const prBody = [
54
54
  `## ${typeLabel}: ${spec.title}`,
@@ -135,7 +135,7 @@ export async function handleGeneratePr(projectId, specId) {
135
135
  return compactResult(text);
136
136
  }
137
137
  function buildCriteriaChecklist() {
138
- return '- [ ] All acceptance criteria met (see HU.md)';
138
+ return '- [ ] All acceptance criteria met (see spec.md)';
139
139
  }
140
140
  function buildLabels(type, risk, scope) {
141
141
  const labels = [];
@@ -158,7 +158,7 @@ export async function handleResolveConflict(projectId, files) {
158
158
  specId: s.id,
159
159
  title: s.title,
160
160
  status: s.status,
161
- context: `Resolve keeping spec ${s.id} (${s.title}) intent. Check HU.md acceptance criteria for guidance.`,
161
+ context: `Resolve keeping spec ${s.id} (${s.title}) intent. Check spec.md acceptance criteria for guidance.`,
162
162
  })),
163
163
  };
164
164
  });
@@ -36,7 +36,7 @@ export async function handleReconcileSpecLiving(input) {
36
36
  lines.push(`- ${icon} ${cr.criterion} — _${cr.evidence}_`);
37
37
  }
38
38
  }
39
- lines.push('', '_progress.md updated with auto-reconcile data._');
39
+ lines.push('', '_spec.md Progress section updated with auto-reconcile data._');
40
40
  return {
41
41
  content: [{ type: 'text', text: lines.join('\n') }],
42
42
  };
@@ -91,7 +91,7 @@ async function autoDetectChanges(spec, _knowledge) {
91
91
  section: 'ficha-file',
92
92
  originalValue: spec.technicalPath,
93
93
  newValue: 'File not found',
94
- reason: 'technical.md file is missing or was moved',
94
+ reason: 'spec.md inline Technical/Files section is missing or incomplete',
95
95
  approved: false,
96
96
  });
97
97
  }
@@ -22,25 +22,6 @@ async function collectExistingSpecIds(specsDir) {
22
22
  }
23
23
  /* v8 ignore stop */
24
24
  }
25
- /** Generate a clean progress.md for the installed spec. */
26
- function generateProgressMd(specId, source, version) {
27
- const now = new Date().toISOString();
28
- return [
29
- `# ${specId} — Progress`,
30
- '',
31
- `> Installed from registry: ${source}@${version}`,
32
- `> Installed at: ${now}`,
33
- '',
34
- '## Status: pendiente',
35
- '',
36
- '## Acceptance Criteria',
37
- '',
38
- '- [ ] Review and adapt spec to local project',
39
- '- [ ] Implement according to spec',
40
- '- [ ] All tests passing',
41
- '',
42
- ].join('\n');
43
- }
44
25
  /** Inject registry source metadata into spec.md frontmatter. */
45
26
  function injectRegistrySource(content, org, name, version) {
46
27
  const frontmatterEnd = content.indexOf('\n---', 3);
@@ -50,9 +31,28 @@ function injectRegistrySource(content, org, name, version) {
50
31
  const insertion = `\nregistry:\n source: "${org}/${name}"\n version: "${version}"`;
51
32
  return content.slice(0, frontmatterEnd) + insertion + content.slice(frontmatterEnd);
52
33
  }
34
+ function mergeLegacyRegistryFiles(files) {
35
+ const specContent = files['spec.md'] ?? '';
36
+ const sections = [specContent.trimEnd()];
37
+ const technical = files['technical.md'] ?? files['FICHA-TECNICA.md'];
38
+ if (technical !== undefined && technical.trim().length > 0) {
39
+ sections.push('## Technical\n\n' + stripLeadingTitleAndFrontmatter(technical).trim());
40
+ }
41
+ const progress = files['progress.md'] ?? files['PROGRESS.md'];
42
+ if (progress !== undefined && progress.trim().length > 0) {
43
+ sections.push('## Progress\n\n' + stripLeadingTitleAndFrontmatter(progress).trim());
44
+ }
45
+ return sections.join('\n\n') + '\n';
46
+ }
47
+ function stripLeadingTitleAndFrontmatter(content) {
48
+ return content.replace(/^---\n[\s\S]*?\n---\n/, '').replace(/^#\s+.*(?:\r?\n)+/, '');
49
+ }
50
+ function isInstallableRegistrySidecar(filePath) {
51
+ return filePath === 'planu-registry.json';
52
+ }
53
53
  /**
54
54
  * Install a spec from raw YAML content (HTTP backend path).
55
- * Writes spec.md with registry source injected + progress.md, then attempts stack adaptation.
55
+ * Writes one unified spec.md with registry source injected, then attempts stack adaptation.
56
56
  */
57
57
  async function installFromYaml(yamlContent, org, name, version, projectPath) {
58
58
  const specsDir = join(projectPath, 'planu', 'specs');
@@ -64,7 +64,6 @@ async function installFromYaml(yamlContent, org, name, version, projectPath) {
64
64
  const specContent = injectRegistrySource(yamlContent, org, name, version);
65
65
  await writeFile(join(specDir, 'spec.md'), specContent, 'utf-8');
66
66
  const source = `${org}/${name}`;
67
- await writeFile(join(specDir, 'progress.md'), generateProgressMd(specId, source, version), 'utf-8');
68
67
  let adapted = false;
69
68
  let adaptationsCount = 0;
70
69
  try {
@@ -140,17 +139,16 @@ export async function handleRegistryInstall(args) {
140
139
  const specDir = join(specsDir, `${specId}-${slug}`);
141
140
  // Create spec directory
142
141
  await mkdir(specDir, { recursive: true });
143
- // Write downloaded files, injecting registry source into spec.md frontmatter
142
+ // Write a unified spec.md. Legacy registry sidecars are folded into sections
143
+ // instead of being materialized as standalone files in the spec directory.
144
+ const source = `${org}/${name}`;
145
+ const specContent = injectRegistrySource(mergeLegacyRegistryFiles(pkg.files), org, name, version);
146
+ await writeFile(join(specDir, 'spec.md'), specContent, 'utf-8');
144
147
  for (const [filePath, content] of Object.entries(pkg.files)) {
145
- let finalContent = content;
146
- if (filePath === 'spec.md') {
147
- finalContent = injectRegistrySource(content, org, name, version);
148
+ if (isInstallableRegistrySidecar(filePath)) {
149
+ await writeFile(join(specDir, filePath), content, 'utf-8');
148
150
  }
149
- await writeFile(join(specDir, filePath), finalContent, 'utf-8');
150
151
  }
151
- // Generate local progress.md
152
- const source = `${org}/${name}`;
153
- await writeFile(join(specDir, 'progress.md'), generateProgressMd(specId, source, version), 'utf-8');
154
152
  // Attempt stack detection and spec adaptation (non-blocking on failure)
155
153
  let adapted = false;
156
154
  let adaptationsCount = 0;
@@ -204,7 +204,7 @@ export async function handleReverseEngineer(args) {
204
204
  const specLocation = projectKnowledge?.specLocation ?? 'planu/specs';
205
205
  const specDir = resolve(join(projectPath, specLocation, `${specId}-${slug}`));
206
206
  const specFilePath = join(specDir, 'spec.md');
207
- const technicalFilePath = join(specDir, 'technical.md');
207
+ const technicalFilePath = specFilePath;
208
208
  spec.specPath = specFilePath;
209
209
  spec.technicalPath = technicalFilePath;
210
210
  try {
@@ -24,7 +24,7 @@ export const CheckSpecAccuracySchema = z.object({
24
24
  .string()
25
25
  .min(1)
26
26
  .max(1_000_000)
27
- .describe('Contenido de la spec o fragmento a validar (texto de HU.md o criterios de aceptacion)'),
27
+ .describe('Contenido de la spec o fragmento a validar (texto de spec.md o criterios de aceptacion)'),
28
28
  frameworks: z
29
29
  .array(z.string().max(500))
30
30
  .max(100)
@@ -29,7 +29,7 @@ async function analyzeSingleSpec(projectId, specId, projectPath) {
29
29
  return errorResult(`Spec "${specId}" not found in project "${projectId}".`);
30
30
  }
31
31
  if (!spec.specPath) {
32
- return errorResult(`Spec "${specId}" has no HU.md path recorded. ` +
32
+ return errorResult(`Spec "${specId}" has no spec.md path recorded. ` +
33
33
  `The spec may have been created before this feature was available.`);
34
34
  }
35
35
  const huPath = join(projectPath, spec.specPath);
@@ -57,7 +57,7 @@ async function analyzeAllSpecs(projectId, projectPath) {
57
57
  const errors = [];
58
58
  for (const spec of activeSpecs) {
59
59
  if (!spec.specPath) {
60
- errors.push(`${spec.id}: no HU.md path`);
60
+ errors.push(`${spec.id}: no spec.md path`);
61
61
  continue;
62
62
  }
63
63
  const huPath = join(projectPath, spec.specPath);
@@ -4,7 +4,7 @@ import type { ListTemplatesInput, ApplyTemplateInput, ToolResult } from '../type
4
4
  */
5
5
  export declare function handleListTemplates(args: ListTemplatesInput): Promise<ToolResult>;
6
6
  /**
7
- * Applies a template to create HU.md + FICHA-TECNICA.md + PROGRESS.md.
7
+ * Applies a template to create one unified spec.md file.
8
8
  */
9
9
  export declare function handleApplyTemplate(args: ApplyTemplateInput): Promise<ToolResult>;
10
10
  //# sourceMappingURL=spec-templates.d.ts.map
@@ -62,7 +62,7 @@ export async function handleListTemplates(args) {
62
62
  }
63
63
  }
64
64
  /**
65
- * Applies a template to create HU.md + FICHA-TECNICA.md + PROGRESS.md.
65
+ * Applies a template to create one unified spec.md file.
66
66
  */
67
67
  export async function handleApplyTemplate(args) {
68
68
  try {
@@ -93,19 +93,14 @@ export async function handleApplyTemplate(args) {
93
93
  const rendered = renderTemplate(template, args.variables, args.includeCriteria);
94
94
  const outputDir = resolveOutputDir(args);
95
95
  await mkdir(outputDir, { recursive: true });
96
- const specPath = join(outputDir, 'HU.md');
97
- const fichaTecnicaPath = join(outputDir, 'FICHA-TECNICA.md');
98
- const progressPath = join(outputDir, 'PROGRESS.md');
99
- await atomicWriteFile(specPath, rendered.hu);
100
- await atomicWriteFile(fichaTecnicaPath, rendered.fichaTecnica);
101
- await atomicWriteFile(progressPath, rendered.progress);
96
+ const specPath = join(outputDir, 'spec.md');
97
+ const specContent = buildUnifiedTemplateSpec(rendered.hu, rendered.fichaTecnica, rendered.progress);
98
+ await atomicWriteFile(specPath, specContent);
102
99
  const result = {
103
100
  templateId: template.id,
104
101
  templateName: template.name,
105
102
  complexityScore: template.complexityScore,
106
103
  specPath,
107
- fichaTecnicaPath,
108
- progressPath,
109
104
  variablesApplied: args.variables.length,
110
105
  criteriaIncluded: rendered.criteriaIncluded,
111
106
  criteriaTotal: rendered.criteriaTotal,
@@ -125,6 +120,19 @@ export async function handleApplyTemplate(args) {
125
120
  };
126
121
  }
127
122
  }
123
+ function buildUnifiedTemplateSpec(hu, technical, progress) {
124
+ const sections = [hu.trimEnd()];
125
+ if (technical.trim().length > 0) {
126
+ sections.push('## Technical\n\n' + stripLeadingTitle(technical).trim());
127
+ }
128
+ if (progress.trim().length > 0) {
129
+ sections.push('## Progress\n\n' + stripLeadingTitle(progress).trim());
130
+ }
131
+ return sections.join('\n\n') + '\n';
132
+ }
133
+ function stripLeadingTitle(content) {
134
+ return content.replace(/^#\s+.*(?:\r?\n)+/, '');
135
+ }
128
136
  /**
129
137
  * Resolves the output directory for the spec files.
130
138
  */
@@ -562,7 +562,7 @@ export function registerInfraGroupTools(server) {
562
562
  }, safeLicensed('assign_role', (args) => handleAssignRole(args)));
563
563
  // ── Framework registry tools ───────────────────────────────────────────────
564
564
  server.registerTool('check_spec_accuracy', {
565
- description: 'Validate spec content (HU.md text or acceptance criteria) against the framework registry ' +
565
+ description: 'Validate spec content (spec.md text or acceptance criteria) against the framework registry ' +
566
566
  'to detect hallucinated APIs, deprecated patterns, and common mistakes. ' +
567
567
  'Returns a confidence score (0-100) and actionable suggestions.',
568
568
  inputSchema: {
@@ -475,7 +475,7 @@ export function registerPlatformGroupTools(s) {
475
475
  annotations: { title: 'List Spec Templates', readOnlyHint: true },
476
476
  }, safeTracked('list_templates', async (args) => handleListTemplates(args)));
477
477
  s.registerTool('apply_template', {
478
- description: 'Create HU.md, FICHA-TECNICA.md, and PROGRESS.md from a spec template. ' +
478
+ description: 'Create a unified spec.md from a spec template. ' +
479
479
  'Templates are language-agnostic and cover common feature patterns (auth, CRUD, API, etc.) and industry verticals (fintech, healthtech, e-commerce, SaaS). ' +
480
480
  'Use list_templates first to discover available templates and their required variables. ' +
481
481
  'Supports partial criteria selection via includeCriteria parameter.',
@@ -788,7 +788,7 @@ export async function handleUpdateStatus(params, server) {
788
788
  }
789
789
  // SPEC-694: Auto-orchestration plan for cross-module/architectural specs on approved
790
790
  const orchestrationPlan = await resolveOrchestrationPlan(newStatus, spec.scope, specId, effectiveGatePath);
791
- // Sync spec.md frontmatter and progress.md
791
+ // Sync spec.md frontmatter and inline ## Progress section.
792
792
  // SPEC-698: capture warning so it surfaces in the tool response (was silent before)
793
793
  const syncResult = await syncSpecFiles(updatedSpec, originalStatus, newStatus, effectiveGatePath);
794
794
  const frontmatterSyncWarnings = syncResult.warning ? [syncResult.warning] : [];
@@ -13,6 +13,12 @@ export interface TestBreakHit {
13
13
  matchedPattern: 'tool-count' | 'snapshot' | 'license-plans-sync' | 'streams-anchor';
14
14
  linePreview: string;
15
15
  }
16
+ export type AnticipatedBreakPattern = 'tool-count' | 'snapshot' | 'license-plans-sync' | 'streams-anchor' | 'other';
17
+ export interface AnticipatedBreak {
18
+ testPath: string;
19
+ pattern: AnticipatedBreakPattern;
20
+ remediation: string;
21
+ }
16
22
  export interface ImpactDetectorInput {
17
23
  specId: string;
18
24
  specPath: string;
@@ -235,7 +235,6 @@ export * from './storage.js';
235
235
  export * from './observatory.js';
236
236
  export * from './orphan-spec-refs.js';
237
237
  export * from './session-safeguard.js';
238
- export * from './implementation-brief.js';
239
238
  export * from './impact-detection.js';
240
239
  export * from './criteria-injection.js';
241
240
  export * from './gemini.js';
@@ -232,7 +232,6 @@ export * from './storage.js';
232
232
  export * from './observatory.js';
233
233
  export * from './orphan-spec-refs.js';
234
234
  export * from './session-safeguard.js';
235
- export * from './implementation-brief.js';
236
235
  export * from './impact-detection.js';
237
236
  export * from './criteria-injection.js';
238
237
  export * from './gemini.js';
@@ -62,11 +62,11 @@ export interface SpecTemplateEntry {
62
62
  criteria: TemplateCriterion[];
63
63
  /** Variables that users need to provide when applying the template. */
64
64
  variables: TemplateVariable[];
65
- /** Template content for HU.md (Handlebars-style {{VariableKey}} placeholders). */
65
+ /** Legacy template content for the main spec body. */
66
66
  huTemplate: string;
67
- /** Template content for FICHA-TECNICA.md. */
67
+ /** Legacy template content for technical details, now embedded into spec.md. */
68
68
  fichaTecnicaTemplate: string;
69
- /** Template content for PROGRESS.md. */
69
+ /** Legacy template content for progress details, now embedded into spec.md. */
70
70
  progressTemplate: string;
71
71
  }
72
72
  /** Result returned by the list_templates tool. */
@@ -115,8 +115,6 @@ export interface ApplyTemplateResult {
115
115
  templateName: string;
116
116
  complexityScore: TemplateComplexity;
117
117
  specPath: string;
118
- fichaTecnicaPath: string;
119
- progressPath: string;
120
118
  variablesApplied: number;
121
119
  criteriaIncluded: number;
122
120
  criteriaTotal: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "3.9.11",
3
+ "version": "3.9.13",
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.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"
35
+ "@planu/core-darwin-arm64": "3.9.13",
36
+ "@planu/core-darwin-x64": "3.9.13",
37
+ "@planu/core-linux-arm64-gnu": "3.9.13",
38
+ "@planu/core-linux-arm64-musl": "3.9.13",
39
+ "@planu/core-linux-x64-gnu": "3.9.13",
40
+ "@planu/core-linux-x64-musl": "3.9.13"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=24.0.0"
@@ -407,10 +407,10 @@
407
407
  "missingVarExample": "Example: \"{example}\"",
408
408
  "missingVarsHint": "Provide all required variables in the `variables` array and try again.",
409
409
  "applySuccess": "Template \"{name}\" applied successfully.",
410
- "filesCreated": "Files created in: {dir}",
410
+ "filesCreated": "spec.md created in: {dir}",
411
411
  "nextSteps": "Next steps:",
412
- "step1": "1. Review and customize HU.md acceptance criteria",
413
- "step2": "2. Update FICHA-TECNICA.md with project-specific details",
412
+ "step1": "1. Review and customize acceptance criteria in spec.md",
413
+ "step2": "2. Update the inline Technical section with project-specific details",
414
414
  "step3": "3. Run create_spec or update_status once the spec is approved",
415
415
  "applyError": "Error applying template: {message}"
416
416
  },
@@ -407,10 +407,10 @@
407
407
  "missingVarExample": "Ejemplo: \"{example}\"",
408
408
  "missingVarsHint": "Proporciona todas las variables requeridas en el array `variables` e intenta de nuevo.",
409
409
  "applySuccess": "Plantilla \"{name}\" aplicada exitosamente.",
410
- "filesCreated": "Archivos creados en: {dir}",
410
+ "filesCreated": "spec.md creado en: {dir}",
411
411
  "nextSteps": "Próximos pasos:",
412
- "step1": "1. Revisar y personalizar los criterios de aceptación en HU.md",
413
- "step2": "2. Actualizar FICHA-TECNICA.md con detalles específicos del proyecto",
412
+ "step1": "1. Revisar y personalizar los criterios de aceptación en spec.md",
413
+ "step2": "2. Actualizar la sección Technical embebida con detalles específicos del proyecto",
414
414
  "step3": "3. Ejecutar create_spec o update_status cuando la spec esté aprobada",
415
415
  "applyError": "Error al aplicar plantilla: {message}"
416
416
  },
@@ -407,10 +407,10 @@
407
407
  "missingVarExample": "Exemplo: \"{example}\"",
408
408
  "missingVarsHint": "Forneça todas as variáveis obrigatórias no array `variables` e tente novamente.",
409
409
  "applySuccess": "Modelo \"{name}\" aplicado com sucesso.",
410
- "filesCreated": "Arquivos criados em: {dir}",
410
+ "filesCreated": "spec.md criado em: {dir}",
411
411
  "nextSteps": "Próximos passos:",
412
- "step1": "1. Revisar e personalizar os critérios de aceitação em HU.md",
413
- "step2": "2. Atualizar FICHA-TECNICA.md com detalhes específicos do projeto",
412
+ "step1": "1. Revisar e personalizar os critérios de aceitação em spec.md",
413
+ "step2": "2. Atualizar a seção Technical embutida com detalhes específicos do projeto",
414
414
  "step3": "3. Executar create_spec ou update_status quando a spec estiver aprovada",
415
415
  "applyError": "Erro ao aplicar modelo: {message}"
416
416
  },
@@ -1,5 +0,0 @@
1
- import type { ConventionExcerpt } from '../../types/index.js';
2
- export declare function extractConventions(projectPath: string, tags: string[], target: string, scope: string): Promise<{
3
- excerpts: ConventionExcerpt[];
4
- }>;
5
- //# sourceMappingURL=convention-extractor.d.ts.map
@@ -1,75 +0,0 @@
1
- // engine/implementation-brief/convention-extractor.ts — SPEC-586: Filter .claude/rules/ by tags
2
- import { readdir, readFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- const MAX_EXCERPT_CHARS = 400;
5
- const MAX_RULES = 3;
6
- const TAG_TO_RULE = {
7
- backend: ['typescript-eslint.md', 'architecture.md'],
8
- frontend: ['typescript-eslint.md'],
9
- testing: ['testing.md'],
10
- test: ['testing.md'],
11
- git: ['git-workflow.md'],
12
- autopilot: ['autopilot-first.md'],
13
- spec: ['sdd-methodology.md'],
14
- parallel: ['parallel-sessions.md'],
15
- };
16
- function rulesForTags(tags, target, scope) {
17
- const selected = new Set();
18
- const combined = [...tags, target, scope].map((t) => t.toLowerCase());
19
- for (const term of combined) {
20
- for (const [key, rules] of Object.entries(TAG_TO_RULE)) {
21
- if (term.includes(key)) {
22
- for (const r of rules) {
23
- selected.add(r);
24
- }
25
- }
26
- }
27
- }
28
- if (combined.some((t) => t === 'backend' || t === 'fullstack')) {
29
- selected.add('architecture.md');
30
- }
31
- return [...selected].slice(0, MAX_RULES);
32
- }
33
- async function readExcerpt(rulesDir, ruleFile) {
34
- try {
35
- const content = await readFile(join(rulesDir, ruleFile), 'utf-8');
36
- const lines = content.split('\n');
37
- let excerpt = '';
38
- for (const line of lines) {
39
- if (excerpt.length + line.length > MAX_EXCERPT_CHARS) {
40
- break;
41
- }
42
- excerpt += line + '\n';
43
- }
44
- return excerpt.trim();
45
- }
46
- catch {
47
- return '';
48
- }
49
- }
50
- async function listRuleFiles(rulesDir) {
51
- try {
52
- const entries = await readdir(rulesDir);
53
- return entries.filter((e) => e.endsWith('.md'));
54
- }
55
- catch {
56
- return [];
57
- }
58
- }
59
- export async function extractConventions(projectPath, tags, target, scope) {
60
- const rulesDir = join(projectPath, '.claude', 'rules');
61
- const available = await listRuleFiles(rulesDir);
62
- if (available.length === 0) {
63
- return { excerpts: [] };
64
- }
65
- const wanted = rulesForTags(tags, target, scope).filter((r) => available.includes(r));
66
- const excerpts = [];
67
- for (const ruleFile of wanted) {
68
- const excerpt = await readExcerpt(rulesDir, ruleFile);
69
- if (excerpt) {
70
- excerpts.push({ rule: ruleFile, excerpt });
71
- }
72
- }
73
- return { excerpts };
74
- }
75
- //# sourceMappingURL=convention-extractor.js.map
@@ -1,2 +0,0 @@
1
- export declare function enumerateExtensionPoints(projectPath: string): Promise<string[]>;
2
- //# sourceMappingURL=extension-points.d.ts.map
@@ -1,32 +0,0 @@
1
- // engine/implementation-brief/extension-points.ts — SPEC-586: Enumerate plugin registry paths
2
- import { stat } from 'node:fs/promises';
3
- import { join } from 'node:path';
4
- const REGISTRY_PATHS = [
5
- 'src/tools/create-spec/adapters',
6
- 'src/engine/detectors',
7
- 'src/tools/generate-tests/generators',
8
- 'src/config',
9
- 'src/engine/autopilot',
10
- 'src/engine/implementation-brief',
11
- 'src/engine/impact-detector',
12
- ];
13
- async function exists(p) {
14
- try {
15
- await stat(p);
16
- return true;
17
- }
18
- catch {
19
- return false;
20
- }
21
- }
22
- export async function enumerateExtensionPoints(projectPath) {
23
- const found = [];
24
- for (const rel of REGISTRY_PATHS) {
25
- const full = join(projectPath, rel);
26
- if (await exists(full)) {
27
- found.push(rel);
28
- }
29
- }
30
- return found;
31
- }
32
- //# sourceMappingURL=extension-points.js.map
@@ -1,3 +0,0 @@
1
- import type { ImplementationBriefInput } from '../../types/index.js';
2
- export declare function generateImplementationBrief(input: ImplementationBriefInput): Promise<void>;
3
- //# sourceMappingURL=generator.d.ts.map