@planu/cli 4.10.4 → 4.10.5
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 +6 -0
- package/dist/config/version.js +1 -1
- package/dist/config/version.ts +1 -1
- package/dist/engine/project-graph/extractors/decision-store-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/git-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/handoff-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/spec-extractor.js +1 -1
- package/dist/engine/project-graph/extractors/validation-extractor.js +1 -1
- package/dist/engine/project-graph/index.d.ts +2 -1
- package/dist/engine/project-graph/index.js +2 -1
- package/dist/engine/project-graph/query.d.ts +1 -2
- package/dist/engine/project-graph/query.js +1 -10
- package/dist/engine/project-graph/redaction.d.ts +4 -0
- package/dist/engine/project-graph/redaction.js +11 -0
- package/dist/engine/validator/validation-report-writer.js +40 -8
- package/dist/tools/design-schema-sql/migrations.js +8 -3
- package/dist/tools/design-schema-sql/tables.js +19 -3
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.js +10 -3
- package/package.json +11 -18
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/engine/context-artifacts/index.d.ts +0 -2
- package/dist/engine/context-artifacts/index.js +0 -2
package/CHANGELOG.md
CHANGED
package/dist/config/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// version.ts — reads from package.json at runtime; updated
|
|
1
|
+
// version.ts — reads from package.json at runtime; updated by the local release flow
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
package/dist/config/version.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { safeJsonParse } from '../../../storage/base-store.js';
|
|
2
2
|
import { hashText } from '../cache.js';
|
|
3
|
-
import { redactGraphText } from '../
|
|
3
|
+
import { redactGraphText } from '../redaction.js';
|
|
4
4
|
function isDecision(value) {
|
|
5
5
|
return (value !== null &&
|
|
6
6
|
typeof value === 'object' &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promisify } from 'node:util';
|
|
2
2
|
import { hashText } from '../cache.js';
|
|
3
|
-
import { redactGraphText } from '../
|
|
3
|
+
import { redactGraphText } from '../redaction.js';
|
|
4
4
|
export async function extractGitGraph(args) {
|
|
5
5
|
try {
|
|
6
6
|
const childProcess = await import('node:child_process');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { buildProjectKnowledgeGraph } from './builder.js';
|
|
2
|
-
export { queryProjectGraphSlice, formatProjectGraphContext, getProjectGraphFreshnessHint,
|
|
2
|
+
export { queryProjectGraphSlice, formatProjectGraphContext, getProjectGraphFreshnessHint, } from './query.js';
|
|
3
|
+
export { redactGraphText } from './redaction.js';
|
|
3
4
|
export { getProjectGraphFreshness, loadProjectGraphPolicy, projectGraphPath, projectGraphCachePath, } from './cache.js';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { buildProjectKnowledgeGraph } from './builder.js';
|
|
2
|
-
export { queryProjectGraphSlice, formatProjectGraphContext, getProjectGraphFreshnessHint,
|
|
2
|
+
export { queryProjectGraphSlice, formatProjectGraphContext, getProjectGraphFreshnessHint, } from './query.js';
|
|
3
|
+
export { redactGraphText } from './redaction.js';
|
|
3
4
|
export { getProjectGraphFreshness, loadProjectGraphPolicy, projectGraphPath, projectGraphCachePath, } from './cache.js';
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function redactGraphText(text: string, policy: ProjectGraphPolicy, maxChars?: number): string;
|
|
1
|
+
import type { ProjectGraphQueryInput, ProjectGraphSlice } from '../../types/project-knowledge-graph.js';
|
|
3
2
|
export declare function queryProjectGraphSlice(input: ProjectGraphQueryInput): Promise<ProjectGraphSlice | null>;
|
|
4
3
|
export declare function formatProjectGraphContext(slice: ProjectGraphSlice): Promise<string>;
|
|
5
4
|
export declare function getProjectGraphFreshnessHint(args: {
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import { hashProjectPath } from '../../storage/base-store.js';
|
|
2
2
|
import { buildProjectKnowledgeGraph } from './builder.js';
|
|
3
3
|
import { getProjectGraphFreshness, loadProjectGraphPolicy, readProjectGraph } from './cache.js';
|
|
4
|
-
|
|
5
|
-
return text.replace(/</g, '<').replace(/>/g, '>');
|
|
6
|
-
}
|
|
7
|
-
export function redactGraphText(text, policy, maxChars = policy.redaction.maxSnippetChars) {
|
|
8
|
-
const truncated = text.slice(0, maxChars);
|
|
9
|
-
return policy.redaction.redactPatterns.reduce((current, pattern) => {
|
|
10
|
-
const re = new RegExp(pattern, 'gi');
|
|
11
|
-
return current.replace(re, '[redacted]');
|
|
12
|
-
}, truncated);
|
|
13
|
-
}
|
|
4
|
+
import { escapeInert, redactGraphText } from './redaction.js';
|
|
14
5
|
function sanitizeLabel(label, policy) {
|
|
15
6
|
return escapeInert(redactGraphText(label, policy));
|
|
16
7
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProjectGraphPolicy } from '../../types/project-knowledge-graph.js';
|
|
2
|
+
export declare function escapeInert(text: string): string;
|
|
3
|
+
export declare function redactGraphText(text: string, policy: ProjectGraphPolicy, maxChars?: number): string;
|
|
4
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function escapeInert(text) {
|
|
2
|
+
return text.replace(/</g, '<').replace(/>/g, '>');
|
|
3
|
+
}
|
|
4
|
+
export function redactGraphText(text, policy, maxChars = policy.redaction.maxSnippetChars) {
|
|
5
|
+
const truncated = text.slice(0, maxChars);
|
|
6
|
+
return policy.redaction.redactPatterns.reduce((current, pattern) => {
|
|
7
|
+
const re = new RegExp(pattern, 'gi');
|
|
8
|
+
return current.replace(re, '[redacted]');
|
|
9
|
+
}, truncated);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { appendArtifact } from '../handoff-artifacts/io.js';
|
|
1
|
+
import { appendArtifact, readArtifact } from '../handoff-artifacts/io.js';
|
|
2
2
|
import { runSpecCompliance, } from './spec-compliance-runner.js';
|
|
3
3
|
export async function writeImplementationReviewReport(input) {
|
|
4
4
|
const specCompliance = await runSpecCompliance(input.spec, input.projectPath);
|
|
5
5
|
const gates = buildGates({
|
|
6
6
|
score: input.score,
|
|
7
|
-
lintPassed: input.lintPassed
|
|
7
|
+
lintPassed: input.lintPassed,
|
|
8
8
|
conventionRegression: input.conventionRegression ?? false,
|
|
9
9
|
specCompliance,
|
|
10
10
|
minimalityReport: input.minimalityReport,
|
|
11
11
|
});
|
|
12
|
-
const
|
|
12
|
+
const mergedGates = await preserveUnobservedBlockingGates(input, gates);
|
|
13
|
+
const passed = mergedGates.every((gate) => gate.passed);
|
|
13
14
|
const reviewer = {
|
|
14
15
|
kind: 'implementation-review-agent',
|
|
15
16
|
agent: 'planu-implementation-reviewer',
|
|
@@ -20,7 +21,7 @@ export async function writeImplementationReviewReport(input) {
|
|
|
20
21
|
schema_version: '1.0.0',
|
|
21
22
|
specId: input.specId,
|
|
22
23
|
passed,
|
|
23
|
-
gates,
|
|
24
|
+
gates: mergedGates,
|
|
24
25
|
reviewer,
|
|
25
26
|
specCompliance: reportCompliance,
|
|
26
27
|
minimalityReport: input.minimalityReport,
|
|
@@ -40,7 +41,7 @@ export async function writeImplementationReviewReport(input) {
|
|
|
40
41
|
sha: artifact.sha,
|
|
41
42
|
reviewer,
|
|
42
43
|
passed,
|
|
43
|
-
gates,
|
|
44
|
+
gates: mergedGates,
|
|
44
45
|
specCompliance: reportCompliance,
|
|
45
46
|
};
|
|
46
47
|
}
|
|
@@ -50,11 +51,38 @@ export async function writeImplementationReviewReport(input) {
|
|
|
50
51
|
error: err instanceof Error ? err.message : String(err),
|
|
51
52
|
reviewer,
|
|
52
53
|
passed,
|
|
53
|
-
gates,
|
|
54
|
+
gates: mergedGates,
|
|
54
55
|
specCompliance: reportCompliance,
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
async function preserveUnobservedBlockingGates(input, nextGates) {
|
|
60
|
+
const currentGateNames = new Set(nextGates.map((gate) => gate.name));
|
|
61
|
+
const explicitlyObserved = new Set([
|
|
62
|
+
...(input.lintPassed !== undefined ? ['lint'] : []),
|
|
63
|
+
...(input.conventionRegression !== undefined ? ['convention-regression'] : []),
|
|
64
|
+
...(input.minimalityReport !== undefined ? ['minimal-implementation'] : []),
|
|
65
|
+
]);
|
|
66
|
+
const existing = await readArtifact({
|
|
67
|
+
projectId: input.projectId,
|
|
68
|
+
specId: input.specId,
|
|
69
|
+
kind: 'validation-report',
|
|
70
|
+
}).catch(() => null);
|
|
71
|
+
if (!existing?.ok) {
|
|
72
|
+
return nextGates;
|
|
73
|
+
}
|
|
74
|
+
const preserved = existing.payload.gates.filter((gate) => !gate.passed &&
|
|
75
|
+
currentGateNames.has(gate.name) &&
|
|
76
|
+
!explicitlyObserved.has(gate.name) &&
|
|
77
|
+
(gate.name === 'lint' ||
|
|
78
|
+
gate.name === 'convention-regression' ||
|
|
79
|
+
gate.name === 'minimal-implementation'));
|
|
80
|
+
if (preserved.length === 0) {
|
|
81
|
+
return nextGates;
|
|
82
|
+
}
|
|
83
|
+
const preservedByName = new Map(preserved.map((gate) => [gate.name, gate]));
|
|
84
|
+
return nextGates.map((gate) => preservedByName.get(gate.name) ?? gate);
|
|
85
|
+
}
|
|
58
86
|
function buildGates(args) {
|
|
59
87
|
return [
|
|
60
88
|
{
|
|
@@ -66,8 +94,12 @@ function buildGates(args) {
|
|
|
66
94
|
},
|
|
67
95
|
{
|
|
68
96
|
name: 'lint',
|
|
69
|
-
passed: args.lintPassed,
|
|
70
|
-
reason: args.lintPassed
|
|
97
|
+
passed: args.lintPassed === true,
|
|
98
|
+
reason: args.lintPassed === true
|
|
99
|
+
? undefined
|
|
100
|
+
: args.lintPassed === false
|
|
101
|
+
? 'Configured lint command failed.'
|
|
102
|
+
: 'Lint result was not provided; report cannot claim lint passed.',
|
|
71
103
|
},
|
|
72
104
|
{
|
|
73
105
|
name: 'convention-regression',
|
|
@@ -63,11 +63,16 @@ export function generateMigrations(tables, indexes, rlsPolicies, engine) {
|
|
|
63
63
|
}
|
|
64
64
|
for (const policy of rlsPolicies) {
|
|
65
65
|
const rolesClause = policy.roles?.length ? ` TO ${policy.roles.join(', ')}` : '';
|
|
66
|
-
const usingClause = `USING (${policy.using})`;
|
|
67
|
-
const withCheckClause = policy.withCheck
|
|
66
|
+
const usingClause = policy.operation === 'INSERT' ? '' : ` USING (${policy.using})`;
|
|
67
|
+
const withCheckClause = policy.withCheck || policy.operation === 'INSERT'
|
|
68
|
+
? ` WITH CHECK (${policy.withCheck ?? policy.using})`
|
|
69
|
+
: '';
|
|
70
|
+
const policyClauses = [rolesClause, usingClause, withCheckClause]
|
|
71
|
+
.filter((clause) => clause.length > 0)
|
|
72
|
+
.join('');
|
|
68
73
|
migrations.push({
|
|
69
74
|
order: order++,
|
|
70
|
-
sql: `CREATE POLICY ${policy.name} ON ${policy.table} FOR ${policy.operation}${
|
|
75
|
+
sql: `CREATE POLICY ${policy.name} ON ${policy.table} FOR ${policy.operation}${policyClauses};`,
|
|
71
76
|
reversible: true,
|
|
72
77
|
reverseSql: `DROP POLICY IF EXISTS ${policy.name} ON ${policy.table};`,
|
|
73
78
|
dataLoss: false,
|
|
@@ -210,12 +210,28 @@ export function generateRLSPolicies(tables) {
|
|
|
210
210
|
}
|
|
211
211
|
else {
|
|
212
212
|
policies.push({
|
|
213
|
-
name: `${table.name}
|
|
213
|
+
name: `${table.name}_insert_authenticated`,
|
|
214
214
|
table: table.name,
|
|
215
|
-
operation: '
|
|
215
|
+
operation: 'INSERT',
|
|
216
|
+
roles: ['authenticated'],
|
|
217
|
+
using: 'true',
|
|
218
|
+
withCheck: 'true',
|
|
219
|
+
justification: 'Authenticated users can insert records',
|
|
220
|
+
}, {
|
|
221
|
+
name: `${table.name}_update_authenticated`,
|
|
222
|
+
table: table.name,
|
|
223
|
+
operation: 'UPDATE',
|
|
224
|
+
roles: ['authenticated'],
|
|
225
|
+
using: 'true',
|
|
226
|
+
withCheck: 'true',
|
|
227
|
+
justification: 'Authenticated users can update records',
|
|
228
|
+
}, {
|
|
229
|
+
name: `${table.name}_delete_authenticated`,
|
|
230
|
+
table: table.name,
|
|
231
|
+
operation: 'DELETE',
|
|
216
232
|
roles: ['authenticated'],
|
|
217
233
|
using: 'true',
|
|
218
|
-
justification: '
|
|
234
|
+
justification: 'Authenticated users can delete records',
|
|
219
235
|
});
|
|
220
236
|
}
|
|
221
237
|
}
|
|
@@ -55,7 +55,7 @@ export function runLintCheck(projectPath, lintCommand) {
|
|
|
55
55
|
const output = `Lint command timed out after ${String(seconds)}s and was skipped so validate can complete within MCP request limits. ` +
|
|
56
56
|
`Run \`${plan.executedCommand}\` manually before marking the spec done.`;
|
|
57
57
|
console.warn(`[Planu] lintCheck timed out: command="${plan.executedCommand}" cwd="${projectPath}" timeoutMs=${String(LINT_CHECK_TIMEOUT_MS)}`);
|
|
58
|
-
return { passed:
|
|
58
|
+
return { passed: false, command, issueCount: 1, output };
|
|
59
59
|
}
|
|
60
60
|
const raw = commandOutput(err);
|
|
61
61
|
const output = formatLintFailureDiagnostics(plan, raw);
|
package/dist/tools/validate.js
CHANGED
|
@@ -296,6 +296,7 @@ export async function handleValidate(args, server) {
|
|
|
296
296
|
passedCount,
|
|
297
297
|
failedCount,
|
|
298
298
|
totalCriteria,
|
|
299
|
+
qualityFailures: qualityGateSummary.failures,
|
|
299
300
|
missing: result.missing,
|
|
300
301
|
indeterminate,
|
|
301
302
|
commitReminder,
|
|
@@ -400,13 +401,18 @@ function buildCompactSummary(specId, score, passedCount, failedCount, totalCrite
|
|
|
400
401
|
};
|
|
401
402
|
}
|
|
402
403
|
function buildCompactValidateText(args) {
|
|
403
|
-
const { score, passedCount, failedCount, totalCriteria, missing, indeterminate, commitReminder, planuReminder, } = args;
|
|
404
|
-
if (failedCount === 0) {
|
|
404
|
+
const { score, passedCount, failedCount, totalCriteria, qualityFailures, missing, indeterminate, commitReminder, planuReminder, } = args;
|
|
405
|
+
if (failedCount === 0 && qualityFailures.length === 0) {
|
|
405
406
|
// All passing — single line
|
|
406
407
|
return (`✅ Validate: ${String(score)}/100 — ${String(passedCount)}/${String(totalCriteria)} criteria passing` +
|
|
407
408
|
commitReminder +
|
|
408
409
|
planuReminder);
|
|
409
410
|
}
|
|
411
|
+
if (failedCount === 0) {
|
|
412
|
+
return (`❌ Validate: ${String(score)}/100 — failing; ${String(passedCount)}/${String(totalCriteria)} criteria passing; failing gates: ${qualityFailures.join(', ')}` +
|
|
413
|
+
commitReminder +
|
|
414
|
+
planuReminder);
|
|
415
|
+
}
|
|
410
416
|
// Failures exist — list only the failing ones (max 5)
|
|
411
417
|
const MAX_FAILURES = 5;
|
|
412
418
|
const labelledFailures = [
|
|
@@ -423,7 +429,8 @@ function buildCompactValidateText(args) {
|
|
|
423
429
|
.slice(0, 3)
|
|
424
430
|
.map((failure) => `${failure.label}: ${failure.criterion}`)
|
|
425
431
|
.join(', ');
|
|
426
|
-
const
|
|
432
|
+
const gateSuffix = qualityFailures.length > 0 ? `; failing gates: ${qualityFailures.join(', ')}` : '';
|
|
433
|
+
const summaryLine = `❌ Validate: ${String(score)}/100 — failing; ${String(failedCount)} failing criteria: [${failNames}${remainingCount + shownFailures.length > 3 ? '...' : ''}]${gateSuffix}`;
|
|
427
434
|
return summaryLine + '\n' + failureList + moreLine + commitReminder + planuReminder;
|
|
428
435
|
}
|
|
429
436
|
function scoreToGrade(score) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planu/cli",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.5",
|
|
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",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"packageName": "@planu/core"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@planu/core-darwin-arm64": "4.10.
|
|
38
|
-
"@planu/core-darwin-x64": "4.10.
|
|
39
|
-
"@planu/core-linux-arm64-gnu": "4.10.
|
|
40
|
-
"@planu/core-linux-arm64-musl": "4.10.
|
|
41
|
-
"@planu/core-linux-x64-gnu": "4.10.
|
|
42
|
-
"@planu/core-linux-x64-musl": "4.10.
|
|
43
|
-
"@planu/core-win32-arm64-msvc": "4.10.
|
|
44
|
-
"@planu/core-win32-x64-msvc": "4.10.
|
|
37
|
+
"@planu/core-darwin-arm64": "4.10.5",
|
|
38
|
+
"@planu/core-darwin-x64": "4.10.5",
|
|
39
|
+
"@planu/core-linux-arm64-gnu": "4.10.5",
|
|
40
|
+
"@planu/core-linux-arm64-musl": "4.10.5",
|
|
41
|
+
"@planu/core-linux-x64-gnu": "4.10.5",
|
|
42
|
+
"@planu/core-linux-x64-musl": "4.10.5",
|
|
43
|
+
"@planu/core-win32-arm64-msvc": "4.10.5",
|
|
44
|
+
"@planu/core-win32-x64-msvc": "4.10.5"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=24.0.0"
|
|
@@ -142,18 +142,12 @@
|
|
|
142
142
|
"@napi-rs/cli": "^3.7.2",
|
|
143
143
|
"@secretlint/secretlint-rule-no-homedir": "^13.0.2",
|
|
144
144
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
|
|
145
|
-
"@semantic-release/changelog": "^6.0.3",
|
|
146
|
-
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
147
|
-
"@semantic-release/git": "^10.0.1",
|
|
148
|
-
"@semantic-release/github": "^12.0.9",
|
|
149
|
-
"@semantic-release/npm": "^13.1.5",
|
|
150
|
-
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
151
145
|
"@stryker-mutator/core": "^9.6.1",
|
|
152
146
|
"@stryker-mutator/vitest-runner": "^9.6.1",
|
|
153
147
|
"@supabase/supabase-js": "^2.110.0",
|
|
154
148
|
"@types/node": "^26.1.0",
|
|
155
149
|
"@vitejs/plugin-vue": "^6.0.7",
|
|
156
|
-
"@vitest/coverage-v8": "^4.1.
|
|
150
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
157
151
|
"@vue/test-utils": "^2.4.11",
|
|
158
152
|
"eslint": "^10.6.0",
|
|
159
153
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -167,13 +161,12 @@
|
|
|
167
161
|
"madge": "^8.0.0",
|
|
168
162
|
"prettier": "^3.9.4",
|
|
169
163
|
"secretlint": "^13.0.2",
|
|
170
|
-
"semantic-release": "^25.0.5",
|
|
171
164
|
"tsc-alias": "^1.9.0",
|
|
172
165
|
"type-coverage": "^2.29.7",
|
|
173
166
|
"typescript": "^6.0.3",
|
|
174
167
|
"typescript-eslint": "^8.62.1",
|
|
175
168
|
"vite": "^8.1.3",
|
|
176
|
-
"vitest": "^4.1.
|
|
169
|
+
"vitest": "^4.1.10",
|
|
177
170
|
"vue": "^3.5.39"
|
|
178
171
|
}
|
|
179
172
|
}
|
package/planu-native.json
CHANGED
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": "4.10.
|
|
5
|
+
"version": "4.10.5",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": [
|
|
8
8
|
"npx",
|