@opengsd/gsd-core 1.6.1 → 1.7.0-rc.2
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/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +711 -0
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-code-fixer.md +2 -0
- package/agents/gsd-code-reviewer.md +2 -0
- package/agents/gsd-codebase-mapper.md +2 -0
- package/agents/gsd-debugger.md +2 -0
- package/agents/gsd-doc-writer.md +2 -0
- package/agents/gsd-eval-auditor.md +2 -0
- package/agents/gsd-executor.md +9 -6
- package/agents/gsd-integration-checker.md +2 -0
- package/agents/gsd-nyquist-auditor.md +2 -0
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-plan-checker.md +2 -0
- package/agents/gsd-planner.md +2 -0
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-roadmapper.md +2 -0
- package/agents/gsd-security-auditor.md +2 -0
- package/agents/gsd-ui-auditor.md +2 -0
- package/agents/gsd-ui-checker.md +2 -0
- package/agents/gsd-ui-researcher.md +2 -0
- package/agents/gsd-verifier.md +5 -2
- package/bin/gsd-mcp-server.js +31 -0
- package/bin/install.js +411 -1146
- package/commands/gsd/review.md +6 -0
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +134 -8
- package/gsd-core/bin/lib/adapter-declarative.cjs +35 -0
- package/gsd-core/bin/lib/adapter-imperative.cjs +52 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +231 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +7 -7
- package/gsd-core/bin/lib/capability-loader.cjs +45 -9
- package/gsd-core/bin/lib/capability-lock.cjs +2 -2
- package/gsd-core/bin/lib/capability-registry.cjs +891 -82
- package/gsd-core/bin/lib/capability-source.cjs +26 -11
- package/gsd-core/bin/lib/capability-validator.cjs +222 -2
- package/gsd-core/bin/lib/cli-skew-check.cjs +44 -0
- package/gsd-core/bin/lib/command-aliases.cjs +8 -0
- package/gsd-core/bin/lib/commands.cjs +2 -1
- package/gsd-core/bin/lib/config.cjs +27 -0
- package/gsd-core/bin/lib/embedding-adapter.cjs +27 -0
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +70 -0
- package/gsd-core/bin/lib/frontmatter.cjs +53 -6
- package/gsd-core/bin/lib/handshake-serialized.cjs +70 -0
- package/gsd-core/bin/lib/hook-bus.cjs +81 -0
- package/gsd-core/bin/lib/host-integration-sdk.cjs +53 -0
- package/gsd-core/bin/lib/host-integration.cjs +469 -0
- package/gsd-core/bin/lib/init.cjs +35 -7
- package/gsd-core/bin/lib/install-engine.cjs +755 -0
- package/gsd-core/bin/lib/install-profiles.cjs +35 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +1 -1
- package/gsd-core/bin/lib/mcp-server.cjs +194 -0
- package/gsd-core/bin/lib/milestone.cjs +68 -40
- package/gsd-core/bin/lib/model-adapter.cjs +50 -0
- package/gsd-core/bin/lib/phase-id.cjs +18 -0
- package/gsd-core/bin/lib/phase.cjs +57 -90
- package/gsd-core/bin/lib/phases-command-router.cjs +4 -3
- package/gsd-core/bin/lib/planning-workspace.cjs +1 -1
- package/gsd-core/bin/lib/probe-core.cjs +132 -2
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +129 -13
- package/gsd-core/bin/lib/roadmap-command-router.cjs +3 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -11
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +3 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -22
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +65 -9
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +54 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +5 -2
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/gsd-core/bin/lib/runtime-name-policy.cjs +160 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +16 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +254 -0
- package/gsd-core/bin/lib/state-command-router.cjs +4 -0
- package/gsd-core/bin/lib/state-io.cjs +55 -0
- package/gsd-core/bin/lib/state-transition.cjs +1603 -0
- package/gsd-core/bin/lib/state.cjs +327 -683
- package/gsd-core/bin/lib/surface.cjs +4 -1
- package/gsd-core/bin/lib/validate.cjs +2 -1
- package/gsd-core/bin/lib/verify.cjs +6 -4
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +12 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +28 -0
- package/gsd-core/bin/lib/workstream.cjs +4 -4
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -0
- package/gsd-core/references/agent-skills-bootstrap.md +60 -0
- package/gsd-core/references/honest-verifier.md +105 -0
- package/gsd-core/references/model-profiles.md +27 -0
- package/gsd-core/references/reviewer-instances.md +99 -0
- package/gsd-core/workflows/autonomous.md +30 -32
- package/gsd-core/workflows/complete-milestone.md +6 -10
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/forensics.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/manager.md +15 -15
- package/gsd-core/workflows/milestone-summary.md +3 -3
- package/gsd-core/workflows/new-milestone.md +6 -0
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +42 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +102 -0
- package/gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md +23 -0
- package/gsd-core/workflows/plan-phase.md +4 -159
- package/gsd-core/workflows/review.md +33 -2
- package/gsd-core/workflows/thread.md +4 -4
- package/gsd-core/workflows/verify-phase.md +11 -4
- package/gsd-core/workflows/verify-work.md +1 -2
- package/hooks/dist/gsd-graphify-update.sh +7 -1
- package/hooks/gsd-graphify-update.sh +7 -1
- package/package.json +6 -4
- package/scripts/ci-test-scope.cjs +38 -9
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -1
- package/scripts/lint-regression-test-names.allowlist.json +3 -0
- package/scripts/lint-test-file-count.allowlist.json +19 -5
- package/scripts/mutation-matrix.cjs +45 -3
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/run-tests.cjs +51 -1
- package/scripts/sync-manifest-versions.cjs +66 -14
- package/skills/gsd-review/SKILL.md +6 -0
- package/scripts/lint-windows-test-portability.cjs +0 -178
package/commands/gsd/review.md
CHANGED
|
@@ -35,6 +35,12 @@ Phase number: extracted from $ARGUMENTS (required)
|
|
|
35
35
|
- `--cursor` — Include Cursor agent review
|
|
36
36
|
- `--agy` / `--antigravity` — Include Antigravity CLI review
|
|
37
37
|
- `--all` — Include all available CLIs
|
|
38
|
+
|
|
39
|
+
**No flags** — if `review.default_reviewers` is set, review with only those configured
|
|
40
|
+
reviewers that are detected; otherwise review with all available CLIs. Configured
|
|
41
|
+
`review.reviewer_instances` names may appear in `review.default_reviewers`; each runs as an
|
|
42
|
+
independent reviewer identity backed by its configured adapter+model (see
|
|
43
|
+
`docs/CONFIGURATION.md`). Instance names are not valid as flags.
|
|
38
44
|
</context>
|
|
39
45
|
|
|
40
46
|
<process>
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0-rc.2",
|
|
4
4
|
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
* Milestone Operations:
|
|
58
58
|
* milestone complete <version> Archive milestone, create MILESTONES.md
|
|
59
59
|
* [--name <name>]
|
|
60
|
-
* [--archive-phases]
|
|
60
|
+
* [--no-archive-phases] Skip moving phase dirs to milestones/vX.Y-phases/ (archived by default)
|
|
61
61
|
*
|
|
62
62
|
* User Story Validation:
|
|
63
63
|
* user-story validate --story "..." Validate "As a / I want to / so that" format
|
|
@@ -204,11 +204,31 @@ const projectRoot = require('./lib/project-root.cjs');
|
|
|
204
204
|
// against any require/load-ordering edge where the export isn't bound yet
|
|
205
205
|
// when this entrypoint is first required (#604).
|
|
206
206
|
const findProjectRoot = (...args) => projectRoot.findProjectRoot(...args);
|
|
207
|
+
|
|
208
|
+
// #1754: CLI skew detection — warn (stderr, non-blocking) if this gsd-tools.cjs
|
|
209
|
+
// is NOT the project-local install while a project-local install exists. Catches
|
|
210
|
+
// the shadowing scenario from #1748 (stale global canary shadowing project-local).
|
|
211
|
+
try {
|
|
212
|
+
const _skew = require('./lib/cli-skew-check.cjs');
|
|
213
|
+
const _skewRoot = findProjectRoot(process.cwd());
|
|
214
|
+
if (_skewRoot) {
|
|
215
|
+
const _skewLocal = path.join(_skewRoot, '.claude', 'gsd-core', 'bin', 'gsd-tools.cjs');
|
|
216
|
+
const _skewWarn = _skew.checkCliSkew({
|
|
217
|
+
resolvedPath: path.resolve(__filename),
|
|
218
|
+
projectRoot: _skewRoot,
|
|
219
|
+
projectLocalExists: fs.existsSync(_skewLocal),
|
|
220
|
+
});
|
|
221
|
+
if (_skewWarn) process.stderr.write(_skewWarn + '\n');
|
|
222
|
+
}
|
|
223
|
+
} catch { /* advisory — never block */ }
|
|
224
|
+
|
|
207
225
|
const { getActiveWorkstream } = require('./lib/planning-workspace.cjs');
|
|
208
226
|
const { resolveActiveWorkstream, applyResolvedWorkstreamEnv } = require('./lib/active-workstream-store.cjs');
|
|
209
227
|
const state = require('./lib/state.cjs');
|
|
210
228
|
const phase = require('./lib/phase.cjs');
|
|
211
229
|
const roadmap = require('./lib/roadmap.cjs');
|
|
230
|
+
// #1561 — assumption-delta advisory checkpoint detector (pure function).
|
|
231
|
+
const { detectAssumptionDelta } = require('./lib/assumption-delta.cjs');
|
|
212
232
|
const verify = require('./lib/verify.cjs');
|
|
213
233
|
const config = require('./lib/config.cjs');
|
|
214
234
|
const template = require('./lib/template.cjs');
|
|
@@ -227,6 +247,10 @@ const evalMod = require('./lib/eval.cjs');
|
|
|
227
247
|
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
|
228
248
|
const verification = require('./lib/verification.cjs');
|
|
229
249
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
250
|
+
// Stale-bake guard (#1688): warns once when model config changed since agents
|
|
251
|
+
// were last baked on static-frontmatter runtimes (codex/opencode). Lazy-required
|
|
252
|
+
// here, invoked from case 'init' below.
|
|
253
|
+
const { warnIfStaleBake } = require('./lib/stale-bake-guard.cjs');
|
|
230
254
|
const loopResolver = require('./lib/loop-resolver.cjs');
|
|
231
255
|
const capabilityState = require('./lib/capability-state.cjs');
|
|
232
256
|
const capabilityWriter = require('./lib/capability-writer.cjs');
|
|
@@ -635,7 +659,7 @@ async function main() {
|
|
|
635
659
|
// discovery; previously it was a partial subset that didn't include
|
|
636
660
|
// phase / roadmap / milestone / progress / etc.
|
|
637
661
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
638
|
-
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
662
|
+
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
639
663
|
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
|
|
640
664
|
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
641
665
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
@@ -1234,6 +1258,56 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1234
1258
|
break;
|
|
1235
1259
|
}
|
|
1236
1260
|
|
|
1261
|
+
case 'dispatch-should-flatten': {
|
|
1262
|
+
// #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
|
|
1263
|
+
//
|
|
1264
|
+
// Resolves the current runtime (GSD_RUNTIME > config.runtime > 'claude'),
|
|
1265
|
+
// looks up registry.runtimes[id].runtime.hostIntegration.dispatch, and
|
|
1266
|
+
// calls shouldFlattenDispatch(dispatch) from host-integration.cjs.
|
|
1267
|
+
//
|
|
1268
|
+
// Fail-closed: any unknown runtime, missing dispatch, or thrown error
|
|
1269
|
+
// yields `true` (inline — the always-safe default).
|
|
1270
|
+
//
|
|
1271
|
+
// Output:
|
|
1272
|
+
// --raw → prints exactly `true` or `false`
|
|
1273
|
+
// --json → prints { runtime, shouldFlatten, dispatch }
|
|
1274
|
+
// default → same as --raw
|
|
1275
|
+
try {
|
|
1276
|
+
// Resolve runtime using the same precedence as `config-get runtime`.
|
|
1277
|
+
const { resolveRuntime } = require('./lib/runtime-slash.cjs');
|
|
1278
|
+
const runtimeId = resolveRuntime(cwd);
|
|
1279
|
+
|
|
1280
|
+
// Look up dispatch from the capability registry.
|
|
1281
|
+
const registry = require('./lib/capability-registry.cjs');
|
|
1282
|
+
const runtimeEntry = registry.runtimes != null
|
|
1283
|
+
? registry.runtimes[runtimeId]
|
|
1284
|
+
: null;
|
|
1285
|
+
const dispatch = runtimeEntry?.runtime?.hostIntegration?.dispatch ?? null;
|
|
1286
|
+
|
|
1287
|
+
// Call shouldFlattenDispatch from host-integration.cjs.
|
|
1288
|
+
const hostIntegration = require('./lib/host-integration.cjs');
|
|
1289
|
+
const shouldFlat = dispatch !== null
|
|
1290
|
+
? hostIntegration.shouldFlattenDispatch(dispatch)
|
|
1291
|
+
: true; // fail-closed: unknown runtime → inline
|
|
1292
|
+
|
|
1293
|
+
const jsonIdx = args.indexOf('--json');
|
|
1294
|
+
if (jsonIdx !== -1) {
|
|
1295
|
+
output({
|
|
1296
|
+
runtime: runtimeId,
|
|
1297
|
+
shouldFlatten: shouldFlat,
|
|
1298
|
+
dispatch: dispatch,
|
|
1299
|
+
}, raw);
|
|
1300
|
+
} else {
|
|
1301
|
+
// --raw or default: print exactly true or false
|
|
1302
|
+
process.stdout.write(shouldFlat ? 'true' : 'false');
|
|
1303
|
+
}
|
|
1304
|
+
} catch {
|
|
1305
|
+
// Fail-closed on any error: inline is always safe.
|
|
1306
|
+
process.stdout.write('true');
|
|
1307
|
+
}
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1237
1311
|
case 'config-new-project': {
|
|
1238
1312
|
// Phase 6 (#3575): dispatch via SDK executeForCjs when available.
|
|
1239
1313
|
const handled = _dispatchNonFamily({
|
|
@@ -1310,6 +1384,43 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1310
1384
|
break;
|
|
1311
1385
|
}
|
|
1312
1386
|
|
|
1387
|
+
case 'assumption-delta': {
|
|
1388
|
+
// #1561 — advisory architecture checkpoint. `scan <phase>` reads the
|
|
1389
|
+
// phase section via the same resolver as roadmap.get-phase and runs the
|
|
1390
|
+
// deterministic detectAssumptionDelta, emitting the typed IR as JSON.
|
|
1391
|
+
const sub = args[1];
|
|
1392
|
+
if (sub === 'scan') {
|
|
1393
|
+
const phaseNum = args[2];
|
|
1394
|
+
// Reject missing or flag-shaped phase values (QA matrix: values that
|
|
1395
|
+
// look like flags). `scan --json` must not treat "--json" as a phase.
|
|
1396
|
+
if (!phaseNum || phaseNum.startsWith('-')) {
|
|
1397
|
+
error('Usage: assumption-delta scan <phase> [--terms <csv>]', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1398
|
+
break;
|
|
1399
|
+
}
|
|
1400
|
+
// Optional --terms <csv> override (replaces the pluralization cues;
|
|
1401
|
+
// optional/chosen keep defaults). An EMPTY value ("") or a flag-shaped
|
|
1402
|
+
// value restores the curated defaults (does NOT disable pluralization).
|
|
1403
|
+
// Terms are normalized (deduped, alphanumeric-only, capped) by
|
|
1404
|
+
// detectAssumptionDelta's resolveTerms.
|
|
1405
|
+
let termsOverride;
|
|
1406
|
+
const termsIdx = args.indexOf('--terms');
|
|
1407
|
+
const termsVal = termsIdx !== -1 ? args[termsIdx + 1] : undefined;
|
|
1408
|
+
if (typeof termsVal === 'string' && !termsVal.startsWith('-')) {
|
|
1409
|
+
const list = termsVal
|
|
1410
|
+
.split(',')
|
|
1411
|
+
.map((t) => t.trim().toLowerCase())
|
|
1412
|
+
.filter((t) => t.length > 0);
|
|
1413
|
+
termsOverride = list.length > 0 ? { pluralization: list } : undefined;
|
|
1414
|
+
}
|
|
1415
|
+
const section = roadmap.getRoadmapPhaseWithFallback(cwd, phaseNum);
|
|
1416
|
+
const result = detectAssumptionDelta(section ?? '', termsOverride);
|
|
1417
|
+
output(result, raw);
|
|
1418
|
+
break;
|
|
1419
|
+
}
|
|
1420
|
+
error(`Unknown assumption-delta subcommand: ${sub}. Available: scan`, ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1313
1424
|
case 'requirements': {
|
|
1314
1425
|
const subcommand = args[1];
|
|
1315
1426
|
if (subcommand === 'mark-complete') {
|
|
@@ -1342,7 +1453,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1342
1453
|
const subcommand = args[1];
|
|
1343
1454
|
if (subcommand === 'complete') {
|
|
1344
1455
|
const milestoneName = parseMultiwordArg(args, 'name');
|
|
1345
|
-
|
|
1456
|
+
// #1871: archive phase dirs by default on milestone complete so the next
|
|
1457
|
+
// new-milestone never inherits un-archived dirs. --no-archive-phases opts out.
|
|
1458
|
+
const archivePhases = !args.includes('--no-archive-phases');
|
|
1346
1459
|
const force = args.includes('--force');
|
|
1347
1460
|
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force }, raw);
|
|
1348
1461
|
} else {
|
|
@@ -1414,6 +1527,10 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1414
1527
|
}
|
|
1415
1528
|
|
|
1416
1529
|
case 'init': {
|
|
1530
|
+
// #1688: warn (at most once per process) if the user edited model_overrides
|
|
1531
|
+
// without re-running `gsd install <runtime>` on a static-frontmatter runtime.
|
|
1532
|
+
// Best-effort, stderr-only, swallowed errors — never blocks the command.
|
|
1533
|
+
try { warnIfStaleBake(cwd); } catch { /* guard must never break init */ }
|
|
1417
1534
|
routeInitCommand({
|
|
1418
1535
|
init,
|
|
1419
1536
|
args,
|
|
@@ -1550,13 +1667,22 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1550
1667
|
return undefined;
|
|
1551
1668
|
};
|
|
1552
1669
|
// Running GSD version (hard gate for engines.gsd at install/load); fail-closed to 0.0.0.
|
|
1670
|
+
// #1920: prefer the authoritative gsd-core/VERSION the installer writes for EVERY runtime
|
|
1671
|
+
// (gsd-core/bin/ -> ../VERSION), so installed layouts report the true version even when the
|
|
1672
|
+
// walked-up ../../package.json is the versionless CommonJS marker or the user's own project.
|
|
1673
|
+
// Fall back to the runtime-root package.json (dev/source tree), then fail-closed. Mirrors
|
|
1674
|
+
// readHostVersion() in capability-loader.cts.
|
|
1553
1675
|
const capHostVersion = () => {
|
|
1676
|
+
const SEMVER_PREFIX = /^\d+\.\d+\.\d+/;
|
|
1554
1677
|
try {
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
} catch {
|
|
1558
|
-
|
|
1559
|
-
|
|
1678
|
+
const v = fs.readFileSync(path.join(__dirname, '..', 'VERSION'), 'utf8').trim();
|
|
1679
|
+
if (SEMVER_PREFIX.test(v)) return v;
|
|
1680
|
+
} catch { /* not an installed tree (no gsd-core/VERSION) */ }
|
|
1681
|
+
try {
|
|
1682
|
+
const pkg = require(path.join(__dirname, '..', '..', 'package.json')); // gsd-core/bin/ -> repo root is two up
|
|
1683
|
+
if (pkg && typeof pkg.version === 'string' && SEMVER_PREFIX.test(pkg.version)) return pkg.version;
|
|
1684
|
+
} catch { /* runtime root has no package.json */ }
|
|
1685
|
+
return '0.0.0';
|
|
1560
1686
|
};
|
|
1561
1687
|
// #1459: the USER-OWNED consent home (GSD_HOME||homedir()) where project-scope consent records
|
|
1562
1688
|
// live — OUTSIDE any repo. SAME rule as the loader/consent-store path resolution so a record
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative embedding adapter (ADR-1239 Phase C-1, AC1 / #1680).
|
|
3
|
+
*
|
|
4
|
+
* NAMES + BOUNDS today's projection path (file emission via install-engine)
|
|
5
|
+
* behind `HostIntegrationInterface`. The declarative adapter is lossy by
|
|
6
|
+
* design: it projects skills/agents/commands and does NOT drive the loop
|
|
7
|
+
* orchestration (that is the imperative adapter's job, AC2 / a later slice).
|
|
8
|
+
*
|
|
9
|
+
* `install`/`uninstall` delegate IN-PROCESS to install-engine's
|
|
10
|
+
* `installRuntimeArtifacts` / `uninstallRuntimeArtifacts` — the SAME engine
|
|
11
|
+
* functions `bin/install.js` uses — so the adapter's output is byte-identical to
|
|
12
|
+
* today's install (the link is gated by `tests/golden-install-parity.test.cjs`).
|
|
13
|
+
* The module-ref call style (`installEngine.fn`) keeps it monkeypatch-friendly
|
|
14
|
+
* for tests, mirroring the install-engine.cts:31-38 pattern.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.createDeclarativeAdapter = createDeclarativeAdapter;
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
const installEngine = require("./install-engine.cjs");
|
|
21
|
+
function createDeclarativeAdapter({ runtime }) {
|
|
22
|
+
if (!runtime || typeof runtime !== 'string') {
|
|
23
|
+
throw new TypeError('createDeclarativeAdapter: runtime is required (non-empty string)');
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
kind: 'declarative',
|
|
27
|
+
runtime,
|
|
28
|
+
install(intent) {
|
|
29
|
+
installEngine.installRuntimeArtifacts(runtime, intent.configDir, intent.scope, intent.resolvedProfile, intent.resolveAttribution);
|
|
30
|
+
},
|
|
31
|
+
uninstall(intent) {
|
|
32
|
+
installEngine.uninstallRuntimeArtifacts(runtime, intent.configDir, intent.scope);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imperative embedding adapter (ADR-1239 Phase C-1, AC2 / #1680).
|
|
3
|
+
*
|
|
4
|
+
* The engine-as-library path: an in-process host plugin calls
|
|
5
|
+
* `createImperativeAdapter({runtime})`, which composes the capability registry
|
|
6
|
+
* via `loadRegistry({includeInstalled:true})` (first-party-wins + consent +
|
|
7
|
+
* fail-closed gates) and binds the engine surface behind the SAME
|
|
8
|
+
* `HostIntegrationInterface` the declarative adapter satisfies. The adapter
|
|
9
|
+
* stays thin — it does NOT reimplement the loop resolver; it delegates to the
|
|
10
|
+
* engine + exposes the composed registry so a host (Phase 5) can bind its
|
|
11
|
+
* primitives (command/dispatch/model/hooks/state/artifact) to the registry's
|
|
12
|
+
* declared capability set.
|
|
13
|
+
*
|
|
14
|
+
* Concrete host binding (OpenCode/VS Code/pi) is deferred to Phase 5 (#1682,
|
|
15
|
+
* D15/D18). This slice ships the adapter + the composed-registry seam.
|
|
16
|
+
*
|
|
17
|
+
* Minimal interface (per ADR-1239 open wire-shape question): satisfies the
|
|
18
|
+
* same `{kind, runtime, install, uninstall}` shape as the declarative adapter,
|
|
19
|
+
* plus an imperative-specific `registry` accessor (the composed loadRegistry
|
|
20
|
+
* result). The full 6-point binding surface grows when a real host consumer
|
|
21
|
+
* fixes the shape.
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.createImperativeAdapter = createImperativeAdapter;
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const installEngine = require("./install-engine.cjs");
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
|
+
const capabilityLoader = require("./capability-loader.cjs");
|
|
30
|
+
function createImperativeAdapter({ runtime }, options = {}) {
|
|
31
|
+
if (!runtime || typeof runtime !== 'string') {
|
|
32
|
+
throw new TypeError('createImperativeAdapter: runtime is required (non-empty string)');
|
|
33
|
+
}
|
|
34
|
+
// Compose first-party ∪ installed capability overlays with the SAME
|
|
35
|
+
// precedence, consent, and fail-closed-gate guarantees the CLI enforces —
|
|
36
|
+
// an in-process host gets identical trust semantics, not a parallel path.
|
|
37
|
+
const registry = capabilityLoader.loadRegistry({
|
|
38
|
+
includeInstalled: true,
|
|
39
|
+
...(options.loadOptions ?? {}),
|
|
40
|
+
});
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
kind: 'imperative',
|
|
43
|
+
runtime,
|
|
44
|
+
registry,
|
|
45
|
+
install(intent) {
|
|
46
|
+
installEngine.installRuntimeArtifacts(runtime, intent.configDir, intent.scope, intent.resolvedProfile, intent.resolveAttribution);
|
|
47
|
+
},
|
|
48
|
+
uninstall(intent) {
|
|
49
|
+
installEngine.uninstallRuntimeArtifacts(runtime, intent.configDir, intent.scope);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Assumption-Delta detector (#1561).
|
|
4
|
+
*
|
|
5
|
+
* A rarely-firing, advisory architecture checkpoint. When a phase makes
|
|
6
|
+
* something PLURAL / OPTIONAL / CHOSEN that used to be SINGULAR / REQUIRED /
|
|
7
|
+
* DERIVED, the primary key / identity model may silently stop matching the
|
|
8
|
+
* generalized intent. This detector scans phase-scope prose for the linguistic
|
|
9
|
+
* signals of that transition so the plan:pre capability hook (see
|
|
10
|
+
* capabilities/assumption-delta/) can surface ONE identity-model question.
|
|
11
|
+
*
|
|
12
|
+
* Design notes (rubber-duck'd):
|
|
13
|
+
* - DETERMINISTIC + TYPED IR. The "does it fire?" decision is a pure function
|
|
14
|
+
* returning { detected, signals, terms }, not an LLM judgment — so the
|
|
15
|
+
* low-false-positive guarantee (acceptance criterion #2) is testable.
|
|
16
|
+
* - BARE "or" IS INTENTIONALLY EXCLUDED from the default pluralization cues.
|
|
17
|
+
* The issue lists "or" as a tell, but bare "or" is extremely common in
|
|
18
|
+
* English prose and would make the gate fire on nearly every phase
|
|
19
|
+
* description. Pluralization requires a stronger second-case cue
|
|
20
|
+
* (second / alternative / fallback / additional / ...). The vocabulary is
|
|
21
|
+
* tunable (config + the `terms` parameter) so teams can widen it.
|
|
22
|
+
* - FENCED CODE BLOCKS ARE STRIPPED first (via the markdown-sectionizer seam)
|
|
23
|
+
* so a trigger term that appears only inside a code snippet does not fire.
|
|
24
|
+
* - Mirrors ui-safety-gate.cts: a pure function + a STDIN-reading CLI whose
|
|
25
|
+
* exit codes mirror grep (0 = signal found, 1 = none, 2 = usage error).
|
|
26
|
+
*
|
|
27
|
+
* Public API:
|
|
28
|
+
* detectAssumptionDelta(text, terms?) -> { detected, signals, terms }
|
|
29
|
+
* DEFAULT_ASSUMPTION_DELTA_TERMS
|
|
30
|
+
*
|
|
31
|
+
* CLI:
|
|
32
|
+
* echo "$PHASE_SECTION" | node gsd-core/bin/lib/assumption-delta.cjs [--json]
|
|
33
|
+
* exit 0 = signal detected, 1 = none, 2 = startup error
|
|
34
|
+
* --json additionally prints the typed IR on stdout
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.DEFAULT_ASSUMPTION_DELTA_TERMS = void 0;
|
|
38
|
+
exports.detectAssumptionDelta = detectAssumptionDelta;
|
|
39
|
+
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
40
|
+
/**
|
|
41
|
+
* Curated default trigger vocabulary. Each kind lists cue terms that signal a
|
|
42
|
+
* core-assumption monopoly has been lost. ADDITIVE-ONLY (Hyrum's Law: once
|
|
43
|
+
* shipped, this set is a depended-upon interface). Tunable via the `terms`
|
|
44
|
+
* parameter or the capability's config slice.
|
|
45
|
+
*/
|
|
46
|
+
exports.DEFAULT_ASSUMPTION_DELTA_TERMS = {
|
|
47
|
+
// Primary trigger — a second X where there was one.
|
|
48
|
+
// Bare "or" excluded (prose-frequency false positives).
|
|
49
|
+
pluralization: [
|
|
50
|
+
'second',
|
|
51
|
+
'alternative',
|
|
52
|
+
'alternate',
|
|
53
|
+
'fallback',
|
|
54
|
+
'also',
|
|
55
|
+
'additional',
|
|
56
|
+
'another',
|
|
57
|
+
'supplementary',
|
|
58
|
+
'alongside',
|
|
59
|
+
'multiple',
|
|
60
|
+
'plural',
|
|
61
|
+
'2nd',
|
|
62
|
+
],
|
|
63
|
+
// required / `only` -> optional
|
|
64
|
+
optional: ['optional', 'optionally'],
|
|
65
|
+
// derived -> chosen / constant -> parameter
|
|
66
|
+
chosen: [
|
|
67
|
+
'chosen',
|
|
68
|
+
'choose',
|
|
69
|
+
'selectable',
|
|
70
|
+
'configurable',
|
|
71
|
+
'parameterized',
|
|
72
|
+
'parameterised',
|
|
73
|
+
'parameterize',
|
|
74
|
+
'parameterise',
|
|
75
|
+
'custom',
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
/** Hardening caps for the tunable term vocabulary (Codex review finding). */
|
|
79
|
+
const MAX_TERMS_PER_KIND = 200;
|
|
80
|
+
const MAX_TERM_LEN = 32;
|
|
81
|
+
/**
|
|
82
|
+
* Normalize a caller-provided term list: trim, lowercase, reject empties and
|
|
83
|
+
* punctuation-only terms (e.g. "-"), dedupe (preserve order), and cap the
|
|
84
|
+
* count/length so a huge or hostile `--terms` value cannot build a giant
|
|
85
|
+
* alternation regex or echo a massive payload. Defaults are already clean, so
|
|
86
|
+
* this is a no-op on them.
|
|
87
|
+
*/
|
|
88
|
+
function normalizeTerms(list) {
|
|
89
|
+
if (!Array.isArray(list))
|
|
90
|
+
return [];
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const raw of list) {
|
|
94
|
+
if (typeof raw !== 'string')
|
|
95
|
+
continue;
|
|
96
|
+
const t = raw.trim().toLowerCase().slice(0, MAX_TERM_LEN);
|
|
97
|
+
// Require at least one alphanumeric char so punctuation-only terms like
|
|
98
|
+
// "-" cannot match prose punctuation as a "signal".
|
|
99
|
+
if (!t || !/[a-z0-9]/.test(t))
|
|
100
|
+
continue;
|
|
101
|
+
if (seen.has(t))
|
|
102
|
+
continue;
|
|
103
|
+
seen.add(t);
|
|
104
|
+
out.push(t);
|
|
105
|
+
if (out.length >= MAX_TERMS_PER_KIND)
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve the effective term set: per-kind override. An explicitly-provided
|
|
112
|
+
* non-empty array for a kind REPLACES that kind's defaults (then normalized);
|
|
113
|
+
* an absent kind KEEPS its defaults. An explicitly-empty array disables that
|
|
114
|
+
* kind (override present, normalized to []). This lets a caller narrow one axis
|
|
115
|
+
* without re-declaring the others.
|
|
116
|
+
*/
|
|
117
|
+
function resolveTerms(terms) {
|
|
118
|
+
const merge = (key) => {
|
|
119
|
+
const t = terms && terms[key];
|
|
120
|
+
return Array.isArray(t) ? normalizeTerms(t) : [...exports.DEFAULT_ASSUMPTION_DELTA_TERMS[key]];
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
pluralization: merge('pluralization'),
|
|
124
|
+
optional: merge('optional'),
|
|
125
|
+
chosen: merge('chosen'),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** Trim + collapse + truncate a context window around a match for the snippet. */
|
|
129
|
+
function makeSnippet(line, term) {
|
|
130
|
+
const cleaned = line.replace(/\s+/g, ' ').trim();
|
|
131
|
+
if (cleaned.length <= 120)
|
|
132
|
+
return cleaned;
|
|
133
|
+
// Centre the window on the matched term when the line is long.
|
|
134
|
+
const idx = cleaned.toLowerCase().indexOf(term);
|
|
135
|
+
if (idx < 0)
|
|
136
|
+
return cleaned.slice(0, 120);
|
|
137
|
+
const start = Math.max(0, idx - 50);
|
|
138
|
+
const end = Math.min(cleaned.length, idx + term.length + 50);
|
|
139
|
+
const prefix = start > 0 ? '…' : '';
|
|
140
|
+
const suffix = end < cleaned.length ? '…' : '';
|
|
141
|
+
return `${prefix}${cleaned.slice(start, end)}${suffix}`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Detect assumption-delta signals in phase-scope prose.
|
|
145
|
+
*
|
|
146
|
+
* @param text - Roadmap phase section / scope prose. Non-string inputs degrade
|
|
147
|
+
* to `{ detected: false }` without throwing.
|
|
148
|
+
* @param terms - Optional per-kind override (see resolveTerms).
|
|
149
|
+
* @returns typed IR: { detected, signals[], terms }. `terms` is the effective
|
|
150
|
+
* (merged) set actually used, so callers/tests can audit what fired.
|
|
151
|
+
*/
|
|
152
|
+
function detectAssumptionDelta(text, terms) {
|
|
153
|
+
if (typeof text !== 'string') {
|
|
154
|
+
return { detected: false, signals: [], terms: resolveTerms(terms) };
|
|
155
|
+
}
|
|
156
|
+
const effective = resolveTerms(terms);
|
|
157
|
+
// Strip fenced code blocks so trigger terms inside code snippets do not fire.
|
|
158
|
+
// stripFencedCode is CommonMark-correct and CRLF-safe.
|
|
159
|
+
const stripped = (0, markdown_sectionizer_cjs_1.stripFencedCode)(text.replace(/\r\n/g, '\n')).text;
|
|
160
|
+
if (stripped.trim().length === 0) {
|
|
161
|
+
return { detected: false, signals: [], terms: effective };
|
|
162
|
+
}
|
|
163
|
+
const signals = [];
|
|
164
|
+
const kinds = ['pluralization', 'optional', 'chosen'];
|
|
165
|
+
for (const kind of kinds) {
|
|
166
|
+
const cueTerms = effective[kind];
|
|
167
|
+
if (cueTerms.length === 0)
|
|
168
|
+
continue;
|
|
169
|
+
// Word-boundary anchored, case-insensitive — same shape as ui-safety-gate.
|
|
170
|
+
// (^|[^a-zA-Z0-9])(TERM)([^a-zA-Z0-9]|$) prevents interior-substring matches.
|
|
171
|
+
const escaped = cueTerms.map(escapeRegex).join('|');
|
|
172
|
+
const pattern = new RegExp('(^|[^a-zA-Z0-9])(' + escaped + ')([^a-zA-Z0-9]|$)', 'gi');
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
for (const line of stripped.split('\n')) {
|
|
175
|
+
pattern.lastIndex = 0;
|
|
176
|
+
for (const m of line.matchAll(pattern)) {
|
|
177
|
+
const raw = m[2];
|
|
178
|
+
if (!raw)
|
|
179
|
+
continue;
|
|
180
|
+
const matched = raw.toLowerCase();
|
|
181
|
+
const key = `${kind}:${matched}`;
|
|
182
|
+
if (seen.has(key))
|
|
183
|
+
continue;
|
|
184
|
+
seen.add(key);
|
|
185
|
+
signals.push({ kind, term: matched, snippet: makeSnippet(line, matched) });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { detected: signals.length > 0, signals, terms: effective };
|
|
190
|
+
}
|
|
191
|
+
function escapeRegex(s) {
|
|
192
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
193
|
+
}
|
|
194
|
+
// ── CLI entry point ──────────────────────────────────────────────────────────
|
|
195
|
+
// Reads phase-section text from STDIN (not argv) to avoid OS ARG_MAX limits.
|
|
196
|
+
// Invoked by workflow bash as: echo "$PHASE_SECTION" | node .../assumption-delta.cjs [--json]
|
|
197
|
+
// Exit 0 = signal detected, 1 = none, 2 = startup error. Mirrors ui-safety-gate.
|
|
198
|
+
if (require.main === module) {
|
|
199
|
+
const argv = process.argv.slice(2);
|
|
200
|
+
const wantJson = argv.includes('--json');
|
|
201
|
+
// --terms <csv>: config-tunable vocabulary override. Replaces the
|
|
202
|
+
// pluralization cues (the primary trigger); optional/chosen keep defaults.
|
|
203
|
+
// An EMPTY value ("") or a flag-shaped value restores the curated defaults
|
|
204
|
+
// (does NOT disable pluralization). Terms are normalized (deduped, etc.) by
|
|
205
|
+
// detectAssumptionDelta's resolveTerms.
|
|
206
|
+
let termsOverride;
|
|
207
|
+
const termsIdx = argv.indexOf('--terms');
|
|
208
|
+
const termsVal = termsIdx !== -1 ? argv[termsIdx + 1] : undefined;
|
|
209
|
+
if (typeof termsVal === 'string' && !termsVal.startsWith('-')) {
|
|
210
|
+
const list = termsVal
|
|
211
|
+
.split(',')
|
|
212
|
+
.map((t) => t.trim().toLowerCase())
|
|
213
|
+
.filter((t) => t.length > 0);
|
|
214
|
+
termsOverride = list.length > 0 ? { pluralization: list } : undefined;
|
|
215
|
+
}
|
|
216
|
+
const chunks = [];
|
|
217
|
+
process.stdin.setEncoding('utf-8');
|
|
218
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
219
|
+
process.stdin.on('end', () => {
|
|
220
|
+
const input = chunks.join('');
|
|
221
|
+
const result = detectAssumptionDelta(input, termsOverride);
|
|
222
|
+
if (wantJson) {
|
|
223
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
224
|
+
}
|
|
225
|
+
process.exit(result.detected ? 0 : 1);
|
|
226
|
+
});
|
|
227
|
+
process.stdin.on('error', (err) => {
|
|
228
|
+
process.stderr.write(`ERROR: assumption-delta.cjs stdin read failed: ${err.message}\n`);
|
|
229
|
+
process.exit(2);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
@@ -33,7 +33,7 @@ const consentMod = require('./capability-consent.cjs');
|
|
|
33
33
|
const projectRootMod = require('./project-root.cjs');
|
|
34
34
|
// #1459 finding 4: the SHARED hardened lock primitive (single source of truth for lifecycle + consent).
|
|
35
35
|
const lockMod = require('./capability-lock.cjs');
|
|
36
|
-
const { platformWriteSync } = require('./shell-command-projection.cjs');
|
|
36
|
+
const { platformWriteSync, retryRenameSync } = require('./shell-command-projection.cjs');
|
|
37
37
|
// #1463: numeric major.minor.patch comparison for the outdated check (the SAME compare the resolver
|
|
38
38
|
// and capability list use). -1 (a<b), 0 (equal), 1 (a>b).
|
|
39
39
|
const semverMod = require('./semver-compare.cjs');
|
|
@@ -369,16 +369,16 @@ function promoteStagingToFinal(stagingDir, finalDir, backupName) {
|
|
|
369
369
|
? node_path_1.default.join(parent, backupName)
|
|
370
370
|
// CONC-3: a random nonce in the unnamed-branch backup name prevents same-ms cross-process collision.
|
|
371
371
|
: node_path_1.default.join(parent, newBackupName(node_path_1.default.basename(finalDir)));
|
|
372
|
-
|
|
372
|
+
retryRenameSync(finalDir, backupDir);
|
|
373
373
|
// DUR-3: fsync the parent dir so the old→backup rename is durable BEFORE the second rename —
|
|
374
374
|
// a crash here must not lose the backup (the only recovery path for reconcile).
|
|
375
375
|
fsyncDir(parent);
|
|
376
376
|
try {
|
|
377
|
-
|
|
377
|
+
retryRenameSync(stagingDir, finalDir);
|
|
378
378
|
}
|
|
379
379
|
catch (err) {
|
|
380
380
|
try {
|
|
381
|
-
|
|
381
|
+
retryRenameSync(backupDir, finalDir);
|
|
382
382
|
}
|
|
383
383
|
catch { /* best-effort restore */ }
|
|
384
384
|
throw err;
|
|
@@ -388,7 +388,7 @@ function promoteStagingToFinal(stagingDir, finalDir, backupName) {
|
|
|
388
388
|
return { backupDir };
|
|
389
389
|
}
|
|
390
390
|
node_fs_1.default.mkdirSync(parent, { recursive: true });
|
|
391
|
-
|
|
391
|
+
retryRenameSync(stagingDir, finalDir);
|
|
392
392
|
fsyncDir(parent); // DUR-3: durable fresh-install promotion.
|
|
393
393
|
return { backupDir: null };
|
|
394
394
|
}
|
|
@@ -1330,8 +1330,8 @@ function reconcileCapabilities(opts) {
|
|
|
1330
1330
|
// - crash after step (b): old bundle live at finalDir; only the aside copy leaks → swept.
|
|
1331
1331
|
const discard = `${finalDir}.discard-${process.pid}-${Date.now()}-${node_crypto_1.default.randomBytes(4).toString('hex')}`;
|
|
1332
1332
|
if (node_fs_1.default.existsSync(finalDir))
|
|
1333
|
-
|
|
1334
|
-
|
|
1333
|
+
retryRenameSync(finalDir, discard); // (a) set the new dir aside
|
|
1334
|
+
retryRenameSync(backupDir, finalDir); // (b) restore the old bundle
|
|
1335
1335
|
fsyncDir(root); // make the restore durable
|
|
1336
1336
|
try {
|
|
1337
1337
|
node_fs_1.default.rmSync(discard, { recursive: true, force: true });
|