@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli/commands/doctor.d.ts +4 -10
  3. package/dist/cli/commands/doctor.js +5 -31
  4. package/dist/cli/commands/inspect.js +2 -14
  5. package/dist/cli/commands/offline.d.ts +1 -2
  6. package/dist/cli/commands/offline.js +24 -41
  7. package/dist/config/release-policy.json +1 -79
  8. package/dist/engine/auto-updater/release-verifier.js +17 -35
  9. package/dist/engine/core-bridge.d.ts +6 -23
  10. package/dist/engine/core-bridge.js +35 -376
  11. package/dist/engine/execution/job-runtime.d.ts +0 -11
  12. package/dist/engine/execution/job-runtime.js +0 -23
  13. package/dist/engine/handoff-artifacts/io.js +1 -0
  14. package/dist/engine/handoff-artifacts/schemas.d.ts +46 -0
  15. package/dist/engine/handoff-artifacts/schemas.js +15 -0
  16. package/dist/engine/lifecycle-reconciliation.js +6 -5
  17. package/dist/engine/living-spec/annotation-parser.js +0 -14
  18. package/dist/engine/project-graph/native.js +2 -2
  19. package/dist/engine/sandbox/profiles/macos.sb +33 -0
  20. package/dist/engine/scan-project/module-discoverer.js +10 -53
  21. package/dist/engine/scope-boundaries/contradiction-checker.js +1 -1
  22. package/dist/engine/validation/validation-receipt.d.ts +5 -0
  23. package/dist/engine/validation/validation-receipt.js +9 -1
  24. package/dist/engine/validator/deep-code-checker.js +19 -26
  25. package/dist/engine/validator/validation-report-writer.d.ts +1 -0
  26. package/dist/engine/validator/validation-report-writer.js +3 -2
  27. package/dist/security/adapters/macos-keychain.d.ts +5 -3
  28. package/dist/security/adapters/macos-keychain.js +46 -20
  29. package/dist/tools/create-spec/autopilot-analyzer.d.ts +1 -1
  30. package/dist/tools/create-spec/autopilot-analyzer.js +2 -2
  31. package/dist/tools/create-spec/relevance-scorer.d.ts +1 -1
  32. package/dist/tools/create-spec/relevance-scorer.js +1 -17
  33. package/dist/tools/create-spec.js +1 -1
  34. package/dist/tools/update-status/dod-gates.d.ts +1 -0
  35. package/dist/tools/update-status/dod-gates.js +123 -4
  36. package/dist/types/atomic-upgrade.d.ts +0 -2
  37. package/dist/types/core-bridge.d.ts +0 -108
  38. package/dist/types/core-bridge.js +1 -1
  39. package/dist/types/durable-job.d.ts +0 -40
  40. package/dist/types/handoff-artifacts.d.ts +17 -1
  41. package/dist/types/security.d.ts +0 -5
  42. package/dist/types/transition-log.d.ts +1 -1
  43. package/package.json +7 -43
  44. package/planu-plugin.json +1 -1
  45. package/dist/config/native-release-public-key.pem +0 -3
  46. package/dist/config/native-targets.json +0 -87
  47. package/dist/engine/execution/job-executor.d.ts +0 -16
  48. package/dist/engine/execution/job-executor.js +0 -140
  49. package/dist/engine/execution/native-relevance-scorer.d.ts +0 -6
  50. package/dist/engine/execution/native-relevance-scorer.js +0 -64
  51. package/dist/engine/execution/native-worker-dispatch.d.ts +0 -3
  52. package/dist/engine/execution/native-worker-dispatch.js +0 -46
  53. package/dist/engine/execution/native-worker-protocol.d.ts +0 -6
  54. package/dist/engine/execution/native-worker-protocol.js +0 -33
  55. package/dist/engine/execution/native-worker-runtime.d.ts +0 -4
  56. package/dist/engine/execution/native-worker-runtime.js +0 -177
  57. package/dist/engine/execution/native-worker-thread.d.ts +0 -2
  58. package/dist/engine/execution/native-worker-thread.js +0 -66
  59. package/dist/engine/macos-keychain-native-port.d.ts +0 -10
  60. package/dist/engine/macos-keychain-native-port.js +0 -64
  61. package/dist/engine/native-capability-policy.d.ts +0 -4
  62. package/dist/engine/native-capability-policy.js +0 -24
  63. package/dist/engine/native-platform.d.ts +0 -3
  64. package/dist/engine/native-platform.js +0 -44
  65. package/dist/engine/native-runtime-handshake.d.ts +0 -20
  66. package/dist/engine/native-runtime-handshake.js +0 -230
  67. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +0 -39
  68. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +0 -1310
  69. package/dist/engine/planu-core.darwin-x64.node.manifest.json +0 -39
  70. package/dist/engine/planu-core.darwin-x64.node.sbom.json +0 -1310
  71. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +0 -38
  72. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +0 -1310
  73. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +0 -38
  74. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +0 -1310
  75. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +0 -38
  76. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +0 -1310
  77. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +0 -38
  78. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +0 -1310
  79. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +0 -38
  80. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +0 -1310
  81. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +0 -38
  82. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +0 -1310
  83. package/dist/types/native-worker.d.ts +0 -48
  84. package/dist/types/native-worker.js +0 -2
  85. 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 !== 'implementation-review-agent' ||
148
- report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
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 independent changes-requested finding');
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 !== 'implementation-review-agent' ||
201
- report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
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, isNativeActive, } from '../core-bridge.js';
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: isNativeActive(),
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, dirname } from 'node:path';
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
- if (isNativeActive()) {
214
- // Rust: optimized directory resolution for monorepo patterns
215
- const pkgJsonPaths = fastFindFilesByName(projectPath, ['package.json']);
216
- // Filter directories that match the workspace globs (simplified heuristic).
217
- const dirs = pkgJsonPaths
218
- .map((p) => dirname(p))
219
- .map((p) => relative(projectPath, p))
220
- .filter((rel) => rel !== '' && !isIgnored(rel.split('/')[0] ?? ''));
221
- // In a real implementation we would match against workspaceGlobs exactly,
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(?:changes?|modif(?:y|ies)|alters?|adds?|introduces?|implements?|enables?|exposes?|exposed|mutat(?:e|es|ing))\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, relative } from 'node:path';
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
- if (isNativeActive()) {
178
- const results = fastFindPatterns(projectPath, [config.pattern], [], config.exclude ? [config.exclude] : []);
179
- evidence.push(...results.map((r) => `${relative(projectPath, r.path)}:${r.line}`));
180
- }
181
- else {
182
- const files = await resolveFiles(config.scope, projectPath, config.exclude);
183
- const regex = buildRegex(config.pattern);
184
- if (regex) {
185
- await Promise.all(files.map(async (file) => {
186
- const fullPath = join(projectPath, file);
187
- try {
188
- const content = await readFile(fullPath, 'utf-8');
189
- const lines = content.split('\n');
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
- catch {
199
- // Skip unreadable files
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: 'implementation-review-agent',
17
- agent: 'planu-implementation-reviewer',
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 { MacOSKeychainNativePort, MacOSKeychainOperation, SecretStoreAdapter } from '../../types/security.js';
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 nativePort;
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(nativePort?: () => MacOSKeychainNativePort, timeoutMs?: number);
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 { requireMacOSKeychainNativePort } from '../../engine/core-bridge.js';
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 decoder = new TextDecoder('utf-8', { fatal: true });
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
- nativePort;
29
+ run;
16
30
  timeoutMs;
17
31
  kind = 'macos-keychain';
18
32
  pending = new Map();
19
33
  nextFence = 0;
20
- constructor(nativePort = requireMacOSKeychainNativePort, timeoutMs = TIMEOUT_MS) {
21
- this.nativePort = nativePort;
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
- return decoder.decode(secret);
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
- finally {
37
- secret.fill(0);
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 secret = Buffer.from(value, 'utf8');
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
- return this.nativePort().set(id, secret);
71
+ await this.run(['delete-generic-password', '-a', id, '-s', SERVICE_LABEL]);
46
72
  }
47
- finally {
48
- secret.fill(0);
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, signal?: AbortSignal): Promise<AutopilotAnalysis>;
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, signal) {
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, signal);
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, signal?: AbortSignal): Promise<string[]>;
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, signal) {
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, (signal) => analyzeProjectForSpec(params.projectPath ?? '', params.description, spec.title, knowledge, signal));
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