@planu/cli 4.8.0 → 4.10.0
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 +22 -0
- package/dist/config/license-plans.json +5 -0
- package/dist/config/project-knowledge-graph.json +70 -2
- package/dist/config/registries/hosts/codex.json +3 -4
- package/dist/engine/evidence-index/index-builder.js +38 -12
- package/dist/engine/project-graph/builder.js +9 -5
- package/dist/engine/project-graph/cache.js +4 -0
- package/dist/engine/project-graph/extractors/decision-store-extractor.d.ts +3 -0
- package/dist/engine/project-graph/extractors/decision-store-extractor.js +57 -0
- package/dist/engine/structural-memory/architecture-snapshot.d.ts +3 -0
- package/dist/engine/structural-memory/architecture-snapshot.js +132 -0
- package/dist/engine/structural-memory/artifact-policy.d.ts +5 -0
- package/dist/engine/structural-memory/artifact-policy.js +17 -0
- package/dist/engine/structural-memory/change-impact.d.ts +3 -0
- package/dist/engine/structural-memory/change-impact.js +159 -0
- package/dist/engine/structural-memory/contracts.d.ts +2 -0
- package/dist/engine/structural-memory/contracts.js +2 -0
- package/dist/engine/structural-memory/helpers.d.ts +28 -0
- package/dist/engine/structural-memory/helpers.js +122 -0
- package/dist/engine/structural-memory/index.d.ts +7 -0
- package/dist/engine/structural-memory/index.js +6 -0
- package/dist/engine/structural-memory/query.d.ts +3 -0
- package/dist/engine/structural-memory/query.js +67 -0
- package/dist/engine/structural-memory/status.d.ts +3 -0
- package/dist/engine/structural-memory/status.js +30 -0
- package/dist/storage/feedback-remote.d.ts +5 -1
- package/dist/storage/feedback-remote.js +75 -16
- package/dist/storage/feedback-store.d.ts +3 -1
- package/dist/storage/feedback-store.js +107 -10
- package/dist/tools/feedback-handler.d.ts +3 -1
- package/dist/tools/feedback-handler.js +47 -1
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-sdd-tools.js +5 -0
- package/dist/tools/status-handler.js +11 -11
- package/dist/tools/tool-registry/core-tools.js +97 -0
- package/dist/tools/tool-registry.js +3 -0
- package/dist/tools/validate-assurance.d.ts +6 -0
- package/dist/tools/validate-assurance.js +55 -0
- package/dist/tools/validate-graph-coverage.d.ts +8 -0
- package/dist/tools/validate-graph-coverage.js +30 -0
- package/dist/tools/validate-helpers.d.ts +9 -0
- package/dist/tools/validate-helpers.js +47 -0
- package/dist/tools/validate-lint.d.ts +3 -0
- package/dist/tools/validate-lint.js +67 -0
- package/dist/tools/validate-minimality.d.ts +16 -0
- package/dist/tools/validate-minimality.js +22 -0
- package/dist/tools/validate-runtime.d.ts +14 -0
- package/dist/tools/validate-runtime.js +85 -0
- package/dist/tools/validate.js +5 -222
- package/dist/types/evidence-index.d.ts +1 -0
- package/dist/types/feedback.d.ts +37 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/project-knowledge-graph.d.ts +10 -0
- package/dist/types/structural-memory.d.ts +138 -0
- package/dist/types/structural-memory.js +2 -0
- package/package.json +20 -20
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// tools/validate-minimality.ts — Minimal implementation report helper for validate.
|
|
2
|
+
import { analyzeMinimalImplementation, loadMinimalImplementationPolicy, } from '../engine/minimality/index.js';
|
|
3
|
+
export async function buildMinimalityReport(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { policy, evidence } = await loadMinimalImplementationPolicy(args.projectPath);
|
|
6
|
+
return analyzeMinimalImplementation({
|
|
7
|
+
policy,
|
|
8
|
+
specId: args.specId,
|
|
9
|
+
specRisk: args.risk,
|
|
10
|
+
specText: [args.title ?? '', ...args.missing, ...args.extra].join('\n'),
|
|
11
|
+
files: args.qualityIssues.map((issue) => ({
|
|
12
|
+
path: issue.file,
|
|
13
|
+
content: [issue.rule, issue.message, issue.suggestion ?? ''].join('\n'),
|
|
14
|
+
})),
|
|
15
|
+
evidence,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=validate-minimality.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ProjectCommandPlan {
|
|
2
|
+
requestedCommand: string;
|
|
3
|
+
executedCommand: string;
|
|
4
|
+
executable: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
projectPackageManager: string | null;
|
|
7
|
+
source: 'project-package-manager' | 'host-path';
|
|
8
|
+
runtimeNode: string;
|
|
9
|
+
hint?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveProjectCommandPlan(projectPath: string, requestedCommand: string, corepackAvailable?: boolean): ProjectCommandPlan;
|
|
12
|
+
export declare function formatLintFailureDiagnostics(plan: ProjectCommandPlan, output: string): string;
|
|
13
|
+
export declare function runProjectCommandPlan(plan: ProjectCommandPlan, projectPath: string, timeoutMs: number): void;
|
|
14
|
+
//# sourceMappingURL=validate-runtime.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// tools/validate-runtime.ts — Project-aware command execution helpers for validate.
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
function readProjectPackageManager(projectPath) {
|
|
5
|
+
try {
|
|
6
|
+
const raw = readFileSync(`${projectPath}/package.json`, 'utf-8');
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
return typeof parsed.packageManager === 'string' ? parsed.packageManager : null;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function shellTokens(command) {
|
|
15
|
+
return command.split(/\s+/).filter((token) => token.length > 0);
|
|
16
|
+
}
|
|
17
|
+
function canRunCorepack(projectPath) {
|
|
18
|
+
try {
|
|
19
|
+
execFileSync('corepack', ['--version'], {
|
|
20
|
+
cwd: projectPath,
|
|
21
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
22
|
+
timeout: 5_000,
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function resolveProjectCommandPlan(projectPath, requestedCommand, corepackAvailable = canRunCorepack(projectPath)) {
|
|
31
|
+
const projectPackageManager = readProjectPackageManager(projectPath);
|
|
32
|
+
const tokens = shellTokens(requestedCommand);
|
|
33
|
+
const usesPnpm = tokens[0] === 'pnpm';
|
|
34
|
+
const projectPinsPnpm = projectPackageManager?.startsWith('pnpm@') ?? false;
|
|
35
|
+
if (usesPnpm && projectPinsPnpm && corepackAvailable) {
|
|
36
|
+
const args = ['pnpm', ...tokens.slice(1)];
|
|
37
|
+
return {
|
|
38
|
+
requestedCommand,
|
|
39
|
+
executedCommand: ['corepack', ...args].join(' '),
|
|
40
|
+
executable: 'corepack',
|
|
41
|
+
args,
|
|
42
|
+
projectPackageManager,
|
|
43
|
+
source: 'project-package-manager',
|
|
44
|
+
runtimeNode: process.version,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
requestedCommand,
|
|
49
|
+
executedCommand: requestedCommand,
|
|
50
|
+
executable: tokens[0] ?? requestedCommand,
|
|
51
|
+
args: tokens.slice(1),
|
|
52
|
+
projectPackageManager,
|
|
53
|
+
source: 'host-path',
|
|
54
|
+
runtimeNode: process.version,
|
|
55
|
+
...(usesPnpm && projectPinsPnpm
|
|
56
|
+
? {
|
|
57
|
+
hint: 'Project pins pnpm via packageManager but Corepack was unavailable; falling back to host PATH. Enable Corepack or run `corepack pnpm ...` to match the project runtime.',
|
|
58
|
+
}
|
|
59
|
+
: {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function formatLintFailureDiagnostics(plan, output) {
|
|
63
|
+
const lines = [
|
|
64
|
+
output.trim(),
|
|
65
|
+
'',
|
|
66
|
+
'Planu validate runtime diagnostics:',
|
|
67
|
+
`- requestedCommand: ${plan.requestedCommand}`,
|
|
68
|
+
`- executedCommand: ${plan.executedCommand}`,
|
|
69
|
+
`- projectPackageManager: ${plan.projectPackageManager ?? 'unknown'}`,
|
|
70
|
+
`- commandSource: ${plan.source}`,
|
|
71
|
+
`- runtimeNode: ${plan.runtimeNode}`,
|
|
72
|
+
];
|
|
73
|
+
if (plan.hint !== undefined) {
|
|
74
|
+
lines.push(`- hint: ${plan.hint}`);
|
|
75
|
+
}
|
|
76
|
+
return lines.filter((line, index) => index !== 0 || line.length > 0).join('\n');
|
|
77
|
+
}
|
|
78
|
+
export function runProjectCommandPlan(plan, projectPath, timeoutMs) {
|
|
79
|
+
execFileSync(plan.executable, plan.args, {
|
|
80
|
+
cwd: projectPath,
|
|
81
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
82
|
+
timeout: timeoutMs,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=validate-runtime.js.map
|
package/dist/tools/validate.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// tools/validate.ts — Compare spec vs implementation (SPEC-595: elicitation on failures)
|
|
2
2
|
// Uses the validator engine to check coverage, missing items, quality issues.
|
|
3
|
-
import { execSync, execFileSync } from 'node:child_process';
|
|
4
|
-
import { readFileSync } from 'node:fs';
|
|
5
3
|
import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
|
|
6
4
|
import { compactObj } from '../engine/compact-obj.js';
|
|
7
5
|
import { resolveProjectId, missingProjectIdError } from './resolve-project-id.js';
|
|
@@ -16,104 +14,13 @@ import { parseConventions, scanConventions } from '../engine/convention-scanner/
|
|
|
16
14
|
import { validateScopeCompliance } from '../engine/scope-boundaries/index.js';
|
|
17
15
|
import { compareWithBaseline } from '../storage/convention-baseline.js';
|
|
18
16
|
import { writeImplementationReviewReport } from '../engine/validator/validation-report-writer.js';
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
17
|
+
import { buildGraphCoverageReport, formatGraphCoverageText } from './validate-graph-coverage.js';
|
|
18
|
+
import { buildMinimalityReport } from './validate-minimality.js';
|
|
19
|
+
import { buildValidateFailureFallback, validateStrictLayoutOrError } from './validate-helpers.js';
|
|
20
|
+
import { runLintCheck } from './validate-lint.js';
|
|
21
|
+
import { checkPlanuUncommittedChanges, runAssuranceGates } from './validate-assurance.js';
|
|
22
22
|
// Re-export for external use (SPEC-018)
|
|
23
23
|
export { validateContractCompliance };
|
|
24
|
-
function graphCoverageGaps(slice) {
|
|
25
|
-
if (slice === null) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
const criteria = slice.nodes.filter((node) => node.type === 'criterion');
|
|
29
|
-
return criteria
|
|
30
|
-
.filter((criterion) => {
|
|
31
|
-
const outgoing = slice.edges.filter((edge) => edge.from === criterion.id);
|
|
32
|
-
return !outgoing.some((edge) => edge.type === 'implements' || edge.type === 'tests');
|
|
33
|
-
})
|
|
34
|
-
.map((criterion) => criterion.label)
|
|
35
|
-
.slice(0, 10);
|
|
36
|
-
}
|
|
37
|
-
async function buildGraphCoverageReport(args) {
|
|
38
|
-
const graphSlice = await queryProjectGraphSlice(args).catch(() => null);
|
|
39
|
-
return {
|
|
40
|
-
freshness: graphSlice?.freshness,
|
|
41
|
-
gaps: graphCoverageGaps(graphSlice),
|
|
42
|
-
compactNodes: graphSlice?.nodes.length ?? 0,
|
|
43
|
-
compactEdges: graphSlice?.edges.length ?? 0,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
function formatGraphCoverageText(report) {
|
|
47
|
-
return report.gaps.length > 0
|
|
48
|
-
? `\nGRAPH ${String(report.gaps.length)} graph-backed coverage gap(s)`
|
|
49
|
-
: '';
|
|
50
|
-
}
|
|
51
|
-
async function buildMinimalityReport(args) {
|
|
52
|
-
try {
|
|
53
|
-
const { policy, evidence } = await loadMinimalImplementationPolicy(args.projectPath);
|
|
54
|
-
return analyzeMinimalImplementation({
|
|
55
|
-
policy,
|
|
56
|
-
specId: args.specId,
|
|
57
|
-
specRisk: args.risk,
|
|
58
|
-
specText: [args.title ?? '', ...args.missing, ...args.extra].join('\n'),
|
|
59
|
-
files: args.qualityIssues.map((issue) => ({
|
|
60
|
-
path: issue.file,
|
|
61
|
-
content: [issue.rule, issue.message, issue.suggestion ?? ''].join('\n'),
|
|
62
|
-
})),
|
|
63
|
-
evidence,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function validateStrictLayoutOrError(args) {
|
|
71
|
-
try {
|
|
72
|
-
const { validateStrictPlanuLayout } = await import('../engine/spec-migrator/index.js');
|
|
73
|
-
const layout = await validateStrictPlanuLayout(args.projectPath, {
|
|
74
|
-
specPath: args.specPath,
|
|
75
|
-
includeRoot: false,
|
|
76
|
-
});
|
|
77
|
-
if (layout.ok) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
content: [
|
|
82
|
-
{
|
|
83
|
-
type: 'text',
|
|
84
|
-
text: `Strict Planu layout validation failed for ${args.specId}.\n\n` +
|
|
85
|
-
`Non-canonical paths:\n${layout.offenders.map((p) => `- ${p}`).join('\n')}\n\n` +
|
|
86
|
-
`Canonical contract:\n${layout.contract}`,
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
isError: true,
|
|
90
|
-
structuredContent: {
|
|
91
|
-
error: 'strict_planu_layout_violation',
|
|
92
|
-
offenders: layout.offenders,
|
|
93
|
-
contract: layout.contract,
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/** Build the fallback interactiveQuestion for validation failures. */
|
|
102
|
-
function buildValidateFailureFallback(failedCount) {
|
|
103
|
-
return {
|
|
104
|
-
header: 'Failures',
|
|
105
|
-
question: `${failedCount} criteria failed. How do you want to proceed?`,
|
|
106
|
-
multiSelect: false,
|
|
107
|
-
options: [
|
|
108
|
-
{
|
|
109
|
-
label: 're-implement',
|
|
110
|
-
description: 'Fix the failing criteria and run validate again (Recommended)',
|
|
111
|
-
},
|
|
112
|
-
{ label: 'ignore', description: 'Ignore failures and proceed (not recommended)' },
|
|
113
|
-
{ label: 'mark-manual', description: 'Mark as manually reviewed — will bypass auto-check' },
|
|
114
|
-
],
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
24
|
// SPEC-595: server? param enables elicitation on failures
|
|
118
25
|
export async function handleValidate(args, server) {
|
|
119
26
|
const { specId } = args;
|
|
@@ -478,130 +385,6 @@ function buildCompactValidateText(args) {
|
|
|
478
385
|
const summaryLine = `❌ Validate: ${String(score)}/100 — ${String(failedCount)} failing: [${failNames}${remainingCount + shownFailures.length > 3 ? '...' : ''}]`;
|
|
479
386
|
return summaryLine + '\n' + failureList + moreLine + commitReminder + planuReminder;
|
|
480
387
|
}
|
|
481
|
-
/** Allowlist: alphanumerics, spaces, and safe shell chars. Rejects ; | $ ` & < > */
|
|
482
|
-
const SAFE_COMMAND_RE = /^[\w\s./:@~=-]+$/;
|
|
483
|
-
const LINT_CHECK_TIMEOUT_MS = 50_000;
|
|
484
|
-
function isSafeCommand(cmd) {
|
|
485
|
-
return SAFE_COMMAND_RE.test(cmd);
|
|
486
|
-
}
|
|
487
|
-
function isCommandTimeout(err) {
|
|
488
|
-
if (!(err instanceof Error)) {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
const errorWithProcessFields = err;
|
|
492
|
-
return (errorWithProcessFields.signal === 'SIGTERM' ||
|
|
493
|
-
errorWithProcessFields.killed === true ||
|
|
494
|
-
errorWithProcessFields.code === 'ETIMEDOUT' ||
|
|
495
|
-
/timed out|timeout/i.test(err.message));
|
|
496
|
-
}
|
|
497
|
-
function commandOutput(err) {
|
|
498
|
-
if (!(err instanceof Error)) {
|
|
499
|
-
return String(err);
|
|
500
|
-
}
|
|
501
|
-
const errorWithOutput = err;
|
|
502
|
-
return [errorWithOutput.stdout, errorWithOutput.stderr]
|
|
503
|
-
.filter((chunk) => chunk !== undefined)
|
|
504
|
-
.map((chunk) => String(chunk))
|
|
505
|
-
.join('\n')
|
|
506
|
-
.trim();
|
|
507
|
-
}
|
|
508
|
-
function runAssuranceGates(projectPath) {
|
|
509
|
-
return {
|
|
510
|
-
newCode: evaluateNewCodeGate(readChangedSpecLogicFiles(projectPath)),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
function readChangedSpecLogicFiles(projectPath) {
|
|
514
|
-
let changedFiles;
|
|
515
|
-
try {
|
|
516
|
-
const output = execFileSync('git', [
|
|
517
|
-
'-C',
|
|
518
|
-
projectPath,
|
|
519
|
-
'diff',
|
|
520
|
-
'--name-only',
|
|
521
|
-
'HEAD',
|
|
522
|
-
'--',
|
|
523
|
-
'src/tools/create-spec',
|
|
524
|
-
'src/tools/validate.ts',
|
|
525
|
-
'src/engine',
|
|
526
|
-
], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
527
|
-
changedFiles = output
|
|
528
|
-
.split('\n')
|
|
529
|
-
.map((line) => line.trim())
|
|
530
|
-
.filter((line) => line.endsWith('.ts'));
|
|
531
|
-
}
|
|
532
|
-
catch {
|
|
533
|
-
return [];
|
|
534
|
-
}
|
|
535
|
-
return changedFiles.flatMap((path) => {
|
|
536
|
-
try {
|
|
537
|
-
return [{ path, content: readFileSync(`${projectPath}/${path}`, 'utf-8') }];
|
|
538
|
-
}
|
|
539
|
-
catch {
|
|
540
|
-
return [];
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
function runLintCheck(projectPath, lintCommand) {
|
|
545
|
-
if (lintCommand !== null && !isSafeCommand(lintCommand)) {
|
|
546
|
-
console.warn(`[Planu] validate: lintCommand contains unsafe characters — skipping execution`);
|
|
547
|
-
return {
|
|
548
|
-
passed: true,
|
|
549
|
-
command: lintCommand,
|
|
550
|
-
issueCount: 0,
|
|
551
|
-
output: '(skipped — unsafe command)',
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
// SPEC-787: use pnpm lint (same as `pnpm lint` in package.json) to match what
|
|
555
|
-
// the developer runs manually. The old `npx eslint src ... || true` never raised
|
|
556
|
-
// an error (|| true suppressed exit code) and ran against a different scope.
|
|
557
|
-
const command = lintCommand ?? 'pnpm lint';
|
|
558
|
-
try {
|
|
559
|
-
execSync(command, {
|
|
560
|
-
cwd: projectPath,
|
|
561
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
562
|
-
timeout: LINT_CHECK_TIMEOUT_MS,
|
|
563
|
-
});
|
|
564
|
-
return { passed: true, command, issueCount: 0, output: '' };
|
|
565
|
-
}
|
|
566
|
-
catch (err) {
|
|
567
|
-
if (isCommandTimeout(err)) {
|
|
568
|
-
const seconds = Math.round(LINT_CHECK_TIMEOUT_MS / 1000);
|
|
569
|
-
const output = `Lint command timed out after ${String(seconds)}s and was skipped so validate can complete within MCP request limits. ` +
|
|
570
|
-
`Run \`${command}\` manually before marking the spec done.`;
|
|
571
|
-
console.warn(`[Planu] lintCheck timed out: command="${command}" cwd="${projectPath}" timeoutMs=${String(LINT_CHECK_TIMEOUT_MS)}`);
|
|
572
|
-
return { passed: true, command, issueCount: 0, output };
|
|
573
|
-
}
|
|
574
|
-
const raw = commandOutput(err);
|
|
575
|
-
// SPEC-787: never truncate — preserve full output so caller sees real issues
|
|
576
|
-
const output = raw.trim();
|
|
577
|
-
const issueCount = parseLintIssueCount(output);
|
|
578
|
-
// SPEC-787: log exact command + cwd for debuggability
|
|
579
|
-
console.warn(`[Planu] lintCheck failed: command="${command}" cwd="${projectPath}" issues=${String(issueCount)}`);
|
|
580
|
-
return { passed: false, command, issueCount, output };
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
function parseLintIssueCount(output) {
|
|
584
|
-
const m = /(\d+)\s+problem/i.exec(output);
|
|
585
|
-
if (m) {
|
|
586
|
-
return parseInt(m[1] ?? '0', 10);
|
|
587
|
-
}
|
|
588
|
-
return output.split('\n').filter((l) => l.trim().length > 0).length;
|
|
589
|
-
}
|
|
590
|
-
/** Check if planu/ directory has uncommitted changes via git status --porcelain */
|
|
591
|
-
function checkPlanuUncommittedChanges(projectPath) {
|
|
592
|
-
try {
|
|
593
|
-
const output = execFileSync('git', ['status', '--porcelain', 'planu/'], {
|
|
594
|
-
cwd: projectPath,
|
|
595
|
-
encoding: 'utf-8',
|
|
596
|
-
timeout: 5_000,
|
|
597
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
598
|
-
});
|
|
599
|
-
return output.trim().length > 0;
|
|
600
|
-
}
|
|
601
|
-
catch {
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
388
|
function scoreToGrade(score) {
|
|
606
389
|
if (score >= 90) {
|
|
607
390
|
return 'A';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type EvidenceRecordKind = 'scenario' | 'test' | 'contract' | 'manual' | 'changed-file' | 'validation' | 'reviewer';
|
|
2
2
|
export type EvidenceRecordStatus = 'valid' | 'stale' | 'invalid' | 'missing';
|
|
3
|
+
export type EvidencePathField = 'testEvidence' | 'contractEvidence' | 'changedFiles' | 'contractValidation.reportPath';
|
|
3
4
|
export interface EvidenceRecord {
|
|
4
5
|
kind: EvidenceRecordKind;
|
|
5
6
|
value: string;
|
package/dist/types/feedback.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
export type FeedbackType = 'bug' | 'feature' | 'suggestion' | 'dx';
|
|
2
2
|
export type FeedbackStatus = 'pending' | 'triaged' | 'spec_created' | 'implementing' | 'resolved' | 'dismissed';
|
|
3
|
+
export type FeedbackRemoteStatus = 'pending' | 'synced' | 'failed' | 'skipped';
|
|
4
|
+
export interface FeedbackRemoteDelivery {
|
|
5
|
+
status: FeedbackRemoteStatus;
|
|
6
|
+
attemptCount: number;
|
|
7
|
+
lastAttemptAt?: string;
|
|
8
|
+
lastSyncedAt?: string;
|
|
9
|
+
lastError?: string;
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FeedbackRemoteResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: 'synced' | 'failed' | 'skipped';
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
3
18
|
export interface FeedbackEntry {
|
|
4
19
|
id: string;
|
|
5
20
|
type: FeedbackType;
|
|
@@ -20,6 +35,7 @@ export interface FeedbackEntry {
|
|
|
20
35
|
planuVersion: string;
|
|
21
36
|
tags?: string[];
|
|
22
37
|
votes?: number;
|
|
38
|
+
remoteDelivery?: FeedbackRemoteDelivery;
|
|
23
39
|
}
|
|
24
40
|
export interface FeedbackFilter {
|
|
25
41
|
type?: FeedbackType;
|
|
@@ -39,6 +55,10 @@ export interface FeedbackSummary {
|
|
|
39
55
|
id: string;
|
|
40
56
|
}[];
|
|
41
57
|
}
|
|
58
|
+
export interface FeedbackRemoteHealth {
|
|
59
|
+
byRemoteStatus: Record<FeedbackRemoteStatus, number>;
|
|
60
|
+
retryable: number;
|
|
61
|
+
}
|
|
42
62
|
export interface TriageGroup {
|
|
43
63
|
representative: FeedbackEntry;
|
|
44
64
|
similar: FeedbackEntry[];
|
|
@@ -65,7 +85,24 @@ export interface ResolveFeedbackInput {
|
|
|
65
85
|
prUrl?: string;
|
|
66
86
|
notes?: string;
|
|
67
87
|
}
|
|
88
|
+
export interface SyncFeedbackInput {
|
|
89
|
+
projectPath?: string;
|
|
90
|
+
includeSynced?: boolean;
|
|
91
|
+
limit?: number;
|
|
92
|
+
}
|
|
93
|
+
export interface FeedbackStatusInput {
|
|
94
|
+
projectPath?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface SyncFeedbackSummary {
|
|
97
|
+
total: number;
|
|
98
|
+
synced: number;
|
|
99
|
+
failed: number;
|
|
100
|
+
skipped: number;
|
|
101
|
+
alreadySynced: number;
|
|
102
|
+
processedIds: string[];
|
|
103
|
+
}
|
|
68
104
|
export interface RemoteFeedbackPayload {
|
|
105
|
+
id?: string;
|
|
69
106
|
type: string;
|
|
70
107
|
title: string;
|
|
71
108
|
description: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export * from './common/index.js';
|
|
|
5
5
|
export * from './estimation.js';
|
|
6
6
|
export * from './spec/index.js';
|
|
7
7
|
export * from './project.js';
|
|
8
|
+
export * from './project-knowledge-graph.js';
|
|
9
|
+
export * from './structural-memory.js';
|
|
8
10
|
export * from './schema.js';
|
|
9
11
|
export * from './ui.js';
|
|
10
12
|
export * from './analysis.js';
|
package/dist/types/index.js
CHANGED
|
@@ -6,6 +6,8 @@ export * from './common/index.js';
|
|
|
6
6
|
export * from './estimation.js';
|
|
7
7
|
export * from './spec/index.js';
|
|
8
8
|
export * from './project.js';
|
|
9
|
+
export * from './project-knowledge-graph.js';
|
|
10
|
+
export * from './structural-memory.js';
|
|
9
11
|
export * from './schema.js';
|
|
10
12
|
export * from './ui.js';
|
|
11
13
|
export * from './analysis.js';
|
|
@@ -27,6 +27,16 @@ export interface ProjectGraphPolicy {
|
|
|
27
27
|
maxSnippetChars: number;
|
|
28
28
|
redactPatterns: string[];
|
|
29
29
|
};
|
|
30
|
+
structuralMemory?: {
|
|
31
|
+
schemaVersion: string;
|
|
32
|
+
nodeTypes: string[];
|
|
33
|
+
edgeTypes: string[];
|
|
34
|
+
response: {
|
|
35
|
+
defaultLimit: number;
|
|
36
|
+
maxLimit: number;
|
|
37
|
+
};
|
|
38
|
+
propertySchemas: Record<string, string[]>;
|
|
39
|
+
};
|
|
30
40
|
toolNamePatterns: string[];
|
|
31
41
|
}
|
|
32
42
|
export interface ProjectGraphEvidencePointer {
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ProjectGraphEdge, ProjectGraphFreshness, ProjectGraphNode, ProjectKnowledgeGraph } from './project-knowledge-graph.js';
|
|
2
|
+
export type StructuralMemoryStatus = 'fresh' | 'stale' | 'missing' | 'corrupt' | 'partial';
|
|
3
|
+
export type StructuralMemoryNextActionType = 'none' | 'rebuild_graph' | 'broaden_filters' | 'inspect_evidence' | 'run_validate';
|
|
4
|
+
export interface StructuralMemoryNextAction {
|
|
5
|
+
type: StructuralMemoryNextActionType;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StructuralMemoryPagination {
|
|
9
|
+
total: number;
|
|
10
|
+
limit: number;
|
|
11
|
+
offset: number;
|
|
12
|
+
hasMore: boolean;
|
|
13
|
+
has_more: boolean;
|
|
14
|
+
truncated: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface StructuralMemoryFreshness {
|
|
17
|
+
status: StructuralMemoryStatus;
|
|
18
|
+
graphPath: string;
|
|
19
|
+
generatedAt?: string;
|
|
20
|
+
changedSources: string[];
|
|
21
|
+
reason: ProjectGraphFreshness['reason'] | 'partial';
|
|
22
|
+
}
|
|
23
|
+
export interface StructuralMemoryEnvelope {
|
|
24
|
+
graphVersion?: string;
|
|
25
|
+
freshness: StructuralMemoryFreshness;
|
|
26
|
+
pagination: StructuralMemoryPagination;
|
|
27
|
+
total: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
offset: number;
|
|
30
|
+
hasMore: boolean;
|
|
31
|
+
has_more: boolean;
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
nextAction: StructuralMemoryNextAction;
|
|
34
|
+
}
|
|
35
|
+
export interface StructuralMemoryQueryInput {
|
|
36
|
+
projectId?: string;
|
|
37
|
+
projectPath: string;
|
|
38
|
+
specId?: string;
|
|
39
|
+
filePath?: string;
|
|
40
|
+
nodeId?: string;
|
|
41
|
+
nodeTypes?: string[];
|
|
42
|
+
limit?: number;
|
|
43
|
+
offset?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface StructuralMemoryQueryResult extends StructuralMemoryEnvelope {
|
|
46
|
+
nodes: ProjectGraphNode[];
|
|
47
|
+
edges: ProjectGraphEdge[];
|
|
48
|
+
summary: Record<string, number>;
|
|
49
|
+
sourceHash?: string;
|
|
50
|
+
sourceHashes?: Record<string, string>;
|
|
51
|
+
}
|
|
52
|
+
export type ArchitectureAspect = 'packages' | 'hotspots' | 'boundaries' | 'releaseRisks' | 'tests' | 'decisions' | 'all';
|
|
53
|
+
export interface ArchitectureSnapshotInput {
|
|
54
|
+
projectId?: string;
|
|
55
|
+
projectPath: string;
|
|
56
|
+
pathPrefix?: string;
|
|
57
|
+
aspects?: ArchitectureAspect[];
|
|
58
|
+
limit?: number;
|
|
59
|
+
}
|
|
60
|
+
export interface ArchitectureSnapshotResult extends StructuralMemoryEnvelope {
|
|
61
|
+
aspects: {
|
|
62
|
+
packages?: {
|
|
63
|
+
name: string;
|
|
64
|
+
fileCount: number;
|
|
65
|
+
}[];
|
|
66
|
+
hotspots?: {
|
|
67
|
+
id: string;
|
|
68
|
+
label: string;
|
|
69
|
+
type: string;
|
|
70
|
+
degree: number;
|
|
71
|
+
}[];
|
|
72
|
+
boundaries?: {
|
|
73
|
+
from: string;
|
|
74
|
+
to: string;
|
|
75
|
+
type: string;
|
|
76
|
+
}[];
|
|
77
|
+
releaseRisks?: {
|
|
78
|
+
id: string;
|
|
79
|
+
label: string;
|
|
80
|
+
type: string;
|
|
81
|
+
}[];
|
|
82
|
+
tests?: {
|
|
83
|
+
id: string;
|
|
84
|
+
label: string;
|
|
85
|
+
}[];
|
|
86
|
+
decisions?: {
|
|
87
|
+
id: string;
|
|
88
|
+
label: string;
|
|
89
|
+
}[];
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface StructuralMemoryStatusInput {
|
|
93
|
+
projectId?: string;
|
|
94
|
+
projectPath: string;
|
|
95
|
+
}
|
|
96
|
+
export interface StructuralMemoryArtifactPolicy {
|
|
97
|
+
artifactRoot: string;
|
|
98
|
+
graphPath: string;
|
|
99
|
+
projectDataDirectory: string;
|
|
100
|
+
forbiddenPathFragment: 'planu/specs';
|
|
101
|
+
specDirectoryPolicy: 'spec.md-only';
|
|
102
|
+
evidenceAndGraphArtifactsRemainOutsideSpecs: true;
|
|
103
|
+
}
|
|
104
|
+
export interface StructuralMemoryStatusResult {
|
|
105
|
+
freshness: StructuralMemoryFreshness;
|
|
106
|
+
nextAction: StructuralMemoryNextAction;
|
|
107
|
+
artifactPolicy: StructuralMemoryArtifactPolicy;
|
|
108
|
+
graph?: Pick<ProjectKnowledgeGraph, 'graphVersion' | 'generatedAt' | 'sourceHash'> & {
|
|
109
|
+
counts: ProjectKnowledgeGraph['metadata'];
|
|
110
|
+
sourceHashCount: number;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export interface StructuralChangeImpactInput {
|
|
114
|
+
projectId?: string;
|
|
115
|
+
projectPath: string;
|
|
116
|
+
baseRef?: string;
|
|
117
|
+
changedFiles?: string[];
|
|
118
|
+
includeUntracked?: boolean;
|
|
119
|
+
limit?: number;
|
|
120
|
+
}
|
|
121
|
+
export interface StructuralChangedFile {
|
|
122
|
+
path: string;
|
|
123
|
+
status: 'added' | 'modified' | 'deleted' | 'renamed' | 'untracked' | 'unknown';
|
|
124
|
+
}
|
|
125
|
+
export interface StructuralChangeImpactResult extends StructuralMemoryEnvelope {
|
|
126
|
+
changedFiles: StructuralChangedFile[];
|
|
127
|
+
affected: {
|
|
128
|
+
specs: ProjectGraphNode[];
|
|
129
|
+
criteria: ProjectGraphNode[];
|
|
130
|
+
tests: ProjectGraphNode[];
|
|
131
|
+
decisions: ProjectGraphNode[];
|
|
132
|
+
evidence: ProjectGraphNode[];
|
|
133
|
+
risks: ProjectGraphNode[];
|
|
134
|
+
releaseRisk: ProjectGraphNode[];
|
|
135
|
+
};
|
|
136
|
+
affectedEdges: ProjectGraphEdge[];
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=structural-memory.d.ts.map
|