@planu/cli 5.3.26 → 5.3.28
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 +16 -0
- package/dist/cli/commands/doctor.d.ts +4 -10
- package/dist/cli/commands/doctor.js +5 -31
- package/dist/cli/commands/inspect.js +2 -14
- package/dist/cli/commands/offline.d.ts +1 -2
- package/dist/cli/commands/offline.js +24 -41
- package/dist/config/release-policy.json +1 -79
- package/dist/engine/auto-updater/release-verifier.js +17 -35
- package/dist/engine/core-bridge.d.ts +6 -23
- package/dist/engine/core-bridge.js +35 -376
- package/dist/engine/execution/job-runtime.d.ts +0 -11
- package/dist/engine/execution/job-runtime.js +0 -23
- package/dist/engine/handoff-artifacts/io.js +1 -0
- package/dist/engine/handoff-artifacts/schemas.d.ts +46 -0
- package/dist/engine/handoff-artifacts/schemas.js +15 -0
- package/dist/engine/lifecycle-reconciliation.js +6 -5
- package/dist/engine/living-spec/annotation-parser.js +0 -14
- package/dist/engine/project-graph/native.js +2 -2
- package/dist/engine/sandbox/profiles/macos.sb +33 -0
- package/dist/engine/scan-project/module-discoverer.js +10 -53
- package/dist/engine/scope-boundaries/contradiction-checker.js +1 -1
- package/dist/engine/validation/validation-receipt.d.ts +5 -0
- package/dist/engine/validation/validation-receipt.js +9 -1
- package/dist/engine/validator/deep-code-checker.js +19 -26
- package/dist/engine/validator/validation-report-writer.d.ts +1 -0
- package/dist/engine/validator/validation-report-writer.js +3 -2
- package/dist/security/adapters/macos-keychain.d.ts +5 -3
- package/dist/security/adapters/macos-keychain.js +46 -20
- package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
- package/dist/tools/create-spec/autopilot-analyzer.js +2 -2
- package/dist/tools/create-spec/relevance-scorer.d.ts +1 -1
- package/dist/tools/create-spec/relevance-scorer.js +1 -17
- package/dist/tools/create-spec.js +1 -1
- package/dist/tools/update-status/dod-gates.d.ts +1 -0
- package/dist/tools/update-status/dod-gates.js +123 -4
- package/dist/types/atomic-upgrade.d.ts +0 -2
- package/dist/types/core-bridge.d.ts +0 -108
- package/dist/types/core-bridge.js +1 -1
- package/dist/types/durable-job.d.ts +0 -40
- package/dist/types/handoff-artifacts.d.ts +17 -1
- package/dist/types/security.d.ts +0 -5
- package/dist/types/transition-log.d.ts +1 -1
- package/package.json +7 -43
- package/planu-plugin.json +1 -1
- package/dist/config/native-release-public-key.pem +0 -3
- package/dist/config/native-targets.json +0 -87
- package/dist/engine/execution/job-executor.d.ts +0 -16
- package/dist/engine/execution/job-executor.js +0 -140
- package/dist/engine/execution/native-relevance-scorer.d.ts +0 -6
- package/dist/engine/execution/native-relevance-scorer.js +0 -64
- package/dist/engine/execution/native-worker-dispatch.d.ts +0 -3
- package/dist/engine/execution/native-worker-dispatch.js +0 -46
- package/dist/engine/execution/native-worker-protocol.d.ts +0 -6
- package/dist/engine/execution/native-worker-protocol.js +0 -33
- package/dist/engine/execution/native-worker-runtime.d.ts +0 -4
- package/dist/engine/execution/native-worker-runtime.js +0 -177
- package/dist/engine/execution/native-worker-thread.d.ts +0 -2
- package/dist/engine/execution/native-worker-thread.js +0 -66
- package/dist/engine/macos-keychain-native-port.d.ts +0 -10
- package/dist/engine/macos-keychain-native-port.js +0 -64
- package/dist/engine/native-capability-policy.d.ts +0 -4
- package/dist/engine/native-capability-policy.js +0 -24
- package/dist/engine/native-platform.d.ts +0 -3
- package/dist/engine/native-platform.js +0 -44
- package/dist/engine/native-runtime-handshake.d.ts +0 -20
- package/dist/engine/native-runtime-handshake.js +0 -230
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +0 -39
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +0 -1310
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +0 -39
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +0 -1310
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +0 -38
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +0 -1310
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +0 -38
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +0 -1310
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +0 -38
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +0 -1310
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +0 -38
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +0 -1310
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +0 -38
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +0 -1310
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +0 -38
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +0 -1310
- package/dist/types/native-worker.d.ts +0 -48
- package/dist/types/native-worker.js +0 -2
- package/planu-native.json +0 -82
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { projectDataDir } from '../storage/base-store.js';
|
|
5
5
|
import { specStore } from '../storage/index.js';
|
|
6
6
|
import { ValidationReportV1Schema } from './handoff-artifacts/schemas.js';
|
|
7
|
+
import { AUTOMATED_VALIDATOR_AGENT } from './validator/validation-report-writer.js';
|
|
7
8
|
import { rollbackTransitionSpec, transitionSpec, } from './spec-state-machine/transition-spec.js';
|
|
8
9
|
import { acquireLock, releaseLock } from './safety/cross-process-lock.js';
|
|
9
10
|
import { appendTransitionEvent, readTransitionLog } from '../storage/transition-log.js';
|
|
@@ -144,10 +145,10 @@ async function validateRequest(args) {
|
|
|
144
145
|
const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
|
|
145
146
|
if (!report.success ||
|
|
146
147
|
report.data.passed ||
|
|
147
|
-
report.data.reviewer.kind !== '
|
|
148
|
-
report.data.reviewer.agent !==
|
|
148
|
+
report.data.reviewer.kind !== 'automation' ||
|
|
149
|
+
report.data.reviewer.agent !== AUTOMATED_VALIDATOR_AGENT ||
|
|
149
150
|
report.data.reviewer.verdict !== 'changes-requested') {
|
|
150
|
-
return failure('validation report is not an
|
|
151
|
+
return failure('validation report is not an automated changes-requested finding');
|
|
151
152
|
}
|
|
152
153
|
const reviewDigest = digest(reportBytes);
|
|
153
154
|
if (reviewDigest !== input.implementationReviewDigest) {
|
|
@@ -197,8 +198,8 @@ async function validateStoredBinding(args) {
|
|
|
197
198
|
const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
|
|
198
199
|
if (!report.success ||
|
|
199
200
|
report.data.passed ||
|
|
200
|
-
report.data.reviewer.kind !== '
|
|
201
|
-
report.data.reviewer.agent !==
|
|
201
|
+
report.data.reviewer.kind !== 'automation' ||
|
|
202
|
+
report.data.reviewer.agent !== AUTOMATED_VALIDATOR_AGENT ||
|
|
202
203
|
report.data.reviewer.verdict !== 'changes-requested' ||
|
|
203
204
|
digest(reportBytes) !== args.receipt.implementationReviewDigest) {
|
|
204
205
|
return failure('implementation review no longer matches the durable receipt', 'RECONCILIATION_BINDING_CONFLICT');
|
|
@@ -68,24 +68,10 @@ export function parseAnnotationsFromContent(content, file) {
|
|
|
68
68
|
}
|
|
69
69
|
return annotations;
|
|
70
70
|
}
|
|
71
|
-
import { fastScanSpecAnnotations, isNativeActive } from '../core-bridge.js';
|
|
72
71
|
/**
|
|
73
72
|
* Parse all @spec annotations from a project directory.
|
|
74
73
|
*/
|
|
75
74
|
export async function parseAnnotationsFromProject(projectPath, filterSpecId) {
|
|
76
|
-
if (isNativeActive()) {
|
|
77
|
-
const allAnnotations = fastScanSpecAnnotations(projectPath);
|
|
78
|
-
const annotations = filterSpecId
|
|
79
|
-
? allAnnotations.filter((a) => a.specId === filterSpecId)
|
|
80
|
-
: allAnnotations;
|
|
81
|
-
// Estimate file counts (approximate for performance if native)
|
|
82
|
-
const annotatedFilePaths = new Set(allAnnotations.map((a) => a.file));
|
|
83
|
-
return {
|
|
84
|
-
annotations,
|
|
85
|
-
fileCount: 0, // In native mode, we don't count files unless requested
|
|
86
|
-
annotatedFileCount: annotatedFilePaths.size,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
75
|
const annotations = [];
|
|
90
76
|
let fileCount = 0;
|
|
91
77
|
let annotatedFileCount = 0;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fastExtractTsRelations, fastQueryAffectedNodes, fastQueryShortestPath, fastScanProjectGraphSources, fastSelectCompactGraphSlice,
|
|
1
|
+
import { fastExtractTsRelations, fastQueryAffectedNodes, fastQueryShortestPath, fastScanProjectGraphSources, fastSelectCompactGraphSlice, } from '../core-bridge.js';
|
|
2
2
|
function toNativeNodes(nodes) {
|
|
3
3
|
return nodes.map((node) => ({ id: node.id }));
|
|
4
4
|
}
|
|
@@ -12,7 +12,7 @@ function toNativeEdges(edges) {
|
|
|
12
12
|
}
|
|
13
13
|
export function createNativeProjectGraphRuntime(limitsPolicy) {
|
|
14
14
|
return {
|
|
15
|
-
nativeActive:
|
|
15
|
+
nativeActive: false,
|
|
16
16
|
scanSources(projectPath, maxFiles) {
|
|
17
17
|
return fastScanProjectGraphSources(projectPath, limitsPolicy, maxFiles === undefined ? {} : { maxFiles });
|
|
18
18
|
},
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
;; macos.sb — sandbox-exec profile for Planu execute_sdd_flow (SPEC-742)
|
|
2
|
+
;; TinyScheme syntax (Apple sandbox profile language).
|
|
3
|
+
;; Denies all by default, then allows specific operations.
|
|
4
|
+
|
|
5
|
+
(version 1)
|
|
6
|
+
|
|
7
|
+
;; Deny everything by default
|
|
8
|
+
(deny default)
|
|
9
|
+
|
|
10
|
+
;; Allow read-only filesystem access (system libraries, binaries)
|
|
11
|
+
(allow file-read*)
|
|
12
|
+
|
|
13
|
+
;; Allow write access to project root (parameterized) and /tmp
|
|
14
|
+
;; Note: The profile is parameterized with PROJECT_ROOT at runtime.
|
|
15
|
+
(allow file-write*
|
|
16
|
+
(subpath (param "PROJECT_ROOT"))
|
|
17
|
+
(subpath "/tmp")
|
|
18
|
+
(subpath "/private/tmp"))
|
|
19
|
+
|
|
20
|
+
;; Allow process execution (for spawning build tools)
|
|
21
|
+
(allow process-exec)
|
|
22
|
+
(allow process-fork)
|
|
23
|
+
|
|
24
|
+
;; Allow network for npm registry only (optional — controlled by network param)
|
|
25
|
+
;; Blocked by default — uncomment to allow specific connections
|
|
26
|
+
;; (allow network-outbound (remote ip "104.16.0.0/12"))
|
|
27
|
+
|
|
28
|
+
;; Allow IPC for process communication
|
|
29
|
+
(allow ipc-posix-shm)
|
|
30
|
+
(allow mach-lookup)
|
|
31
|
+
|
|
32
|
+
;; Allow reading system information
|
|
33
|
+
(allow sysctl-read)
|
|
@@ -2,9 +2,8 @@ import { technologyValue } from '../technology-registry.js';
|
|
|
2
2
|
// engine/scan-project/module-discoverer.ts — Detects module boundaries in a project (SPEC-134)
|
|
3
3
|
// Language-agnostic: supports monorepo, package-based, framework-based, and directory-based detection.
|
|
4
4
|
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
5
|
-
import { join, relative, basename
|
|
5
|
+
import { join, relative, basename } from 'node:path';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
|
-
import { isNativeActive, fastFindFilesByName } from '../core-bridge.js';
|
|
8
7
|
// === Constants ===
|
|
9
8
|
const IGNORE_PATTERNS = [
|
|
10
9
|
'node_modules',
|
|
@@ -210,29 +209,15 @@ async function detectMonorepo(projectPath) {
|
|
|
210
209
|
return null;
|
|
211
210
|
}
|
|
212
211
|
const resolved = [];
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.map((p) =>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// but for speed we take all dirs with package.json that aren't ignored.
|
|
223
|
-
resolved.push(...dirs);
|
|
224
|
-
}
|
|
225
|
-
if (resolved.length === 0) {
|
|
226
|
-
for (const pattern of workspaceGlobs) {
|
|
227
|
-
// Append '/' to match only directories (glob treats trailing slash as dir-only)
|
|
228
|
-
const dirPattern = pattern.endsWith('/') ? pattern : `${pattern}/`;
|
|
229
|
-
const matches = await glob(dirPattern, {
|
|
230
|
-
cwd: projectPath,
|
|
231
|
-
ignore: IGNORE_PATTERNS.map((p) => `**/${p}/**`),
|
|
232
|
-
});
|
|
233
|
-
// Strip trailing slash glob may add
|
|
234
|
-
resolved.push(...matches.map((m) => m.replace(/\/$/, '')));
|
|
235
|
-
}
|
|
212
|
+
for (const pattern of workspaceGlobs) {
|
|
213
|
+
// Append '/' to match only directories (glob treats trailing slash as dir-only)
|
|
214
|
+
const dirPattern = pattern.endsWith('/') ? pattern : `${pattern}/`;
|
|
215
|
+
const matches = await glob(dirPattern, {
|
|
216
|
+
cwd: projectPath,
|
|
217
|
+
ignore: IGNORE_PATTERNS.map((p) => `**/${p}/**`),
|
|
218
|
+
});
|
|
219
|
+
// Strip trailing slash glob may add
|
|
220
|
+
resolved.push(...matches.map((m) => m.replace(/\/$/, '')));
|
|
236
221
|
}
|
|
237
222
|
if (resolved.length === 0) {
|
|
238
223
|
return null;
|
|
@@ -255,34 +240,6 @@ async function detectMonorepo(projectPath) {
|
|
|
255
240
|
}
|
|
256
241
|
// === Package-based detection ===
|
|
257
242
|
async function detectPackageBased(projectPath) {
|
|
258
|
-
if (isNativeActive()) {
|
|
259
|
-
const manifests = fastFindFilesByName(projectPath, PACKAGE_MANIFESTS);
|
|
260
|
-
if (manifests.length > 0) {
|
|
261
|
-
const modules = [];
|
|
262
|
-
const seen = new Set();
|
|
263
|
-
for (const m of manifests) {
|
|
264
|
-
const rel = relative(projectPath, dirname(m));
|
|
265
|
-
if (rel === '' || rel.includes('/') || isIgnored(rel) || seen.has(rel)) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
seen.add(rel);
|
|
269
|
-
const [estimatedFiles, language] = await Promise.all([
|
|
270
|
-
countSourceFiles(join(projectPath, rel)),
|
|
271
|
-
detectLanguage(join(projectPath, rel)),
|
|
272
|
-
]);
|
|
273
|
-
modules.push({
|
|
274
|
-
name: rel,
|
|
275
|
-
path: rel,
|
|
276
|
-
estimatedFiles,
|
|
277
|
-
language,
|
|
278
|
-
detectionMethod: 'package',
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
if (modules.length > 0) {
|
|
282
|
-
return modules;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
243
|
const subdirs = await getSubdirs(projectPath);
|
|
287
244
|
const modules = [];
|
|
288
245
|
for (const subdir of subdirs) {
|
|
@@ -66,7 +66,7 @@ function splitClauses(text) {
|
|
|
66
66
|
.map((clause) => clause.trim())
|
|
67
67
|
.filter(Boolean);
|
|
68
68
|
}
|
|
69
|
-
const AFFIRMATIVE_ACTION_VERBS = /\b(?:
|
|
69
|
+
const AFFIRMATIVE_ACTION_VERBS = /\b(?:build(?:s|ing)?|chang(?:e|es|ing)|modif(?:y|ies|ying)|alter(?:s|ing)?|add(?:s|ing)?|introduc(?:e|es|ing)|implement(?:s|ing)?|enabl(?:e|es|ing)|expos(?:e|es|ing|ed)|mutat(?:e|es|ing)|rewrit(?:e|es|ing|ten))\b/;
|
|
70
70
|
function assertsForbiddenAction(text) {
|
|
71
71
|
return AFFIRMATIVE_ACTION_VERBS.test(normalize(text));
|
|
72
72
|
}
|
|
@@ -3,6 +3,11 @@ import type { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
|
3
3
|
import { type StorageCatalogContext } from '../../storage/storage-catalog.js';
|
|
4
4
|
export { normalizeValidationCommand } from './validation-receipt-codec.js';
|
|
5
5
|
export declare const LOCAL_VALIDATION_RECEIPT_ISSUER = "planu-local-validator";
|
|
6
|
+
export declare class ValidationReceiptSignerUnavailableError extends Error {
|
|
7
|
+
readonly reason: 'PLATFORM_UNSUPPORTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE';
|
|
8
|
+
readonly code: 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
|
|
9
|
+
constructor(reason: 'PLATFORM_UNSUPPORTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE', remediation?: string);
|
|
10
|
+
}
|
|
6
11
|
export declare class ValidationReceiptStore implements ValidationReceiptPersistence {
|
|
7
12
|
private readonly options;
|
|
8
13
|
private readonly logDirectory;
|
|
@@ -6,11 +6,19 @@ import { reportClassifiedDegradation } from '../../errors/classified-degradation
|
|
|
6
6
|
import { resolvedStorageArtifactPath, } from '../../storage/storage-catalog.js';
|
|
7
7
|
import { formatSecretReference, getSecretProvider, parseSecretReference, SecretProviderError, } from '../../security/secret-provider.js';
|
|
8
8
|
import { redactText } from '../../security/redactor.js';
|
|
9
|
-
import { ValidationReceiptSignerUnavailableError } from '../macos-keychain-native-port.js';
|
|
10
9
|
import { acquireLock, releaseLock } from '../safety/file-mutex.js';
|
|
11
10
|
import { canonicalize, DIGEST, ISSUER_ID, PROJECT_ID, receiptCore, safeProof, sha256, SPEC_ID, validBindings, validReceiptShape, } from './validation-receipt-codec.js';
|
|
12
11
|
export { normalizeValidationCommand } from './validation-receipt-codec.js';
|
|
13
12
|
export const LOCAL_VALIDATION_RECEIPT_ISSUER = 'planu-local-validator';
|
|
13
|
+
export class ValidationReceiptSignerUnavailableError extends Error {
|
|
14
|
+
reason;
|
|
15
|
+
code = 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
|
|
16
|
+
constructor(reason, remediation = 'Restart the MCP host and retry.') {
|
|
17
|
+
super(`[Planu] VALIDATION_RECEIPT_SIGNER_UNAVAILABLE:${reason} ${remediation}`);
|
|
18
|
+
this.reason = reason;
|
|
19
|
+
this.name = 'ValidationReceiptSignerUnavailableError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
14
22
|
export class ValidationReceiptStore {
|
|
15
23
|
options;
|
|
16
24
|
logDirectory;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Planu — Validator: deep code checker
|
|
2
2
|
// Performs real filesystem/grep analysis for spec criteria that go beyond file-name matching.
|
|
3
3
|
import { readFile, stat } from 'node:fs/promises';
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from 'node:path';
|
|
5
5
|
import { glob } from 'glob';
|
|
6
6
|
const DEFAULT_IGNORE = ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**'];
|
|
7
7
|
function escapeRegex(input) {
|
|
@@ -152,7 +152,6 @@ async function resolveFiles(scopeGlob, projectPath, exclude) {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
// ─── Checkers ─────────────────────────────────────────────────────────────────
|
|
155
|
-
import { isNativeActive, fastFindPatterns } from '../core-bridge.js';
|
|
156
155
|
function buildRegex(pattern) {
|
|
157
156
|
try {
|
|
158
157
|
return new RegExp(pattern, 'g');
|
|
@@ -174,32 +173,26 @@ function buildRegex(pattern) {
|
|
|
174
173
|
*/
|
|
175
174
|
export async function grepCheck(config, projectPath, checkType) {
|
|
176
175
|
const evidence = [];
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
for (let i = 0; i < lines.length; i++) {
|
|
191
|
-
const line = lines[i];
|
|
192
|
-
if (line !== undefined && regex.test(line)) {
|
|
193
|
-
evidence.push(`${file}:${i + 1}`);
|
|
194
|
-
regex.lastIndex = 0;
|
|
195
|
-
}
|
|
176
|
+
const files = await resolveFiles(config.scope, projectPath, config.exclude);
|
|
177
|
+
const regex = buildRegex(config.pattern);
|
|
178
|
+
if (regex) {
|
|
179
|
+
await Promise.all(files.map(async (file) => {
|
|
180
|
+
const fullPath = join(projectPath, file);
|
|
181
|
+
try {
|
|
182
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
183
|
+
const lines = content.split('\n');
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
const line = lines[i];
|
|
186
|
+
if (line !== undefined && regex.test(line)) {
|
|
187
|
+
evidence.push(`${file}:${i + 1}`);
|
|
188
|
+
regex.lastIndex = 0;
|
|
196
189
|
}
|
|
197
190
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Skip unreadable files
|
|
194
|
+
}
|
|
195
|
+
}));
|
|
203
196
|
}
|
|
204
197
|
const matchCount = evidence.length;
|
|
205
198
|
const passed = checkType === 'grep_absent' ? matchCount === 0 : matchCount >= config.expected;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Spec } from '../../types/index.js';
|
|
2
2
|
import type { ValidationReportV1 } from '../../types/handoff-artifacts.js';
|
|
3
3
|
import type { MinimalImplementationReport } from '../../types/minimal-implementation-gate.js';
|
|
4
|
+
export declare const AUTOMATED_VALIDATOR_AGENT = "planu-automated-validator";
|
|
4
5
|
export declare function writeImplementationReviewReport(input: {
|
|
5
6
|
projectId: string;
|
|
6
7
|
specId: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendArtifact, readArtifact } from '../handoff-artifacts/io.js';
|
|
2
2
|
import { runSpecCompliance, } from './spec-compliance-runner.js';
|
|
3
|
+
export const AUTOMATED_VALIDATOR_AGENT = 'planu-automated-validator';
|
|
3
4
|
export async function writeImplementationReviewReport(input) {
|
|
4
5
|
const specCompliance = await runSpecCompliance(input.spec, input.projectPath, input.signal, input.projectId);
|
|
5
6
|
const reportScore = resolveExecutableValidationScore(input.score, specCompliance);
|
|
@@ -13,8 +14,8 @@ export async function writeImplementationReviewReport(input) {
|
|
|
13
14
|
const mergedGates = await preserveUnobservedGates(input, gates);
|
|
14
15
|
const passed = mergedGates.every((gate) => gate.passed);
|
|
15
16
|
const reviewer = {
|
|
16
|
-
kind: '
|
|
17
|
-
agent:
|
|
17
|
+
kind: 'automation',
|
|
18
|
+
agent: AUTOMATED_VALIDATOR_AGENT,
|
|
18
19
|
verdict: passed ? 'approved' : 'changes-requested',
|
|
19
20
|
};
|
|
20
21
|
const reportCompliance = toValidationReportCompliance(specCompliance);
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MacOSKeychainOperation, SecretStoreAdapter } from '../../types/security.js';
|
|
2
2
|
export declare class MacOSKeychainOperationError extends Error {
|
|
3
3
|
readonly code: 'KEYCHAIN_TIMEOUT' | 'KEYCHAIN_OUTCOME_UNKNOWN';
|
|
4
4
|
constructor(code: 'KEYCHAIN_TIMEOUT' | 'KEYCHAIN_OUTCOME_UNKNOWN', operation: MacOSKeychainOperation);
|
|
5
5
|
}
|
|
6
6
|
export declare class MacOSKeychainAdapter implements SecretStoreAdapter {
|
|
7
|
-
private readonly
|
|
7
|
+
private readonly run;
|
|
8
8
|
private readonly timeoutMs;
|
|
9
9
|
readonly kind: 'macos-keychain';
|
|
10
10
|
private readonly pending;
|
|
11
11
|
private nextFence;
|
|
12
|
-
constructor(
|
|
12
|
+
constructor(run?: (args: string[]) => Promise<{
|
|
13
|
+
stdout: string;
|
|
14
|
+
}>, timeoutMs?: number);
|
|
13
15
|
get(id: string): Promise<string | null>;
|
|
14
16
|
set(id: string, value: string): Promise<void>;
|
|
15
17
|
delete(id: string): Promise<void>;
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
4
|
+
const execFile = promisify(execFileCallback);
|
|
2
5
|
const TIMEOUT_MS = 10_000;
|
|
3
|
-
const
|
|
6
|
+
const SERVICE_LABEL = 'Planu';
|
|
7
|
+
const MISSING_ITEM_EXIT_CODE = 44;
|
|
8
|
+
async function defaultSecurityRun(args) {
|
|
9
|
+
return execFile('/usr/bin/security', args, { timeout: TIMEOUT_MS });
|
|
10
|
+
}
|
|
11
|
+
function isMissingCredentialExit(error) {
|
|
12
|
+
const failure = error;
|
|
13
|
+
return failure.code === MISSING_ITEM_EXIT_CODE || failure.code === String(MISSING_ITEM_EXIT_CODE);
|
|
14
|
+
}
|
|
15
|
+
function stripTrailingNewline(value) {
|
|
16
|
+
return value.endsWith('\n') ? value.slice(0, -1) : value;
|
|
17
|
+
}
|
|
4
18
|
export class MacOSKeychainOperationError extends Error {
|
|
5
19
|
code;
|
|
6
20
|
constructor(code, operation) {
|
|
@@ -12,13 +26,13 @@ export class MacOSKeychainOperationError extends Error {
|
|
|
12
26
|
}
|
|
13
27
|
}
|
|
14
28
|
export class MacOSKeychainAdapter {
|
|
15
|
-
|
|
29
|
+
run;
|
|
16
30
|
timeoutMs;
|
|
17
31
|
kind = 'macos-keychain';
|
|
18
32
|
pending = new Map();
|
|
19
33
|
nextFence = 0;
|
|
20
|
-
constructor(
|
|
21
|
-
this.
|
|
34
|
+
constructor(run = defaultSecurityRun, timeoutMs = TIMEOUT_MS) {
|
|
35
|
+
this.run = run;
|
|
22
36
|
this.timeoutMs = timeoutMs;
|
|
23
37
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1) {
|
|
24
38
|
throw new Error('[Planu] macOS Keychain timeout must be a positive integer');
|
|
@@ -26,32 +40,44 @@ export class MacOSKeychainAdapter {
|
|
|
26
40
|
}
|
|
27
41
|
get(id) {
|
|
28
42
|
return this.enqueue(id, 'get', false, async () => {
|
|
29
|
-
const secret = await this.nativePort().get(id);
|
|
30
|
-
if (secret === null) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
43
|
try {
|
|
34
|
-
|
|
44
|
+
const result = await this.run([
|
|
45
|
+
'find-generic-password',
|
|
46
|
+
'-a',
|
|
47
|
+
id,
|
|
48
|
+
'-s',
|
|
49
|
+
SERVICE_LABEL,
|
|
50
|
+
'-w',
|
|
51
|
+
]);
|
|
52
|
+
return Buffer.from(stripTrailingNewline(result.stdout), 'base64').toString('utf8');
|
|
35
53
|
}
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (isMissingCredentialExit(error)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
38
59
|
}
|
|
39
60
|
});
|
|
40
61
|
}
|
|
41
62
|
set(id, value) {
|
|
42
|
-
return this.enqueue(id, 'set', true, () => {
|
|
43
|
-
const
|
|
63
|
+
return this.enqueue(id, 'set', true, async () => {
|
|
64
|
+
const encoded = Buffer.from(value, 'utf8').toString('base64');
|
|
65
|
+
await this.run(['add-generic-password', '-U', '-a', id, '-s', SERVICE_LABEL, '-w', encoded]);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
delete(id) {
|
|
69
|
+
return this.enqueue(id, 'delete', true, async () => {
|
|
44
70
|
try {
|
|
45
|
-
|
|
71
|
+
await this.run(['delete-generic-password', '-a', id, '-s', SERVICE_LABEL]);
|
|
46
72
|
}
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!isMissingCredentialExit(error)) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
reportClassifiedDegradation('MACOS_SECRET_DELETE', error);
|
|
49
78
|
}
|
|
50
79
|
});
|
|
51
80
|
}
|
|
52
|
-
delete(id) {
|
|
53
|
-
return this.enqueue(id, 'delete', true, () => this.nativePort().delete(id));
|
|
54
|
-
}
|
|
55
81
|
enqueue(id, operation, mutates, execute) {
|
|
56
82
|
const predecessor = this.pending.get(id)?.settled ?? Promise.resolve();
|
|
57
83
|
const fence = (this.nextFence += 1);
|
|
@@ -20,7 +20,7 @@ export declare function getEmptyAutopilotResult(): AutopilotAnalysis;
|
|
|
20
20
|
* Analyze the project to enrich a spec with real file paths and patterns.
|
|
21
21
|
* Runs automatically during create_spec — no user interaction needed.
|
|
22
22
|
*/
|
|
23
|
-
export declare function analyzeProjectForSpec(projectPath: string, description: string, title: string, _knowledge: ProjectKnowledge | null
|
|
23
|
+
export declare function analyzeProjectForSpec(projectPath: string, description: string, title: string, _knowledge: ProjectKnowledge | null): Promise<AutopilotAnalysis>;
|
|
24
24
|
/**
|
|
25
25
|
* SPEC-619 AC1: Detect broad-scope descriptions that mention 2+ major subsystems.
|
|
26
26
|
* When true, create_spec should ask clarifying questions before proceeding.
|
|
@@ -17,7 +17,7 @@ export function getEmptyAutopilotResult() {
|
|
|
17
17
|
* Analyze the project to enrich a spec with real file paths and patterns.
|
|
18
18
|
* Runs automatically during create_spec — no user interaction needed.
|
|
19
19
|
*/
|
|
20
|
-
export async function analyzeProjectForSpec(projectPath, description, title, _knowledge
|
|
20
|
+
export async function analyzeProjectForSpec(projectPath, description, title, _knowledge) {
|
|
21
21
|
const result = {
|
|
22
22
|
suggestedFiles: { create: [], modify: [], test: [] },
|
|
23
23
|
detectedPatterns: [],
|
|
@@ -44,7 +44,7 @@ export async function analyzeProjectForSpec(projectPath, description, title, _kn
|
|
|
44
44
|
const keywords = extractKeywordsScorer(`${title} ${description}`);
|
|
45
45
|
// Scan project files to find semantically related ones
|
|
46
46
|
try {
|
|
47
|
-
const relatedFiles = await findRelevantFiles(projectPath, keywords, 10
|
|
47
|
+
const relatedFiles = await findRelevantFiles(projectPath, keywords, 10);
|
|
48
48
|
if (relatedFiles.length === 0) {
|
|
49
49
|
result.noRelatedFilesMessage =
|
|
50
50
|
'No related files detected — please populate ## Technical manually';
|
|
@@ -14,5 +14,5 @@ export declare function scoreFile(filePath: string, keywords: Set<string>): Prom
|
|
|
14
14
|
* Scans `src/**\/\*.ts` and `tests/**\/\*.ts` under projectPath,
|
|
15
15
|
* scores each, drops score=0 entries, returns top-`limit` by descending score.
|
|
16
16
|
*/
|
|
17
|
-
export declare function findRelevantFiles(projectPath: string, keywords: Set<string>, limit?: number
|
|
17
|
+
export declare function findRelevantFiles(projectPath: string, keywords: Set<string>, limit?: number): Promise<string[]>;
|
|
18
18
|
//# sourceMappingURL=relevance-scorer.d.ts.map
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { glob } from 'glob';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { isNativeActive } from '../../engine/core-bridge.js';
|
|
8
|
-
import { runNativeWorkerOperation } from '../../engine/execution/native-worker-runtime.js';
|
|
9
|
-
import { hashProjectPath } from '../../storage/base-store.js';
|
|
10
7
|
/** Stop words to exclude from keyword extraction. */
|
|
11
8
|
const STOP_WORDS = new Set([
|
|
12
9
|
// English stop words
|
|
@@ -136,23 +133,10 @@ export async function scoreFile(filePath, keywords) {
|
|
|
136
133
|
* Scans `src/**\/\*.ts` and `tests/**\/\*.ts` under projectPath,
|
|
137
134
|
* scores each, drops score=0 entries, returns top-`limit` by descending score.
|
|
138
135
|
*/
|
|
139
|
-
export async function findRelevantFiles(projectPath, keywords, limit = 10
|
|
136
|
+
export async function findRelevantFiles(projectPath, keywords, limit = 10) {
|
|
140
137
|
if (keywords.size === 0) {
|
|
141
138
|
return [];
|
|
142
139
|
}
|
|
143
|
-
if (isNativeActive()) {
|
|
144
|
-
const result = await runNativeWorkerOperation({
|
|
145
|
-
projectId: hashProjectPath(projectPath),
|
|
146
|
-
operation: 'find-relevant-files',
|
|
147
|
-
payload: { rootPath: projectPath, keywords: [...keywords], limit },
|
|
148
|
-
deadlineAt: Date.now() + 5_000,
|
|
149
|
-
signal,
|
|
150
|
-
});
|
|
151
|
-
if (!Array.isArray(result) || !result.every((filePath) => typeof filePath === 'string')) {
|
|
152
|
-
throw new Error('[Planu] Native relevance worker returned an invalid payload');
|
|
153
|
-
}
|
|
154
|
-
return result;
|
|
155
|
-
}
|
|
156
140
|
const patterns = [join(projectPath, 'src/**/*.ts'), join(projectPath, 'tests/**/*.ts')];
|
|
157
141
|
const candidates = [];
|
|
158
142
|
for (const pattern of patterns) {
|
|
@@ -1099,7 +1099,7 @@ async function prepareCreateSpecCandidate(initialParams, server) {
|
|
|
1099
1099
|
if (constitutionCheck.blocked && constitutionCheck.errorResult) {
|
|
1100
1100
|
return earlyPreparation(constitutionCheck.errorResult);
|
|
1101
1101
|
}
|
|
1102
|
-
const inlineAnalysis = await withBudget('create-spec-inline-analysis', getRuntimePolicy().createSpec.inlineAnalysisBudgetMs, (
|
|
1102
|
+
const inlineAnalysis = await withBudget('create-spec-inline-analysis', getRuntimePolicy().createSpec.inlineAnalysisBudgetMs, () => analyzeProjectForSpec(params.projectPath ?? '', params.description, spec.title, knowledge));
|
|
1103
1103
|
const autopilot = unwrapBudget(inlineAnalysis, getEmptyAutopilotResult());
|
|
1104
1104
|
if (autopilot.isIdea) {
|
|
1105
1105
|
return earlyPreparation({
|
|
@@ -107,6 +107,7 @@ export declare function checkValidationReportGate(specId: string, projectId: str
|
|
|
107
107
|
export declare function readApprovedValidationReportGate(specId: string, projectId: string, force: boolean | undefined): Promise<ValidationReportGateResult>;
|
|
108
108
|
/** SPEC-1051: Approval requires a dedicated spec reviewer artifact. */
|
|
109
109
|
export declare function checkSpecReviewGate(specId: string, projectId: string, _forceApprove: boolean | undefined): Promise<ToolResult | null>;
|
|
110
|
+
export declare function checkImplementationReviewGate(specId: string, projectId: string, _force: boolean | undefined): Promise<ToolResult | null>;
|
|
110
111
|
/**
|
|
111
112
|
* SPEC-335: Combined done-gates runner — DoD + security.
|
|
112
113
|
* When force=true, gates are not blocking but failing items are recorded in
|