@planu/cli 5.0.0 → 5.2.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 +76 -1
- package/dist/cli/commands/doctor.d.ts +22 -0
- package/dist/cli/commands/doctor.js +176 -2
- package/dist/cli/commands/spec.js +10 -1
- package/dist/core/spec-validator.js +32 -18
- package/dist/engine/autopilot/bootstrap.js +27 -0
- package/dist/engine/core-bridge.d.ts +28 -0
- package/dist/engine/core-bridge.js +67 -0
- package/dist/engine/drift-monitor.js +16 -18
- package/dist/engine/evidence-gates/artifact-reader.d.ts +2 -0
- package/dist/engine/evidence-gates/artifact-reader.js +59 -2
- package/dist/engine/evidence-gates/evidence-autofill.d.ts +10 -0
- package/dist/engine/evidence-gates/evidence-autofill.js +148 -0
- package/dist/engine/evidence-gates/evidence-skeletons.d.ts +19 -0
- package/dist/engine/evidence-gates/evidence-skeletons.js +69 -0
- package/dist/engine/execution/operation-journal.js +10 -4
- package/dist/engine/living-spec/hash-tracker.js +26 -28
- package/dist/engine/minimality/policy-loader.js +247 -6
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +14 -14
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +14 -14
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +14 -14
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +14 -14
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +14 -14
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +14 -14
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +14 -14
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +14 -14
- package/dist/engine/reverse-engineer/api-detector.js +2 -13
- package/dist/engine/reverse-engineer/complexity-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/config-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/dependency-graph.js +2 -13
- package/dist/engine/reverse-engineer/test-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/walk-ignore.d.ts +3 -0
- package/dist/engine/reverse-engineer/walk-ignore.js +26 -0
- package/dist/engine/spec-format/acceptance-criteria.js +13 -12
- package/dist/engine/spec-format/text-fences.js +20 -2
- package/dist/engine/spec-language/english-only.d.ts +14 -0
- package/dist/engine/spec-language/english-only.js +58 -0
- package/dist/engine/spec-migrator/criteria-scanner.js +11 -13
- package/dist/engine/spec-migrator/drift-detector.js +10 -12
- package/dist/engine/spec-state-syncer.js +1 -1
- package/dist/engine/timing/budget.js +5 -1
- package/dist/engine/vector-store/tfidf.d.ts +13 -9
- package/dist/engine/vector-store/tfidf.js +26 -0
- package/dist/engine/worker-config-loader.d.ts +1 -1
- package/dist/engine/worker-config-loader.js +1 -11
- package/dist/engine/workers/schema.d.ts +0 -8
- package/dist/engine/workers/schema.js +0 -1
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +40 -1
- package/dist/server/routes/specs.js +7 -5
- package/dist/storage/global-store.d.ts +9 -0
- package/dist/storage/global-store.js +23 -0
- package/dist/storage/semantic-index-store.d.ts +23 -0
- package/dist/storage/semantic-index-store.js +105 -0
- package/dist/storage/status-store/self-healing.js +15 -17
- package/dist/tools/challenge-spec/scenarios-utils.js +5 -1
- package/dist/tools/challenge-spec-helpers.d.ts +10 -1
- package/dist/tools/challenge-spec-helpers.js +63 -22
- package/dist/tools/challenge-spec.js +18 -3
- package/dist/tools/check-readiness.js +37 -13
- package/dist/tools/create-spec/spec-builder.d.ts +7 -0
- package/dist/tools/create-spec/spec-builder.js +19 -4
- package/dist/tools/create-spec.js +216 -101
- package/dist/tools/init-project/handler.js +78 -25
- package/dist/tools/learn.js +10 -8
- package/dist/tools/register-spec-tools/core-spec-tools.js +13 -12
- package/dist/tools/registry/auth.js +1 -11
- package/dist/tools/semantic-search-handler.js +5 -6
- package/dist/tools/status-handler.js +19 -6
- package/dist/tools/sync-spec-state-handler.js +49 -1
- package/dist/tools/update-status/batch.d.ts +6 -2
- package/dist/tools/update-status/batch.js +58 -1
- package/dist/tools/update-status/dod-gates.d.ts +16 -1
- package/dist/tools/update-status/dod-gates.js +191 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +8 -0
- package/dist/tools/update-status/done-receipt-verifier.js +37 -2
- package/dist/tools/update-status/evidence-gate.d.ts +4 -0
- package/dist/tools/update-status/evidence-gate.js +67 -2
- package/dist/tools/update-status/file-sync.d.ts +2 -2
- package/dist/tools/update-status/index.d.ts +23 -1
- package/dist/tools/update-status/index.js +201 -24
- package/dist/tools/update-status/transition-guard.js +13 -1
- package/dist/tools/validation-loop-handler.js +16 -15
- package/dist/tools/workspace-dashboard-handler.js +38 -0
- package/dist/types/evidence-autofill.d.ts +34 -0
- package/dist/types/evidence-autofill.js +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/spec/core.d.ts +6 -0
- package/dist/types/spec/inputs.d.ts +7 -2
- package/dist/types/spec-format.d.ts +1 -1
- package/dist/types/spec-language-translation.d.ts +18 -0
- package/dist/types/spec-language-translation.js +5 -0
- package/dist/types/spec-registry.d.ts +0 -2
- package/dist/types/status.d.ts +2 -0
- package/dist/types/transition-log.d.ts +1 -1
- package/dist/types/validation.d.ts +8 -2
- package/dist/types/vector-store.d.ts +18 -0
- package/dist/types/workers.d.ts +0 -3
- package/package.json +11 -10
- package/planu-native.json +8 -29
- package/planu-plugin.json +1 -1
- package/dist/engine/security/cve-refresher.d.ts +0 -12
- package/dist/engine/security/cve-refresher.js +0 -128
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
+
## [5.2.0] - 2026-08-04
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- feat(autopilot): refresh semantic index fire-and-forget on spec:created (SPEC-1345)
|
|
5
|
+
- feat(i18n): auto-detect and persist user locale (SPEC-1347)
|
|
6
|
+
- feat(spec-language): offer translation instead of rejecting non-English specs (SPEC-1342)
|
|
7
|
+
- feat(semantic-search): persist index with incremental updates (SPEC-1345)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
- fix(storage): classify non-ENOENT failures in sync global-config read
|
|
11
|
+
- fix(status): prefer implementing work over review work in compact status (SPEC-1228)
|
|
12
|
+
- fix(challenge): stop handler names activating event scenarios across clauses (SPEC-1262)
|
|
13
|
+
|
|
14
|
+
### Performance
|
|
15
|
+
- perf(core-bridge): route main-thread hot paths through native dispatch (SPEC-1344)
|
|
16
|
+
- perf(init-project): parallelize independent pipeline stages (SPEC-1343)
|
|
17
|
+
|
|
18
|
+
### Chores
|
|
19
|
+
- chore(website): regenerate deterministic product proof
|
|
20
|
+
- chore(planu): close lifecycle for 8 specs done + SPEC-1368 bug spec + session checkpoint
|
|
21
|
+
- chore(planu): file SPEC-1367 dogfood bug spec (validate false-negative on test-only criteria)
|
|
22
|
+
- chore(planu): file SPEC-1366 dogfood bug spec and refresh session context
|
|
23
|
+
- chore(doctor): remove commercial remnants and deepen diagnostics (SPEC-1346)
|
|
24
|
+
- chore(planu): file SPEC-1365 dogfood bug spec (teamSuggestion recommends unregistered tools)
|
|
25
|
+
- chore(planu): session checkpoint after v5.1.1 release
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## [5.1.1] - 2026-08-04
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
- fix(release-harness): resolve published artifact from staged repack when publish has no tarball argv
|
|
32
|
+
- fix(release): stop CLI-guard stdout pollution, npm view array probe, and tarball-path publish leak
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## [5.1.0] - 2026-08-04
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
- feat(lifecycle): automate done evidence pipeline end to end
|
|
39
|
+
|
|
40
|
+
### Bug Fixes
|
|
41
|
+
- fix(update-status): make typed failure explicit at dod-gates catch sites
|
|
42
|
+
- fix(deps): patch hono, fast-uri, ip-address, and undici advisories via overrides
|
|
43
|
+
- fix(evidence-gates): derive contract example kinds from filenames
|
|
44
|
+
- fix(reverse-engineer): share walk-ignore list and add fast release-gate test lane
|
|
45
|
+
- fix(challenge): suppress ungrounded concurrency boilerplate in challenge_spec
|
|
46
|
+
- fix(update-status): report per-spec failure reasons in batch results
|
|
47
|
+
- fix(create-spec): guarantee spec.md write before reporting persisted
|
|
48
|
+
- fix(create-spec): release idempotency claim when create_spec fails before commit
|
|
49
|
+
- fix(release): accept npm 12 pack metadata
|
|
50
|
+
- fix(deps): patch transitive security advisories
|
|
51
|
+
- fix(privacy): redact minimality policy locators
|
|
52
|
+
- fix(lifecycle): route review evidence by target
|
|
53
|
+
- fix(readiness): unify canonical validation evidence
|
|
54
|
+
|
|
55
|
+
### Chores
|
|
56
|
+
- chore(pnpm): disable modules purge confirmation for non-TTY automation
|
|
57
|
+
- chore(planu): session checkpoint before release
|
|
58
|
+
- chore(planu): close SPEC-1350 lifecycle state
|
|
59
|
+
- chore(planu): hand off freshness blocker
|
|
60
|
+
- chore(planu): checkpoint release handoff
|
|
61
|
+
- chore(planu): preserve native engine review state
|
|
62
|
+
- chore(planu): persist delayed challenge evidence
|
|
63
|
+
- chore(planu): recover delayed audit specs
|
|
64
|
+
- chore(planu): capture lifecycle dogfood failures
|
|
65
|
+
- chore(planu): persist release remediation handoffs
|
|
66
|
+
- chore(planu): approve final release blockers
|
|
67
|
+
- chore(planu): start dependency security remediation
|
|
68
|
+
- chore(planu): approve final security remediation
|
|
69
|
+
- chore(planu): checkpoint final release remediations
|
|
70
|
+
- chore(planu): start lifecycle routing implementation
|
|
71
|
+
- chore(planu): approve lifecycle routing remediation
|
|
72
|
+
- chore(planu): checkpoint release remediation specs
|
|
73
|
+
- chore(planu): track v5 release dogfood regressions
|
|
74
|
+
|
|
75
|
+
|
|
1
76
|
## [5.0.0] - 2026-07-31
|
|
2
77
|
|
|
3
78
|
### Breaking Changes
|
|
@@ -4508,4 +4583,4 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
|
|
|
4508
4583
|
- Mermaid diagram generation (architecture, sequence, state machine, ER, data flow)
|
|
4509
4584
|
- Multi-language i18n (EN/ES/PT) for generated specs
|
|
4510
4585
|
- Clean Architecture (hexagonal) — engine, tools, storage, types layers
|
|
4511
|
-
- 10,857 tests with ≥95% coverage
|
|
4586
|
+
- 10,857 tests with ≥95% coverage
|
|
@@ -10,7 +10,29 @@ interface ToolCheck {
|
|
|
10
10
|
paths: string[];
|
|
11
11
|
format: 'standard' | 'zed';
|
|
12
12
|
}
|
|
13
|
+
interface DeepCheckResult {
|
|
14
|
+
name: string;
|
|
15
|
+
/** 'warn' is a degraded-but-working state (e.g. TS fallback active); 'fail' blocks usage. */
|
|
16
|
+
status: 'ok' | 'warn' | 'fail';
|
|
17
|
+
detail: string;
|
|
18
|
+
}
|
|
13
19
|
declare function checkTool(check: ToolCheck): ToolReport[];
|
|
20
|
+
/** Check (1): the running Node version satisfies the `engines.node` field in package.json. */
|
|
21
|
+
declare function checkNodeVersion(): DeepCheckResult;
|
|
22
|
+
/**
|
|
23
|
+
* Check (2): the native Rust module loads, with a remediation hint when it doesn't.
|
|
24
|
+
* Missing native is a supported degraded mode (TypeScript fallback stays correct), so
|
|
25
|
+
* this reports 'warn' rather than 'fail' — it never blocks `planu doctor`'s exit code.
|
|
26
|
+
*/
|
|
27
|
+
declare function checkNativeModule(): DeepCheckResult;
|
|
28
|
+
/** Check (3): PLANU_HOME / the resolved data directory is writable. */
|
|
29
|
+
declare function checkDataDirWritable(): DeepCheckResult;
|
|
30
|
+
/** Check (4): the runtime SQLite database opens without corruption. */
|
|
31
|
+
declare function checkRuntimeDatabase(): DeepCheckResult;
|
|
32
|
+
/** Check (5): the active locale resolves to a real translated message. */
|
|
33
|
+
declare function checkLocaleResolution(): DeepCheckResult;
|
|
34
|
+
declare function runDeepChecks(): DeepCheckResult[];
|
|
35
|
+
export { checkNodeVersion, checkNativeModule, checkDataDirWritable, checkRuntimeDatabase, checkLocaleResolution, runDeepChecks, };
|
|
14
36
|
export { checkTool };
|
|
15
37
|
export declare const doctorCommand: CliCommand;
|
|
16
38
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
// cli/commands/doctor.ts — planu doctor: verify MCP installations health (SPEC-236)
|
|
2
|
-
|
|
2
|
+
// Deep diagnostics (Node version, native engine, storage writability, runtime DB,
|
|
3
|
+
// locale resolution) added by SPEC-1346 / SPEC-1344.
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
5
|
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
5
8
|
import { bold, cyan, green, yellow, red, dim } from '../colors.js';
|
|
6
9
|
import { readJsonFile, hasPlanuEntry } from './install.js';
|
|
10
|
+
import { resolveStorageLayout } from '../../storage/storage-layout.js';
|
|
11
|
+
import { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
12
|
+
import { probeNativeLoad } from '../../engine/core-bridge.js';
|
|
13
|
+
import { getLocale, t } from '../../i18n/index.js';
|
|
7
14
|
// ---------------------------------------------------------------------------
|
|
8
15
|
// Constants
|
|
9
16
|
// ---------------------------------------------------------------------------
|
|
@@ -86,6 +93,166 @@ function checkTool(check) {
|
|
|
86
93
|
}
|
|
87
94
|
return reports;
|
|
88
95
|
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Deep diagnostics (SPEC-1346 / SPEC-1344)
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/** Root of the package, resolved relative to this module so it works from src/ and dist/. */
|
|
100
|
+
const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../../package.json');
|
|
101
|
+
function parseVersionTuple(version) {
|
|
102
|
+
const match = /(\d+)\.(\d+)\.(\d+)/.exec(version);
|
|
103
|
+
return [Number(match?.[1] ?? 0), Number(match?.[2] ?? 0), Number(match?.[3] ?? 0)];
|
|
104
|
+
}
|
|
105
|
+
function isVersionAtLeast(actual, required) {
|
|
106
|
+
for (let i = 0; i < 3; i++) {
|
|
107
|
+
const a = actual[i] ?? 0;
|
|
108
|
+
const r = required[i] ?? 0;
|
|
109
|
+
if (a !== r) {
|
|
110
|
+
return a > r;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/** Check (1): the running Node version satisfies the `engines.node` field in package.json. */
|
|
116
|
+
function checkNodeVersion() {
|
|
117
|
+
let requiredRange;
|
|
118
|
+
try {
|
|
119
|
+
const pkg = JSON.parse(readFileSync(PACKAGE_ROOT, 'utf-8'));
|
|
120
|
+
requiredRange = pkg.engines?.node ?? '';
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return {
|
|
124
|
+
name: 'Node version',
|
|
125
|
+
status: 'fail',
|
|
126
|
+
detail: 'Could not read package.json engines field',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (!requiredRange) {
|
|
130
|
+
return { name: 'Node version', status: 'ok', detail: `Running Node ${process.version}` };
|
|
131
|
+
}
|
|
132
|
+
const required = parseVersionTuple(requiredRange);
|
|
133
|
+
const actual = parseVersionTuple(process.version);
|
|
134
|
+
if (isVersionAtLeast(actual, required)) {
|
|
135
|
+
return {
|
|
136
|
+
name: 'Node version',
|
|
137
|
+
status: 'ok',
|
|
138
|
+
detail: `${process.version} satisfies ${requiredRange}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
name: 'Node version',
|
|
143
|
+
status: 'fail',
|
|
144
|
+
detail: `${process.version} does not satisfy required range ${requiredRange}. Upgrade Node to continue.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check (2): the native Rust module loads, with a remediation hint when it doesn't.
|
|
149
|
+
* Missing native is a supported degraded mode (TypeScript fallback stays correct), so
|
|
150
|
+
* this reports 'warn' rather than 'fail' — it never blocks `planu doctor`'s exit code.
|
|
151
|
+
*/
|
|
152
|
+
function checkNativeModule() {
|
|
153
|
+
if (process.env.DISABLE_NATIVE_CORE === '1') {
|
|
154
|
+
return {
|
|
155
|
+
name: 'Native engine',
|
|
156
|
+
status: 'ok',
|
|
157
|
+
detail: 'Disabled via DISABLE_NATIVE_CORE=1 (TypeScript fallback in use)',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const diagnostic = probeNativeLoad();
|
|
161
|
+
if (diagnostic.loaded) {
|
|
162
|
+
return { name: 'Native engine', status: 'ok', detail: `Loaded (${diagnostic.loaded})` };
|
|
163
|
+
}
|
|
164
|
+
const reason = diagnostic.errors[0] ?? 'unknown failure';
|
|
165
|
+
return {
|
|
166
|
+
name: 'Native engine',
|
|
167
|
+
status: 'warn',
|
|
168
|
+
detail: `Failed to load (${reason}). Running on the TypeScript fallback. Reinstall the platform package for your OS/arch to restore native acceleration.`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Check (3): PLANU_HOME / the resolved data directory is writable. */
|
|
172
|
+
function checkDataDirWritable() {
|
|
173
|
+
const layout = resolveStorageLayout();
|
|
174
|
+
const probePath = join(layout.data, `.doctor-write-probe-${String(process.pid)}`);
|
|
175
|
+
try {
|
|
176
|
+
mkdirSync(layout.data, { recursive: true, mode: 0o700 });
|
|
177
|
+
writeFileSync(probePath, 'ok', { mode: 0o600 });
|
|
178
|
+
unlinkSync(probePath);
|
|
179
|
+
return { name: 'Data directory', status: 'ok', detail: `Writable: ${layout.data}` };
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
183
|
+
return {
|
|
184
|
+
name: 'Data directory',
|
|
185
|
+
status: 'fail',
|
|
186
|
+
detail: `Cannot write to ${layout.data} (${message}). Check ownership/permissions on that path or set PLANU_HOME to a writable location.`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Check (4): the runtime SQLite database opens without corruption. */
|
|
191
|
+
function checkRuntimeDatabase() {
|
|
192
|
+
const layout = resolveStorageLayout();
|
|
193
|
+
try {
|
|
194
|
+
const db = new RuntimeDatabase({ path: layout.runtimeDatabase });
|
|
195
|
+
db.close();
|
|
196
|
+
return {
|
|
197
|
+
name: 'Runtime database',
|
|
198
|
+
status: 'ok',
|
|
199
|
+
detail: `Opens correctly: ${layout.runtimeDatabase}`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
return {
|
|
205
|
+
name: 'Runtime database',
|
|
206
|
+
status: 'fail',
|
|
207
|
+
detail: `Cannot open ${layout.runtimeDatabase} (${message}). Move or delete the corrupted file and retry — Planu recreates it on next use.`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Check (5): the active locale resolves to a real translated message. */
|
|
212
|
+
function checkLocaleResolution() {
|
|
213
|
+
const locale = getLocale();
|
|
214
|
+
const sample = t('locale.changed', locale);
|
|
215
|
+
if (sample === 'locale.changed') {
|
|
216
|
+
return {
|
|
217
|
+
name: 'Locale resolution',
|
|
218
|
+
status: 'fail',
|
|
219
|
+
detail: `Locale "${locale}" resolved but message bundle failed to load. Reinstall Planu to restore i18n/messages/.`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return { name: 'Locale resolution', status: 'ok', detail: `Resolved locale: ${locale}` };
|
|
223
|
+
}
|
|
224
|
+
function runDeepChecks() {
|
|
225
|
+
return [
|
|
226
|
+
checkNodeVersion(),
|
|
227
|
+
checkNativeModule(),
|
|
228
|
+
checkDataDirWritable(),
|
|
229
|
+
checkRuntimeDatabase(),
|
|
230
|
+
checkLocaleResolution(),
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
function deepCheckIcon(status) {
|
|
234
|
+
if (status === 'ok') {
|
|
235
|
+
return green('+');
|
|
236
|
+
}
|
|
237
|
+
return status === 'warn' ? yellow('!') : red('x');
|
|
238
|
+
}
|
|
239
|
+
function deepCheckLabel(status) {
|
|
240
|
+
if (status === 'ok') {
|
|
241
|
+
return green('OK');
|
|
242
|
+
}
|
|
243
|
+
return status === 'warn' ? yellow('DEGRADED') : red('FAIL');
|
|
244
|
+
}
|
|
245
|
+
function printDeepChecks(results) {
|
|
246
|
+
process.stdout.write(`\n${bold('Deep diagnostics')}\n\n`);
|
|
247
|
+
for (const result of results) {
|
|
248
|
+
const icon = deepCheckIcon(result.status);
|
|
249
|
+
const label = deepCheckLabel(result.status);
|
|
250
|
+
process.stdout.write(` [${icon}] ${result.name.padEnd(18)} ${label}\n`);
|
|
251
|
+
process.stdout.write(` ${dim(result.detail)}\n`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Exported for tests
|
|
255
|
+
export { checkNodeVersion, checkNativeModule, checkDataDirWritable, checkRuntimeDatabase, checkLocaleResolution, runDeepChecks, };
|
|
89
256
|
function statusIcon(status) {
|
|
90
257
|
switch (status) {
|
|
91
258
|
case 'ok':
|
|
@@ -143,9 +310,16 @@ export const doctorCommand = {
|
|
|
143
310
|
}
|
|
144
311
|
}
|
|
145
312
|
printReports(allReports);
|
|
313
|
+
const deepChecks = runDeepChecks();
|
|
314
|
+
printDeepChecks(deepChecks);
|
|
146
315
|
const installed = allReports.filter((r) => r.status === 'ok').length;
|
|
147
316
|
const invalid = allReports.filter((r) => r.status === 'invalid-config').length;
|
|
317
|
+
const failedDeepChecks = deepChecks.filter((c) => c.status === 'fail').length;
|
|
148
318
|
process.stdout.write('\n');
|
|
319
|
+
if (failedDeepChecks > 0) {
|
|
320
|
+
process.stdout.write(`${red('Warning:')} ${String(failedDeepChecks)} deep diagnostic check(s) failed. See remediation steps above.\n`);
|
|
321
|
+
process.exitCode = 1;
|
|
322
|
+
}
|
|
149
323
|
if (invalid > 0) {
|
|
150
324
|
process.stdout.write(`${yellow('Warning:')} ${String(invalid)} config file(s) have invalid JSON. Run ${cyan('planu install')} to fix.\n`);
|
|
151
325
|
}
|
|
@@ -30,13 +30,14 @@ function printSpecSubcommandHelp() {
|
|
|
30
30
|
` ${'list'.padEnd(10)} ${dim('List specs [--status draft|approved|done...]')}`,
|
|
31
31
|
` ${'show'.padEnd(10)} ${dim('Show spec details (SPEC-NNN)')}`,
|
|
32
32
|
` ${'status'.padEnd(10)} ${dim('Update spec status (SPEC-NNN <status>)')}`,
|
|
33
|
+
` ${''.padEnd(10)} ${dim('Batch targets: draft|review|approved|implementing|discarded')}`,
|
|
33
34
|
` ${'validate'.padEnd(10)} ${dim('Validate a spec against its codebase')}`,
|
|
34
35
|
'',
|
|
35
36
|
cyan('Examples:'),
|
|
36
37
|
` planu spec create "Add login flow"`,
|
|
37
38
|
` planu spec list --status approved`,
|
|
38
39
|
` planu spec show SPEC-001`,
|
|
39
|
-
` planu spec status SPEC-001 done
|
|
40
|
+
` planu spec status SPEC-001 done --implementation-review-digest sha256:<64-hex>`,
|
|
40
41
|
` planu spec validate SPEC-001`,
|
|
41
42
|
];
|
|
42
43
|
process.stdout.write(lines.join('\n') + '\n');
|
|
@@ -207,6 +208,7 @@ async function runShow(args, flags) {
|
|
|
207
208
|
// ---------------------------------------------------------------------------
|
|
208
209
|
// Subcommand: spec status
|
|
209
210
|
// ---------------------------------------------------------------------------
|
|
211
|
+
// eslint-disable-next-line complexity -- batch done requires an explicit pre-resolution fail-closed branch
|
|
210
212
|
async function runStatus(args, flags) {
|
|
211
213
|
const { positionals, values } = parseArgs({
|
|
212
214
|
args,
|
|
@@ -215,6 +217,7 @@ async function runStatus(args, flags) {
|
|
|
215
217
|
notes: { type: 'string', short: 'n' },
|
|
216
218
|
batch: { type: 'boolean' },
|
|
217
219
|
set: { type: 'string', short: 's' },
|
|
220
|
+
'implementation-review-digest': { type: 'string' },
|
|
218
221
|
},
|
|
219
222
|
strict: false,
|
|
220
223
|
allowPositionals: true,
|
|
@@ -232,6 +235,11 @@ async function runStatus(args, flags) {
|
|
|
232
235
|
process.exitCode = 1;
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
238
|
+
if (batch && status === 'done') {
|
|
239
|
+
process.stderr.write(`${red('BATCH_DONE_UNSUPPORTED:')} Close specs individually with update_status(done) and full implementation review evidence.\n`);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
235
243
|
const projectId = values['project-id'] ?? detectProjectId();
|
|
236
244
|
if (batch) {
|
|
237
245
|
const result = await handleUpdateStatusBatch({
|
|
@@ -253,6 +261,7 @@ async function runStatus(args, flags) {
|
|
|
253
261
|
projectId,
|
|
254
262
|
status: status,
|
|
255
263
|
reviewNotes: values.notes ?? undefined,
|
|
264
|
+
implementationReviewDigest: values['implementation-review-digest'] ?? undefined,
|
|
256
265
|
});
|
|
257
266
|
if (result.isError) {
|
|
258
267
|
process.stderr.write(`${red(formatToolResult(result, flags))}\n`);
|
|
@@ -6,6 +6,8 @@ import { readFile } from 'node:fs/promises';
|
|
|
6
6
|
import { checkSpecReadiness } from '../engine/readiness-checker.js';
|
|
7
7
|
import { scoreSpecQuality } from '../engine/spec-quality-scorer.js';
|
|
8
8
|
import { parseFrontmatter, stripFrontmatter } from '../engine/frontmatter-parser.js';
|
|
9
|
+
import { extractNormalizedAcceptanceCriteria } from '../engine/spec-format/acceptance-criteria.js';
|
|
10
|
+
import { parseFrontmatterScenarios } from '../engine/validator/spec-compliance-runner.js';
|
|
9
11
|
// ── Required frontmatter fields ──────────────────────────────────────────────
|
|
10
12
|
const REQUIRED_FRONTMATTER_FIELDS = ['id', 'title', 'status', 'type', 'target', 'scope'];
|
|
11
13
|
// ── Required body sections (SPEC-785 aligned with lean-spec-generator output) ─
|
|
@@ -133,6 +135,20 @@ function mapQualityToValidation(dimensions) {
|
|
|
133
135
|
}
|
|
134
136
|
return warnings;
|
|
135
137
|
}
|
|
138
|
+
function countCanonicalBddScenarios(raw) {
|
|
139
|
+
const criteria = extractNormalizedAcceptanceCriteria(raw, { allowLegacyBodyFallback: false });
|
|
140
|
+
const bodyCriteria = criteria.filter((criterion) => criterion.source === 'body');
|
|
141
|
+
const bddCandidates = bodyCriteria.length > 0
|
|
142
|
+
? bodyCriteria.map((criterion) => criterion.text)
|
|
143
|
+
: parseFrontmatterScenarios(raw).map((scenario) => scenario.steps?.map((step) => `${step.keyword} ${step.text}`).join(' ') ??
|
|
144
|
+
scenario.title);
|
|
145
|
+
return {
|
|
146
|
+
criteriaCount: criteria.length,
|
|
147
|
+
bddScenarioCount: bddCandidates.filter((criterion) => /\b(?:given|dado)\b/i.test(criterion) &&
|
|
148
|
+
/\b(?:when|cuando)\b/i.test(criterion) &&
|
|
149
|
+
/\b(?:then|entonces)\b/i.test(criterion)).length,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
136
152
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
137
153
|
/**
|
|
138
154
|
* Validate a spec's format against the unified spec.md schema (SPEC-630).
|
|
@@ -168,22 +184,16 @@ export async function validateSpecFormat(spec, opts) {
|
|
|
168
184
|
allWarnings.push(...unifiedResult.warnings);
|
|
169
185
|
// ── 4. Readiness check ───────────────────────────────────────────────────
|
|
170
186
|
let readinessScore = 0;
|
|
171
|
-
let
|
|
172
|
-
|
|
187
|
+
let readinessReport;
|
|
188
|
+
// The canonical metrics are independent from the readiness checker. Keep
|
|
189
|
+
// them truthful even when that best-effort checker throws.
|
|
190
|
+
const { criteriaCount, bddScenarioCount } = countCanonicalBddScenarios(raw);
|
|
173
191
|
try {
|
|
174
192
|
// Cast to Spec since checkSpecReadiness needs the full type but only reads
|
|
175
193
|
// the fields available on the Pick type we receive.
|
|
176
|
-
|
|
177
|
-
readinessScore =
|
|
178
|
-
|
|
179
|
-
bddScenarioCount = (raw.match(/^\s+-\s+title:/gm) ?? []).length;
|
|
180
|
-
// Count criteria lines (checkbox or GIVEN/WHEN/THEN)
|
|
181
|
-
const bodyLines = body.split('\n');
|
|
182
|
-
criteriaCount = bodyLines.filter((l) => /^- \[[ x]\]/i.test(l.trim()) || /^- .*\bgiven\b.*\bwhen\b.*\bthen\b/i.test(l.trim())).length;
|
|
183
|
-
if (criteriaCount === 0) {
|
|
184
|
-
criteriaCount = bddScenarioCount;
|
|
185
|
-
}
|
|
186
|
-
const mapped = mapReadinessToValidation(report.issues.blockers, report.issues.warnings);
|
|
194
|
+
readinessReport = await checkSpecReadiness(spec, readinessMode);
|
|
195
|
+
readinessScore = readinessReport.score;
|
|
196
|
+
const mapped = mapReadinessToValidation(readinessReport.issues.blockers, readinessReport.issues.warnings);
|
|
187
197
|
allErrors.push(...mapped.errors);
|
|
188
198
|
allWarnings.push(...mapped.warnings);
|
|
189
199
|
}
|
|
@@ -197,11 +207,12 @@ export async function validateSpecFormat(spec, opts) {
|
|
|
197
207
|
return { valid, errors: allErrors, warnings: allWarnings };
|
|
198
208
|
}
|
|
199
209
|
// ── 5. Quality scoring ───────────────────────────────────────────────────
|
|
200
|
-
let qualityScore
|
|
201
|
-
let qualityGrade
|
|
210
|
+
let qualityScore;
|
|
211
|
+
let qualityGrade;
|
|
212
|
+
let qualityReport;
|
|
202
213
|
if (!skipQuality) {
|
|
203
214
|
try {
|
|
204
|
-
|
|
215
|
+
qualityReport = await scoreSpecQuality(spec);
|
|
205
216
|
qualityScore = qualityReport.score.total;
|
|
206
217
|
qualityGrade = qualityReport.score.grade;
|
|
207
218
|
const qualityWarnings = mapQualityToValidation(qualityReport.dimensions);
|
|
@@ -226,11 +237,14 @@ export async function validateSpecFormat(spec, opts) {
|
|
|
226
237
|
warnings: allWarnings,
|
|
227
238
|
metrics: {
|
|
228
239
|
readinessScore,
|
|
229
|
-
qualityScore,
|
|
230
|
-
qualityGrade,
|
|
231
240
|
bddScenarioCount,
|
|
232
241
|
criteriaCount,
|
|
242
|
+
...(qualityScore === undefined || qualityGrade === undefined
|
|
243
|
+
? {}
|
|
244
|
+
: { qualityScore, qualityGrade }),
|
|
233
245
|
},
|
|
246
|
+
readinessReport,
|
|
247
|
+
qualityReport,
|
|
234
248
|
};
|
|
235
249
|
}
|
|
236
250
|
//# sourceMappingURL=spec-validator.js.map
|
|
@@ -99,6 +99,31 @@ function registerPluginInstalledListener() {
|
|
|
99
99
|
})();
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* SPEC-1345 AC4: Fire-and-forget semantic index refresh on spec:created.
|
|
104
|
+
* Reconciles the persisted TF-IDF index against the current corpus so the next
|
|
105
|
+
* semantic_search call hits a warm, up-to-date index.
|
|
106
|
+
*/
|
|
107
|
+
function registerSemanticIndexRefreshListener() {
|
|
108
|
+
onAutopilotEvent('spec:created', (event) => {
|
|
109
|
+
void (async () => {
|
|
110
|
+
try {
|
|
111
|
+
const { getOrCreateBackend } = await import('../../storage/vector-store/backend-factory.js');
|
|
112
|
+
const { SemanticIndexStore } = await import('../../storage/semantic-index-store.js');
|
|
113
|
+
const backend = await getOrCreateBackend(event.projectId);
|
|
114
|
+
const allDocs = await backend.getAll();
|
|
115
|
+
if (allDocs.length === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const indexStore = new SemanticIndexStore(event.projectId, 'all');
|
|
119
|
+
await indexStore.loadOrBuild(allDocs.map((doc) => ({ id: doc.id, content: doc.content })));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* best-effort — never surface to user */
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
102
127
|
const AUTOPILOT_EVENTS = [
|
|
103
128
|
'spec:created',
|
|
104
129
|
'spec:status:approved',
|
|
@@ -133,5 +158,7 @@ export function bootstrapAutopilotHandlers() {
|
|
|
133
158
|
registerPluginInstalledListener();
|
|
134
159
|
// SPEC-628: Register daily cron for auto_fix_health (moved from update_status cascade)
|
|
135
160
|
registerDailyCronListener();
|
|
161
|
+
// SPEC-1345 AC4: Register semantic index refresh on spec:created
|
|
162
|
+
registerSemanticIndexRefreshListener();
|
|
136
163
|
}
|
|
137
164
|
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -31,6 +31,34 @@ export declare function fastExtractTsRelations(rootPath: string, paths: string[]
|
|
|
31
31
|
export declare function fastQueryAffectedNodes(seed: string, edges: GraphEdgeInputRs[], relationFilter: string[], policy: ProjectGraphLimitsPolicyRs, overrides?: Partial<ResolvedProjectGraphLimitsRs>): AffectedNodeRs[];
|
|
32
32
|
export declare function fastQueryShortestPath(from: string, to: string, edges: GraphEdgeInputRs[], relationFilter: string[], policy: ProjectGraphLimitsPolicyRs, overrides?: Partial<ResolvedProjectGraphLimitsRs>): ShortestPathResultRs;
|
|
33
33
|
export declare function fastSelectCompactGraphSlice(seeds: string[], nodes: GraphNodeInputRs[], edges: GraphEdgeInputRs[], policy: ProjectGraphLimitsPolicyRs, overrides?: Partial<ResolvedProjectGraphLimitsRs>): CompactGraphSliceRs;
|
|
34
|
+
/** Worker-dispatched variant of {@link fastDetectDriftParallel} for main-thread callers. */
|
|
35
|
+
export declare function fastDetectDriftParallelAsync(rootPath: string, keywords: string[]): Promise<{
|
|
36
|
+
value: DriftResult;
|
|
37
|
+
degradationNotice?: string;
|
|
38
|
+
}>;
|
|
39
|
+
/** Worker-dispatched variant of {@link fastScanAndHashFiles} for main-thread callers. */
|
|
40
|
+
export declare function fastScanAndHashFilesAsync(rootPath: string): Promise<{
|
|
41
|
+
value: FileHash[];
|
|
42
|
+
degradationNotice?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Worker-dispatched variant of {@link fastScanProjectMetadata} for main-thread callers. */
|
|
45
|
+
export declare function fastScanProjectMetadataAsync(rootPath: string): Promise<{
|
|
46
|
+
value: FileMetadata[];
|
|
47
|
+
degradationNotice?: string;
|
|
48
|
+
}>;
|
|
49
|
+
/** Worker-dispatched variant of {@link fastScanSpecs} for main-thread callers. */
|
|
50
|
+
export declare function fastScanSpecsAsync(rootPath: string): Promise<{
|
|
51
|
+
value: SpecBriefRs[] | null;
|
|
52
|
+
degradationNotice?: string;
|
|
53
|
+
}>;
|
|
34
54
|
export declare const isNativeActive: () => boolean;
|
|
35
55
|
export declare const nativeLoadDiagnostic: () => NativeLoadDiagnostic;
|
|
56
|
+
/**
|
|
57
|
+
* Force a native load attempt regardless of the calling thread and return the
|
|
58
|
+
* resulting diagnostic. `nativeLoadDiagnostic()` alone only reports load state
|
|
59
|
+
* that some other main-thread-bypassing caller already triggered (e.g. the
|
|
60
|
+
* macOS Keychain port); this is the main-thread-safe way to actually probe
|
|
61
|
+
* native availability, used by `planu doctor`.
|
|
62
|
+
*/
|
|
63
|
+
export declare const probeNativeLoad: () => NativeLoadDiagnostic;
|
|
36
64
|
//# sourceMappingURL=core-bridge.d.ts.map
|
|
@@ -12,6 +12,7 @@ import { detectMusl } from './native-platform.js';
|
|
|
12
12
|
import { findDuplicateBlocks } from './core-bridge-duplicate-blocks.js';
|
|
13
13
|
import { appendGapEntry, verifyGapsChain } from './core-bridge-gaps.js';
|
|
14
14
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
15
|
+
import { runNativeWorkerOperation } from './execution/native-worker-runtime.js';
|
|
15
16
|
export { negotiateNativeRuntime, NATIVE_CAPABILITY_ABI, NATIVE_NODE_API_ABI, NATIVE_PROTOCOL_ABI, RUNTIME_STORAGE_SCHEMA, };
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
const reportedDegradations = new Set();
|
|
@@ -678,9 +679,75 @@ export function fastSelectCompactGraphSlice(seeds, nodes, edges, policy, overrid
|
|
|
678
679
|
}
|
|
679
680
|
return tsSelectCompactGraphSlice(seeds, nodes, edges, limits);
|
|
680
681
|
}
|
|
682
|
+
// Main-thread dispatch strategy (SPEC-1344) -----------------------------------
|
|
683
|
+
// getNative() deliberately returns null on the main thread so long native FFI
|
|
684
|
+
// calls never block the event loop (see comment above getNative()). For hot
|
|
685
|
+
// paths that need acceleration anyway, dispatchViaWorker() moves the call onto
|
|
686
|
+
// the existing native-worker-dispatch.ts runtime (a worker thread, where
|
|
687
|
+
// isMainThread is false and native loads normally). If the worker fails to
|
|
688
|
+
// spawn, times out, or the operation isn't allowlisted, it falls back to the
|
|
689
|
+
// caller-supplied synchronous implementation so behavior never regresses.
|
|
690
|
+
const WORKER_DISPATCH_TIMEOUT_MS = 30_000;
|
|
691
|
+
const DEGRADATION_NOTICE_THRESHOLD_MS = 2_000;
|
|
692
|
+
async function dispatchViaWorker(operation, payload, projectId, syncFallback) {
|
|
693
|
+
const start = Date.now();
|
|
694
|
+
if (isMainThread && process.env.DISABLE_NATIVE_CORE !== '1') {
|
|
695
|
+
try {
|
|
696
|
+
const value = (await runNativeWorkerOperation({
|
|
697
|
+
operation,
|
|
698
|
+
payload,
|
|
699
|
+
projectId,
|
|
700
|
+
deadlineAt: start + WORKER_DISPATCH_TIMEOUT_MS,
|
|
701
|
+
}));
|
|
702
|
+
return { value };
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
/* reliability-optional: NATIVE_WORKER_DISPATCH_FAILED — falls through to sync fallback */
|
|
706
|
+
reportOnce(`NATIVE_WORKER_DISPATCH_FAILED:${operation}`, () => {
|
|
707
|
+
reportClassifiedDegradation('NATIVE_WORKER_DISPATCH_FAILED', error instanceof Error ? error : FALLBACK_ERROR);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const value = syncFallback();
|
|
712
|
+
const elapsedMs = Date.now() - start;
|
|
713
|
+
if (elapsedMs > DEGRADATION_NOTICE_THRESHOLD_MS) {
|
|
714
|
+
return {
|
|
715
|
+
value,
|
|
716
|
+
degradationNotice: `native engine unavailable — run planu doctor (fallback took ${String(elapsedMs)}ms)`,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
return { value };
|
|
720
|
+
}
|
|
721
|
+
/** Worker-dispatched variant of {@link fastDetectDriftParallel} for main-thread callers. */
|
|
722
|
+
export async function fastDetectDriftParallelAsync(rootPath, keywords) {
|
|
723
|
+
return dispatchViaWorker('detect-drift', { rootPath, keywords }, fastHashProjectPath(rootPath), () => fastDetectDriftParallel(rootPath, keywords));
|
|
724
|
+
}
|
|
725
|
+
/** Worker-dispatched variant of {@link fastScanAndHashFiles} for main-thread callers. */
|
|
726
|
+
export async function fastScanAndHashFilesAsync(rootPath) {
|
|
727
|
+
return dispatchViaWorker('scan-and-hash-files', { rootPath }, fastHashProjectPath(rootPath), () => fastScanAndHashFiles(rootPath));
|
|
728
|
+
}
|
|
729
|
+
/** Worker-dispatched variant of {@link fastScanProjectMetadata} for main-thread callers. */
|
|
730
|
+
export async function fastScanProjectMetadataAsync(rootPath) {
|
|
731
|
+
return dispatchViaWorker('scan-project-metadata', { rootPath }, fastHashProjectPath(rootPath), () => fastScanProjectMetadata(rootPath));
|
|
732
|
+
}
|
|
733
|
+
/** Worker-dispatched variant of {@link fastScanSpecs} for main-thread callers. */
|
|
734
|
+
export async function fastScanSpecsAsync(rootPath) {
|
|
735
|
+
return dispatchViaWorker('scan-specs', { rootPath }, fastHashProjectPath(rootPath), () => fastScanSpecs(rootPath));
|
|
736
|
+
}
|
|
681
737
|
export const isNativeActive = () => getNative() !== null;
|
|
682
738
|
export const nativeLoadDiagnostic = () => {
|
|
683
739
|
refreshNativeCapabilityDiagnostic();
|
|
684
740
|
return loadDiagnostic;
|
|
685
741
|
};
|
|
742
|
+
/**
|
|
743
|
+
* Force a native load attempt regardless of the calling thread and return the
|
|
744
|
+
* resulting diagnostic. `nativeLoadDiagnostic()` alone only reports load state
|
|
745
|
+
* that some other main-thread-bypassing caller already triggered (e.g. the
|
|
746
|
+
* macOS Keychain port); this is the main-thread-safe way to actually probe
|
|
747
|
+
* native availability, used by `planu doctor`.
|
|
748
|
+
*/
|
|
749
|
+
export const probeNativeLoad = () => {
|
|
750
|
+
loadNative();
|
|
751
|
+
return nativeLoadDiagnostic();
|
|
752
|
+
};
|
|
686
753
|
//# sourceMappingURL=core-bridge.js.map
|