@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.
- package/CHANGELOG.md +12 -0
- package/dist/config/criteria-injection-rules.json +1 -1
- package/dist/config/dor-dod-items.json +3 -3
- package/dist/config/hook-templates/planu-spec-sanctity.sh +14 -5
- package/dist/config/server-instructions.js +1 -1
- package/dist/config/server-instructions.ts +1 -1
- package/dist/config/skill-templates/planu-new-spec.md +1 -1
- package/dist/config/skill-templates/planu-resume-work.md +1 -1
- package/dist/config/subagent-templates/planu-readiness-auditor.md +3 -3
- package/dist/config/subagent-templates/planu-spec-implementer.md +1 -1
- package/dist/config/workflow-conventions-catalog.json +3 -3
- package/dist/core/spec-api.js +1 -1
- package/dist/engine/agent-generator/builders.js +1 -1
- package/dist/engine/autopilot/bootstrap.js +0 -138
- package/dist/engine/autopilot/handlers-b2.d.ts +2 -2
- package/dist/engine/autopilot/handlers-b2.js +15 -19
- package/dist/engine/ci-generator/local-script.js +4 -4
- package/dist/engine/ci-generator/planu-steps.js +4 -5
- package/dist/engine/compliance/auto-remediator.js +0 -1
- package/dist/engine/drift/violation-resolver.js +0 -1
- package/dist/engine/health/auto-fixer.js +1 -33
- package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.d.ts +2 -2
- package/dist/engine/housekeeping/ephemeral-artifacts-cleaner.js +7 -28
- package/dist/engine/living-spec-analyzer.js +3 -34
- package/dist/engine/living-specs/index.d.ts +2 -2
- package/dist/engine/living-specs/index.js +10 -35
- package/dist/engine/planu-config-writer.js +1 -1
- package/dist/engine/progress-writer.d.ts +2 -2
- package/dist/engine/progress-writer.js +2 -2
- package/dist/engine/scan-project/index.js +2 -2
- package/dist/engine/skill-generator/skills-content.js +1 -1
- package/dist/engine/spec-format/lean-technical-generator.d.ts +1 -1
- package/dist/engine/spec-format/lean-technical-generator.js +2 -2
- package/dist/engine/spec-format/technical-md-populator.d.ts +1 -1
- package/dist/engine/spec-format/technical-md-populator.js +2 -2
- package/dist/engine/spec-migrator/drift-detector.js +1 -1
- package/dist/engine/spec-migrator/lean-migration.js +5 -4
- package/dist/engine/spec-migrator/planu-root-cleaner.js +1 -1
- package/dist/engine/spec-registry/packager.d.ts +1 -1
- package/dist/engine/spec-registry/packager.js +2 -2
- package/dist/engine/spec-registry/validator.js +1 -2
- package/dist/engine/spec-splitter.js +2 -2
- package/dist/engine/spec-summary-html/report-renderer.d.ts +3 -4
- package/dist/engine/spec-summary-html/report-renderer.js +6 -135
- package/dist/engine/spec-summary-html.js +1 -1
- package/dist/engine/universal-rules/rules/planu-english-specs.js +4 -6
- package/dist/server/routes/specs.js +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +1 -1
- package/dist/tools/create-spec/spec-builder.js +1 -1
- package/dist/tools/decompose-spec.js +1 -1
- package/dist/tools/git/pr-ops.js +2 -2
- package/dist/tools/git/sync-ops.js +1 -1
- package/dist/tools/reconcile-spec-living-handler.js +1 -1
- package/dist/tools/reconcile-spec.js +1 -1
- package/dist/tools/registry/install.js +27 -29
- package/dist/tools/reverse-engineer/handler.js +1 -1
- package/dist/tools/schemas/registry.js +1 -1
- package/dist/tools/spec-coverage.js +2 -2
- package/dist/tools/spec-templates.d.ts +1 -1
- package/dist/tools/spec-templates.js +17 -9
- package/dist/tools/tool-registry/group-infra.js +1 -1
- package/dist/tools/tool-registry/group-platform.js +1 -1
- package/dist/tools/update-status/index.js +1 -1
- package/dist/types/impact-detection.d.ts +6 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/spec-templates.d.ts +3 -5
- package/package.json +7 -7
- package/src/i18n/messages/en.json +3 -3
- package/src/i18n/messages/es.json +3 -3
- package/src/i18n/messages/pt.json +3 -3
- package/dist/engine/implementation-brief/convention-extractor.d.ts +0 -5
- package/dist/engine/implementation-brief/convention-extractor.js +0 -75
- package/dist/engine/implementation-brief/extension-points.d.ts +0 -2
- package/dist/engine/implementation-brief/extension-points.js +0 -32
- package/dist/engine/implementation-brief/generator.d.ts +0 -3
- package/dist/engine/implementation-brief/generator.js +0 -139
- package/dist/engine/implementation-brief/helpers-scanner.d.ts +0 -3
- package/dist/engine/implementation-brief/helpers-scanner.js +0 -163
- package/dist/engine/implementation-brief/test-pattern-matcher.d.ts +0 -3
- package/dist/engine/implementation-brief/test-pattern-matcher.js +0 -90
- package/dist/engine/risk-analyzer/risk-generator.d.ts +0 -6
- package/dist/engine/risk-analyzer/risk-generator.js +0 -94
- package/dist/types/implementation-brief.d.ts +0 -41
- 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}/
|
|
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
|
|
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 =
|
|
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
|
|
60
|
+
'Add file paths to the inline ## Files section in spec.md, then re-run decompose_spec.',
|
|
61
61
|
];
|
|
62
62
|
}
|
|
63
63
|
return [
|
package/dist/tools/git/pr-ops.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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('', '
|
|
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: '
|
|
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
|
|
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
|
|
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
|
-
|
|
146
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, '
|
|
97
|
-
const
|
|
98
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/types/index.js
CHANGED
|
@@ -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
|
-
/**
|
|
65
|
+
/** Legacy template content for the main spec body. */
|
|
66
66
|
huTemplate: string;
|
|
67
|
-
/**
|
|
67
|
+
/** Legacy template content for technical details, now embedded into spec.md. */
|
|
68
68
|
fichaTecnicaTemplate: string;
|
|
69
|
-
/**
|
|
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.
|
|
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.
|
|
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.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": "
|
|
410
|
+
"filesCreated": "spec.md created in: {dir}",
|
|
411
411
|
"nextSteps": "Next steps:",
|
|
412
|
-
"step1": "1. Review and customize
|
|
413
|
-
"step2": "2. Update
|
|
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": "
|
|
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
|
|
413
|
-
"step2": "2. Actualizar
|
|
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": "
|
|
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
|
|
413
|
-
"step2": "2. Atualizar
|
|
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,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
|