@planu/cli 5.3.38 → 5.3.40

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,24 @@
1
+ ## [5.3.40] - 2026-08-23
2
+
3
+ ### Bug Fixes
4
+ - fix(spec-1590): feed full multi-line BDD criteria to scope-contradiction check
5
+
6
+ ### Chores
7
+ - chore(planu): sync state after SPEC-1590 done
8
+ - chore(spec-1273): mark done and reconcile Files with regenerated artifacts
9
+
10
+
11
+ ## [5.3.39] - 2026-08-23
12
+
13
+ ### Bug Fixes
14
+ - fix(spec-1589): credit criteria whose acceptance-to-verification map test passes
15
+ - fix(spec-1273): expose request_changes and estimate on canonical MCP surface
16
+
17
+ ### Chores
18
+ - chore(planu): sync autopilot state for SPEC-1273/1589 done
19
+ - chore(spec-1556): discard as resolved by SPEC-1507
20
+
21
+
1
22
  ## [5.3.38] - 2026-08-23
2
23
 
3
24
  ### Bug Fixes
package/README.md CHANGED
@@ -21,7 +21,7 @@ Planu is a Spec Driven Development stack for AI coding agents: an MCP server and
21
21
  - **Local change detection** — observes project changes without requiring a hosted configuration service.
22
22
  - **Unified Spec Format** — Requirements and technical details in a single, context-efficient `spec.md`.
23
23
  - **Atomic Integrity** — Indestructible writes and **Freeze-on-done** immutability (P0).
24
- - **30 official MCP tools** for the public SDD surface, with advanced workflows kept in skills, CLI commands, and internal pipelines.
24
+ - **32 official MCP tools** for the public SDD surface, with advanced workflows kept in skills, CLI commands, and internal pipelines.
25
25
  - **Multi-agent compatible** — Optimized for Claude Code, Cursor, Windsurf, and Gemini CLI.
26
26
 
27
27
  ---
@@ -1,4 +1,4 @@
1
1
  /** Single editable authority for the official local SDD tool surface. */
2
- export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ['planu_status', 'session_checkpoint', 'facilitate', 'init_project', 'clarify_requirements', 'create_spec', 'list_specs', 'challenge_spec', 'check_readiness', 'update_status', 'update_status_batch', 'package_handoff', 'validate', 'get_job', 'watch_job', 'cancel_job', 'restart_job', 'reconcile_spec', 'create_rule', 'create_skill', 'skill_search', 'configure_code_graph', 'code_graph_status', 'submit_feedback', 'triage_feedback', 'resolve_feedback', 'sync_feedback', 'feedback_status', 'bump_spec_version', 'migrate_legacy_spec'];
2
+ export declare const OFFICIAL_SDD_TOOL_NAMES: readonly ['planu_status', 'session_checkpoint', 'facilitate', 'init_project', 'clarify_requirements', 'create_spec', 'list_specs', 'challenge_spec', 'check_readiness', 'update_status', 'update_status_batch', 'request_changes', 'estimate', 'package_handoff', 'validate', 'get_job', 'watch_job', 'cancel_job', 'restart_job', 'reconcile_spec', 'create_rule', 'create_skill', 'skill_search', 'configure_code_graph', 'code_graph_status', 'submit_feedback', 'triage_feedback', 'resolve_feedback', 'sync_feedback', 'feedback_status', 'bump_spec_version', 'migrate_legacy_spec'];
3
3
  export declare function readCanonicalToolNames(): string[];
4
4
  //# sourceMappingURL=official-sdd-tools.d.ts.map
@@ -11,6 +11,8 @@ export const OFFICIAL_SDD_TOOL_NAMES = [
11
11
  'check_readiness',
12
12
  'update_status',
13
13
  'update_status_batch',
14
+ 'request_changes',
15
+ 'estimate',
14
16
  'package_handoff',
15
17
  'validate',
16
18
  'get_job',
@@ -14,6 +14,8 @@
14
14
  "check_readiness",
15
15
  "update_status",
16
16
  "update_status_batch",
17
+ "request_changes",
18
+ "estimate",
17
19
  "package_handoff",
18
20
  "validate",
19
21
  "get_job",
@@ -28,6 +28,7 @@
28
28
  "description": "Create, validate, estimate, and manage specs through their full lifecycle.",
29
29
  "tools": [
30
30
  "estimate",
31
+ "request_changes",
31
32
  "challenge_spec",
32
33
  "check_readiness",
33
34
  "clarify_requirements",
@@ -1,4 +1,5 @@
1
1
  import type { NormalizedAcceptanceCriterion } from '../../types/spec-format.js';
2
+ export declare function extractSectionCriteria(section: string): string[];
2
3
  export declare function extractNormalizedAcceptanceCriteria(raw: string, options?: {
3
4
  allowLegacyBodyFallback?: boolean;
4
5
  }): NormalizedAcceptanceCriterion[];
@@ -80,7 +80,7 @@ function extractOneCriterion(body) {
80
80
  const listItem = extractListItems(body).find((item) => !TECHNICAL_MARKER.test(item));
81
81
  return listItem ?? null;
82
82
  }
83
- function extractSectionCriteria(section) {
83
+ export function extractSectionCriteria(section) {
84
84
  const explicitBodies = extractExplicitCriterionBodies(section);
85
85
  if (explicitBodies.length > 0) {
86
86
  return explicitBodies
@@ -6,6 +6,14 @@ import type { CodeState, CriterionEvidenceResult } from '../../types/index.js';
6
6
  * spec-compliance-runner.ts instead of re-implementing YAML parsing here.
7
7
  */
8
8
  export declare function extractDeclaredTestPaths(specContent: string): Map<string, string[]>;
9
+ /**
10
+ * Parse the `### Acceptance-To-Verification Map` section and return, per
11
+ * explicit `ACn` ordinal, the `*.test.*` file paths named on that row.
12
+ * Rows without a leading `ACn:` ordinal (umbrella/shared rows) are ignored,
13
+ * so a row such as "All criteria are verified by ..." cannot inflate an
14
+ * unrelated criterion.
15
+ */
16
+ export declare function extractAcceptanceMapTestPaths(specContent: string): Map<string, string[]>;
9
17
  /**
10
18
  * SPEC-1367 review finding 2: the probe cache is created per validate
11
19
  * invocation (see validateSpec) and threaded through explicitly — never
@@ -15,6 +15,7 @@ import { parseFrontmatterScenarios } from './spec-compliance-runner.js';
15
15
  import { normalizeCriterionText } from '../criterion-identity.js';
16
16
  import { resolveContainedProjectFile } from '../safety/contained-project-file.js';
17
17
  import { technologyValue } from '../technology-registry.js';
18
+ import { extractSection } from '../spec-format/markdown-sections.js';
18
19
  /**
19
20
  * Parse the spec.md frontmatter `scenarios` block and return, per criterion
20
21
  * (matched by normalized scenario title), the declared TEST file paths.
@@ -33,6 +34,39 @@ export function extractDeclaredTestPaths(specContent) {
33
34
  }
34
35
  return map;
35
36
  }
37
+ const AC_ROW_PREFIX = /^ac(\d+)\s*:/i;
38
+ const TEST_FILE_TOKEN = /[\w./-]+\.test\.\w+/g;
39
+ /**
40
+ * Parse the `### Acceptance-To-Verification Map` section and return, per
41
+ * explicit `ACn` ordinal, the `*.test.*` file paths named on that row.
42
+ * Rows without a leading `ACn:` ordinal (umbrella/shared rows) are ignored,
43
+ * so a row such as "All criteria are verified by ..." cannot inflate an
44
+ * unrelated criterion.
45
+ */
46
+ export function extractAcceptanceMapTestPaths(specContent) {
47
+ const map = new Map();
48
+ const section = extractSection(specContent, 'acceptance-to-verification map');
49
+ if (!section) {
50
+ return map;
51
+ }
52
+ for (const rawLine of section.split('\n')) {
53
+ const line = rawLine
54
+ .replace(/^\s*[-*+]\s+/, '')
55
+ .replace(/^\s*\d+[.)]\s+/, '')
56
+ .trim();
57
+ const rowMatch = AC_ROW_PREFIX.exec(line);
58
+ if (!rowMatch?.[1]) {
59
+ continue;
60
+ }
61
+ const tokens = [...line.matchAll(TEST_FILE_TOKEN)].map((match) => match[0]);
62
+ if (tokens.length === 0) {
63
+ continue;
64
+ }
65
+ const key = `AC${Number.parseInt(rowMatch[1], 10)}`;
66
+ map.set(key, [...(map.get(key) ?? []), ...tokens]);
67
+ }
68
+ return map;
69
+ }
36
70
  const DEFAULT_PROBE_TIMEOUT_MS = 60_000;
37
71
  /**
38
72
  * SPEC-1367 review finding 2: the probe cache is created per validate
@@ -4,7 +4,7 @@
4
4
  import { readFile } from 'node:fs/promises';
5
5
  import { extractCriteria, extractVerifyBlocks } from './validator/extractors.js';
6
6
  import { scanCodeForSpec, checkCriterionEvidence, classifyDriftSeverity, quickQualityCheck, } from './validator/analyzer.js';
7
- import { extractDeclaredTestPaths, createProbeCache } from './validator/test-evidence-grounding.js';
7
+ import { extractDeclaredTestPaths, extractAcceptanceMapTestPaths, filterValidatedTestPaths, groundAgainstDeclaredTests, createProbeCache, } from './validator/test-evidence-grounding.js';
8
8
  import { resolveApplicableDimensions } from './validator/scope-resolver.js';
9
9
  import { buildHolisticReportFromFlatScore } from './validator/holistic-report.js';
10
10
  import { readEvidenceArtifacts, traceabilityRowHasCurrentCommandEvidence, traceabilityRowHasCurrentTestEvidence, } from './evidence-gates/artifact-reader.js';
@@ -98,10 +98,12 @@ export async function validateSpec(spec, projectPath) {
98
98
  // (forgiving — empty maps if the file is unreadable).
99
99
  let verifyBlockMap = new Map();
100
100
  let declaredTestsMap = new Map();
101
+ let acMapTestPaths = new Map();
101
102
  try {
102
103
  const specContent = await readFile(spec.specPath, 'utf-8');
103
104
  verifyBlockMap = extractVerifyBlocks(specContent);
104
105
  declaredTestsMap = extractDeclaredTestPaths(specContent);
106
+ acMapTestPaths = extractAcceptanceMapTestPaths(specContent);
105
107
  }
106
108
  catch {
107
109
  // spec file not available — proceed without verify blocks or declared tests
@@ -129,8 +131,22 @@ export async function validateSpec(spec, projectPath) {
129
131
  const evidence = await checkCriterionEvidence(criterion, projectPath, codeState, verifyBlock, undefined, declaredTestPaths, probeCache);
130
132
  if (evidence.status === 'proven') {
131
133
  matches.push(criterion);
134
+ continue;
135
+ }
136
+ if (hasExplicitCriterionId(criterion)) {
137
+ const mapPaths = acMapTestPaths.get(criterionIdentity.id);
138
+ if (mapPaths && mapPaths.length > 0) {
139
+ const safeMapPaths = await filterValidatedTestPaths(projectPath, mapPaths);
140
+ if (safeMapPaths.length > 0) {
141
+ const mapGrounded = groundAgainstDeclaredTests(projectPath, safeMapPaths, probeCache);
142
+ if (mapGrounded?.status === 'proven') {
143
+ matches.push(criterion);
144
+ continue;
145
+ }
146
+ }
147
+ }
132
148
  }
133
- else if (evidence.status === 'indeterminate') {
149
+ if (evidence.status === 'indeterminate') {
134
150
  indeterminate.push(criterion);
135
151
  }
136
152
  else {
@@ -21,6 +21,7 @@ import { detectChallengeCapabilities, } from './challenge-spec/scenarios-utils.j
21
21
  import { collectCapabilityScenarios } from './challenge-spec/scenario-collector.js';
22
22
  import { buildChallengeReport, parseChallengeResolutionEvidence, } from './challenge-spec/challenge-report.js';
23
23
  import { extractListItems, findMarkdownSectionRange, } from '../engine/spec-format/markdown-sections.js';
24
+ import { extractSectionCriteria } from '../engine/spec-format/acceptance-criteria.js';
24
25
  const ALL_FOCUS_AREAS = [
25
26
  'failures',
26
27
  'concurrency',
@@ -219,7 +220,7 @@ export async function handleChallengeSpec(args, server) {
219
220
  const criteriaRange = findMarkdownSectionRange(specContent, 'Acceptance Criteria');
220
221
  const criteriaTexts = criteriaRange === null
221
222
  ? []
222
- : extractListItems(specContent.slice(criteriaRange.contentStart, criteriaRange.end));
223
+ : extractSectionCriteria(specContent.slice(criteriaRange.contentStart, criteriaRange.end));
223
224
  const contradictionResult = checkScopeContradictions(resolvedOutOfScope, criteriaTexts);
224
225
  for (const c of contradictionResult.contradictions) {
225
226
  failureScenarios.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.3.38",
3
+ "version": "5.3.40",
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.38",
5
+ "version": "5.3.40",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",
@@ -19,6 +19,8 @@
19
19
  "check_readiness",
20
20
  "update_status",
21
21
  "update_status_batch",
22
+ "request_changes",
23
+ "estimate",
22
24
  "package_handoff",
23
25
  "validate",
24
26
  "get_job",