@opengsd/gsd-core 1.3.1 → 1.4.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/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/agents/gsd-advisor-researcher.md +1 -20
- package/agents/gsd-ai-researcher.md +2 -21
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-domain-researcher.md +2 -21
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-framework-selector.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-pattern-mapper.md +1 -1
- package/agents/gsd-phase-researcher.md +92 -166
- package/agents/gsd-planner.md +9 -36
- package/agents/gsd-project-researcher.md +62 -141
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +3 -22
- package/agents/gsd-user-profiler.md +1 -1
- package/agents/gsd-verifier.md +8 -2
- package/bin/install.js +1977 -339
- package/commands/gsd/autonomous.md +2 -0
- package/commands/gsd/execute-phase.md +2 -0
- package/commands/gsd/graphify.md +11 -6
- package/commands/gsd/import.md +6 -2
- package/commands/gsd/plan-phase.md +4 -2
- package/commands/gsd/progress.md +1 -0
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/update.md +3 -2
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +61 -6
- package/gsd-core/bin/gsd-tools.cjs +238 -32
- package/gsd-core/bin/lib/check-command-router.cjs +1 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
- package/gsd-core/bin/lib/commands.cjs +5 -4
- package/gsd-core/bin/lib/config.cjs +28 -4
- package/gsd-core/bin/lib/core.cjs +72 -28
- package/gsd-core/bin/lib/graphify.cjs +2 -2
- package/gsd-core/bin/lib/init-command-router.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +19 -3
- package/gsd-core/bin/lib/install-profiles.cjs +58 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
- package/gsd-core/bin/lib/intel.cjs +3 -20
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase.cjs +3 -3
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
- package/gsd-core/bin/lib/security.cjs +73 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +9 -0
- package/gsd-core/bin/lib/surface.cjs +54 -11
- package/gsd-core/bin/lib/validate.cjs +2 -2
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planning-config.md +3 -1
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/workflows/execute-phase.md +19 -8
- package/gsd-core/workflows/help/modes/full.md +4 -3
- package/gsd-core/workflows/ingest-docs.md +3 -2
- package/gsd-core/workflows/plan-phase.md +14 -10
- package/gsd-core/workflows/plan-review-convergence.md +3 -3
- package/gsd-core/workflows/review.md +24 -7
- package/gsd-core/workflows/ship.md +5 -8
- package/gsd-core/workflows/spec-phase.md +2 -1
- package/gsd-core/workflows/update.md +34 -6
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-context-monitor.js +1 -1
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/gsd-workflow-guard.js +1 -0
- package/hooks/dist/gsd-worktree-path-guard.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-context-monitor.js +1 -1
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/gsd-workflow-guard.js +1 -0
- package/hooks/gsd-worktree-path-guard.js +1 -1
- package/hooks/hooks.json +69 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +8 -1
- package/scripts/affected-tests-lib.cjs +3 -2
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/cli.cjs +226 -28
- package/scripts/changeset/lint.cjs +5 -4
- package/scripts/changeset/new.cjs +4 -4
- package/scripts/check-alias-drift.cjs +77 -71
- package/scripts/check-env.cjs +185 -179
- package/scripts/check-npm-integrity.cjs +115 -109
- package/scripts/ci-guard-runner.cjs +11 -5
- package/scripts/ci-prepare-test-scope.cjs +27 -22
- package/scripts/ci-rebase-check.cjs +46 -45
- package/scripts/ci-test-scope.cjs +126 -22
- package/scripts/diff-touches-shipped-paths.cjs +52 -44
- package/scripts/gen-inventory-manifest.cjs +38 -32
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +28 -22
- package/scripts/lint-descriptions.cjs +32 -28
- package/scripts/lint-docs-required.cjs +4 -4
- package/scripts/lint-legacy-dir-name.cjs +56 -52
- package/scripts/lint-pr-check-project-dir.cjs +3 -1
- package/scripts/lint-shell-command-projection-drift.cjs +27 -22
- package/scripts/lint-skill-deps.cjs +31 -26
- package/scripts/lint-test-file-count.allowlist.json +2 -0
- package/scripts/lint-test-file-count.cjs +5 -4
- package/scripts/mutation-matrix.cjs +6 -3
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +8 -3
- package/scripts/release-tarball-smoke.cjs +6 -4
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +2 -1
- package/scripts/run-cross-platform-tests.cjs +11 -7
- package/scripts/run-tests.cjs +8 -7
- package/scripts/strip-prose-atrefs.cjs +1 -1
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-runtime-launcher.cjs +0 -3
- package/scripts/verify-npm-publish.cjs +14 -26
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
|
|
171
171
|
const fs = require('fs');
|
|
172
172
|
const path = require('path');
|
|
173
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
173
174
|
const core = require('./lib/core.cjs');
|
|
174
175
|
const { error, ERROR_REASON } = core;
|
|
175
176
|
// Resolve findProjectRoot lazily at call time rather than binding it at module
|
|
@@ -197,6 +198,8 @@ const learnings = require('./lib/learnings.cjs');
|
|
|
197
198
|
const gapChecker = require('./lib/gap-checker.cjs');
|
|
198
199
|
const { routeStateCommand } = require('./lib/state-command-router.cjs');
|
|
199
200
|
const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
|
201
|
+
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
|
202
|
+
const verification = require('./lib/verification.cjs');
|
|
200
203
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
201
204
|
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
|
202
205
|
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
|
@@ -298,13 +301,11 @@ async function main() {
|
|
|
298
301
|
|
|
299
302
|
// Optional workstream override for parallel milestone work.
|
|
300
303
|
// Priority: --ws flag > GSD_WORKSTREAM env var > session/shared pointer > null.
|
|
301
|
-
let ws = null;
|
|
302
304
|
let workstreamContext = null;
|
|
303
305
|
try {
|
|
304
306
|
workstreamContext = resolveActiveWorkstream(cwd, args, process.env, {
|
|
305
307
|
getStored: getActiveWorkstream,
|
|
306
308
|
});
|
|
307
|
-
ws = workstreamContext.ws;
|
|
308
309
|
args = workstreamContext.args;
|
|
309
310
|
// Set env var so all modules (planningDir, planningPaths) auto-resolve workstream paths.
|
|
310
311
|
applyResolvedWorkstreamEnv(workstreamContext, process.env);
|
|
@@ -378,8 +379,8 @@ async function main() {
|
|
|
378
379
|
'current-timestamp, detect-custom-files, docs-init, effort, extract-messages, find-phase, ' +
|
|
379
380
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
380
381
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
381
|
-
'learnings, list-todos, milestone, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
382
|
-
'profile-sample, progress, prompt-budget, requirements, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
382
|
+
'classify-confidence, learnings, list-todos, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
383
|
+
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
383
384
|
'task, template, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
384
385
|
'Global flags:\n' +
|
|
385
386
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -423,6 +424,7 @@ async function main() {
|
|
|
423
424
|
'generate-slug', 'current-timestamp', 'verify-path-exists',
|
|
424
425
|
'verify-summary', 'template', 'frontmatter', 'detect-custom-files',
|
|
425
426
|
'worktree', 'prompt-budget',
|
|
427
|
+
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
426
428
|
]);
|
|
427
429
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
428
430
|
cwd = findProjectRoot(cwd);
|
|
@@ -547,7 +549,22 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
547
549
|
}
|
|
548
550
|
|
|
549
551
|
case 'resolve-granularity': {
|
|
550
|
-
|
|
552
|
+
// Parse optional --granularity <val> flag (space form only); positional is phase-type.
|
|
553
|
+
// The =form (--granularity=<val>) is intentionally not supported: parseNamedArgs and
|
|
554
|
+
// the /gsd:plan-phase + init plan-phase paths accept only the space form, so supporting
|
|
555
|
+
// = here alone would create an inconsistency (#703).
|
|
556
|
+
const granArgs = args.slice(1);
|
|
557
|
+
let granOverride;
|
|
558
|
+
const granPositionals = [];
|
|
559
|
+
for (let i = 0; i < granArgs.length; i++) {
|
|
560
|
+
const a = granArgs[i];
|
|
561
|
+
if (a === '--granularity' && granArgs[i + 1] !== undefined && !granArgs[i + 1].startsWith('--')) {
|
|
562
|
+
if (granOverride === undefined) { granOverride = granArgs[++i]; } else { ++i; }
|
|
563
|
+
} else {
|
|
564
|
+
granPositionals.push(a);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
commands.cmdResolveGranularity(cwd, granPositionals[0], raw, granOverride);
|
|
551
568
|
break;
|
|
552
569
|
}
|
|
553
570
|
|
|
@@ -761,6 +778,26 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
761
778
|
break;
|
|
762
779
|
}
|
|
763
780
|
|
|
781
|
+
// ─── Verification Status ───────────────────────────────────────────────
|
|
782
|
+
//
|
|
783
|
+
// verification status <phaseDir>
|
|
784
|
+
// Read the first *-VERIFICATION.md in phaseDir and return
|
|
785
|
+
// { status, next_action, next_command } routing result.
|
|
786
|
+
//
|
|
787
|
+
// Note: `verification` (reads verifier-emitted status) is distinct from
|
|
788
|
+
// `verify` (runs verification checks like plan-structure/artifacts).
|
|
789
|
+
|
|
790
|
+
case 'verification': {
|
|
791
|
+
routeVerificationCommand({
|
|
792
|
+
verification,
|
|
793
|
+
args,
|
|
794
|
+
cwd,
|
|
795
|
+
raw,
|
|
796
|
+
error,
|
|
797
|
+
});
|
|
798
|
+
break;
|
|
799
|
+
}
|
|
800
|
+
|
|
764
801
|
case 'generate-slug': {
|
|
765
802
|
// Phase 6 (#3575): dispatch via SDK executeForCjs when available.
|
|
766
803
|
// SDK handler: generateSlug in sdk/src/query/utils.ts.
|
|
@@ -1232,8 +1269,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1232
1269
|
worktreeSafety.cmdWorktreeCleanupWave(cwd, args.slice(2));
|
|
1233
1270
|
} else if (subcommand === 'reap-orphans') {
|
|
1234
1271
|
worktreeSafety.cmdWorktreeReapOrphans(cwd);
|
|
1272
|
+
} else if (subcommand === 'base-check') {
|
|
1273
|
+
require('./lib/worktree-base-ref.cjs').cmdWorktreeBaseCheck(cwd, args.slice(2));
|
|
1274
|
+
} else if (subcommand === 'set-baseref') {
|
|
1275
|
+
require('./lib/worktree-base-ref.cjs').cmdWorktreeSetBaseRef(cwd, args.slice(2));
|
|
1235
1276
|
} else {
|
|
1236
|
-
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1277
|
+
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1237
1278
|
}
|
|
1238
1279
|
break;
|
|
1239
1280
|
}
|
|
@@ -1528,33 +1569,26 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1528
1569
|
|
|
1529
1570
|
// ── Validate required args ─────────────────────────────────────────
|
|
1530
1571
|
if (!budgetStr) {
|
|
1531
|
-
|
|
1532
|
-
process.exit(1);
|
|
1572
|
+
throw new ExitError(1, 'Error: --budget <N> is required');
|
|
1533
1573
|
}
|
|
1534
1574
|
const budget = parseInt(budgetStr, 10);
|
|
1535
1575
|
if (!Number.isFinite(budget) || budget <= 0) {
|
|
1536
|
-
|
|
1537
|
-
process.exit(1);
|
|
1576
|
+
throw new ExitError(1, 'Error: --budget must be a positive integer');
|
|
1538
1577
|
}
|
|
1539
1578
|
if (!instructionsFile) {
|
|
1540
|
-
|
|
1541
|
-
process.exit(1);
|
|
1579
|
+
throw new ExitError(1, 'Error: --instructions-file <path> is required');
|
|
1542
1580
|
}
|
|
1543
1581
|
if (!roadmapFile) {
|
|
1544
|
-
|
|
1545
|
-
process.exit(1);
|
|
1582
|
+
throw new ExitError(1, 'Error: --roadmap-file <path> is required');
|
|
1546
1583
|
}
|
|
1547
1584
|
if (planFiles.length === 0) {
|
|
1548
|
-
|
|
1549
|
-
process.exit(1);
|
|
1585
|
+
throw new ExitError(1, 'Error: at least one --plan-file <path> is required');
|
|
1550
1586
|
}
|
|
1551
1587
|
if (!outputPromptFile) {
|
|
1552
|
-
|
|
1553
|
-
process.exit(1);
|
|
1588
|
+
throw new ExitError(1, 'Error: --output-prompt <path> is required');
|
|
1554
1589
|
}
|
|
1555
1590
|
if (!outputMetadataFile) {
|
|
1556
|
-
|
|
1557
|
-
process.exit(1);
|
|
1591
|
+
throw new ExitError(1, 'Error: --output-metadata <path> is required');
|
|
1558
1592
|
}
|
|
1559
1593
|
|
|
1560
1594
|
// ── Validate and read required files ──────────────────────────────
|
|
@@ -1564,11 +1598,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1564
1598
|
return await fs.promises.readFile(resolved, 'utf8');
|
|
1565
1599
|
} catch (err) {
|
|
1566
1600
|
if (err && err.code === 'ENOENT') {
|
|
1567
|
-
|
|
1568
|
-
process.exit(1);
|
|
1601
|
+
throw new ExitError(1, `Error: file not found for ${flagName}: ${resolved}`);
|
|
1569
1602
|
}
|
|
1570
|
-
|
|
1571
|
-
process.exit(1);
|
|
1603
|
+
throw new ExitError(1, `Error: cannot read file for ${flagName}: ${resolved}`);
|
|
1572
1604
|
}
|
|
1573
1605
|
}
|
|
1574
1606
|
|
|
@@ -1579,8 +1611,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1579
1611
|
return await fs.promises.readFile(resolved, 'utf8');
|
|
1580
1612
|
} catch (err) {
|
|
1581
1613
|
if (err && err.code === 'ENOENT') return null;
|
|
1582
|
-
|
|
1583
|
-
process.exit(1);
|
|
1614
|
+
throw new ExitError(1, `Error: cannot read optional file: ${resolved}`);
|
|
1584
1615
|
}
|
|
1585
1616
|
}
|
|
1586
1617
|
|
|
@@ -1593,11 +1624,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1593
1624
|
return { file: path.basename(p), content };
|
|
1594
1625
|
} catch (err) {
|
|
1595
1626
|
if (err && err.code === 'ENOENT') {
|
|
1596
|
-
|
|
1597
|
-
process.exit(1);
|
|
1627
|
+
throw new ExitError(1, `Error: plan file not found: ${resolved}`);
|
|
1598
1628
|
}
|
|
1599
|
-
|
|
1600
|
-
process.exit(1);
|
|
1629
|
+
throw new ExitError(1, `Error: cannot read plan file: ${resolved}`);
|
|
1601
1630
|
}
|
|
1602
1631
|
}));
|
|
1603
1632
|
|
|
@@ -1626,7 +1655,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1626
1655
|
await fs.promises.writeFile(path.resolve(outputPromptFile), prompt);
|
|
1627
1656
|
|
|
1628
1657
|
if (metadata.hardFailed) {
|
|
1629
|
-
|
|
1658
|
+
throw new ExitError(2);
|
|
1630
1659
|
}
|
|
1631
1660
|
break;
|
|
1632
1661
|
}
|
|
@@ -1663,6 +1692,183 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1663
1692
|
break;
|
|
1664
1693
|
}
|
|
1665
1694
|
|
|
1695
|
+
// ─── Research Store ────────────────────────────────────────────────────
|
|
1696
|
+
//
|
|
1697
|
+
// research-store get <key> [--kind <k>]
|
|
1698
|
+
// -> getResearch(cwd, key, { homeDir }); searches both tiers; core.output(result, raw)
|
|
1699
|
+
// (--kind is accepted for backward compatibility but no longer drives tier selection)
|
|
1700
|
+
// research-store put <key> --content <str> --source <s> --provider <p>
|
|
1701
|
+
// --confidence <c> --kind <k>
|
|
1702
|
+
// -> putResearch(cwd, key, { content, source, provider, confidence, kind })
|
|
1703
|
+
//
|
|
1704
|
+
// Tier is derived from source: 'curated' source writes to process.env.HOME/.gsd/research-cache;
|
|
1705
|
+
// all other sources write to cwd/.planning/research/.cache.
|
|
1706
|
+
// Tests may override the home directory by setting the HOME env var.
|
|
1707
|
+
|
|
1708
|
+
case 'research-store': {
|
|
1709
|
+
const researchStore = require('./lib/research-store.cjs');
|
|
1710
|
+
const subcommand = args[1];
|
|
1711
|
+
const homeDir = process.env.HOME || require('os').homedir();
|
|
1712
|
+
if (subcommand === 'get') {
|
|
1713
|
+
const key = args[2];
|
|
1714
|
+
if (!key || key.startsWith('--')) {
|
|
1715
|
+
error('Usage: gsd-tools research-store get <key> [--kind <k>]', ERROR_REASON.USAGE);
|
|
1716
|
+
}
|
|
1717
|
+
if (!researchStore.isValidResearchKey(key)) {
|
|
1718
|
+
error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
|
|
1719
|
+
}
|
|
1720
|
+
// --kind is accepted but no longer drives tier selection; getResearch searches both tiers
|
|
1721
|
+
const result = researchStore.getResearch(cwd, key, { homeDir });
|
|
1722
|
+
core.output(result, raw);
|
|
1723
|
+
} else if (subcommand === 'put') {
|
|
1724
|
+
const key = args[2];
|
|
1725
|
+
if (!key || key.startsWith('--')) {
|
|
1726
|
+
error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
|
|
1727
|
+
}
|
|
1728
|
+
if (!researchStore.isValidResearchKey(key)) {
|
|
1729
|
+
error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
|
|
1730
|
+
}
|
|
1731
|
+
const contentIdx = args.indexOf('--content');
|
|
1732
|
+
const sourceIdx = args.indexOf('--source');
|
|
1733
|
+
const providerIdx = args.indexOf('--provider');
|
|
1734
|
+
const confidenceIdx = args.indexOf('--confidence');
|
|
1735
|
+
const kindIdx = args.indexOf('--kind');
|
|
1736
|
+
// For each flag, if the following value is missing or itself starts with '--', reject.
|
|
1737
|
+
function getFlagValue(idx, flagName) {
|
|
1738
|
+
if (idx === -1) return null;
|
|
1739
|
+
const val = args[idx + 1];
|
|
1740
|
+
if (val === undefined || val.startsWith('--')) {
|
|
1741
|
+
error(`research-store put: missing value for ${flagName}`, ERROR_REASON.USAGE);
|
|
1742
|
+
}
|
|
1743
|
+
return val;
|
|
1744
|
+
}
|
|
1745
|
+
const content = getFlagValue(contentIdx, '--content');
|
|
1746
|
+
const source = getFlagValue(sourceIdx, '--source');
|
|
1747
|
+
const provider = getFlagValue(providerIdx, '--provider');
|
|
1748
|
+
const confidence = getFlagValue(confidenceIdx, '--confidence');
|
|
1749
|
+
const kind = getFlagValue(kindIdx, '--kind');
|
|
1750
|
+
if (!content || !source || !provider || !confidence || !kind) {
|
|
1751
|
+
error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
|
|
1752
|
+
}
|
|
1753
|
+
const entry = researchStore.putResearch(cwd, key, { content, source, provider, confidence, kind }, { homeDir });
|
|
1754
|
+
core.output(entry, raw);
|
|
1755
|
+
} else {
|
|
1756
|
+
error('Unknown research-store subcommand. Available: get, put', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1757
|
+
}
|
|
1758
|
+
break;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// ─── Research Plan ─────────────────────────────────────────────────────
|
|
1762
|
+
//
|
|
1763
|
+
// research-plan --input <path>
|
|
1764
|
+
// Read+JSON.parse file; call planResearch({ questions, ecosystem, config, cwd })
|
|
1765
|
+
// { ecosystem, config, questions: [{ text, kind, library?, version? }] }
|
|
1766
|
+
|
|
1767
|
+
case 'research-plan': {
|
|
1768
|
+
const researchProvider = require('./lib/research-provider.cjs');
|
|
1769
|
+
const inputIdx = args.indexOf('--input');
|
|
1770
|
+
const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
|
|
1771
|
+
if (!inputPath || inputPath.startsWith('--')) {
|
|
1772
|
+
error('Usage: gsd-tools research-plan --input <path>', ERROR_REASON.USAGE);
|
|
1773
|
+
}
|
|
1774
|
+
let planInput;
|
|
1775
|
+
try {
|
|
1776
|
+
const raw_ = fs.readFileSync(path.resolve(inputPath), 'utf8');
|
|
1777
|
+
planInput = JSON.parse(raw_);
|
|
1778
|
+
} catch (readErr) {
|
|
1779
|
+
error(`research-plan: cannot read/parse --input file: ${inputPath}`, ERROR_REASON.USAGE);
|
|
1780
|
+
}
|
|
1781
|
+
if (planInput === null || typeof planInput !== 'object' || Array.isArray(planInput)) {
|
|
1782
|
+
error('research-plan: --input must be an object with a questions array', ERROR_REASON.USAGE);
|
|
1783
|
+
}
|
|
1784
|
+
if (!Array.isArray(planInput.questions)) {
|
|
1785
|
+
error('research-plan: --input must be an object with a questions array', ERROR_REASON.USAGE);
|
|
1786
|
+
}
|
|
1787
|
+
const { ecosystem = '', config: planConfig = {}, questions } = planInput;
|
|
1788
|
+
const homeDir = process.env.HOME || require('os').homedir();
|
|
1789
|
+
const plan = researchProvider.planResearch({ questions, ecosystem, config: planConfig, cwd, homeDir });
|
|
1790
|
+
core.output(plan, raw);
|
|
1791
|
+
break;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// ─── Classify Confidence ──────────────────────────────────────────────
|
|
1795
|
+
//
|
|
1796
|
+
// classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]
|
|
1797
|
+
// -> classifyConfidence({ provider, verifiedAgainstOfficial, legitimacyVerdict }); core.output(result, raw)
|
|
1798
|
+
//
|
|
1799
|
+
// legitimacyVerdict is CODE-COMPUTED via checkPackages — never caller-supplied — so an agent cannot self-assert OK→HIGH.
|
|
1800
|
+
|
|
1801
|
+
case 'classify-confidence': {
|
|
1802
|
+
const researchProvider = require('./lib/research-provider.cjs');
|
|
1803
|
+
const providerIdx = args.indexOf('--provider');
|
|
1804
|
+
const provider = providerIdx !== -1 ? args[providerIdx + 1] : null;
|
|
1805
|
+
if (!provider || provider.startsWith('--')) {
|
|
1806
|
+
error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
|
|
1807
|
+
}
|
|
1808
|
+
const verified = args.includes('--verified');
|
|
1809
|
+
const pkgIdx = args.indexOf('--package');
|
|
1810
|
+
const pkg = pkgIdx !== -1 ? args[pkgIdx + 1] : null;
|
|
1811
|
+
const ecoIdx = args.indexOf('--ecosystem');
|
|
1812
|
+
const ecosystem = ecoIdx !== -1 ? args[ecoIdx + 1] : null;
|
|
1813
|
+
let legitimacyVerdict = null;
|
|
1814
|
+
if (pkg && (!pkg.startsWith('--'))) {
|
|
1815
|
+
const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
|
|
1816
|
+
if (!ecosystem || ecosystem.startsWith('--') || !VALID_ECOSYSTEMS.has(ecosystem)) {
|
|
1817
|
+
error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
|
|
1818
|
+
}
|
|
1819
|
+
const pkgLegitimacy = require('./lib/package-legitimacy.cjs');
|
|
1820
|
+
const results = await pkgLegitimacy.checkPackages({ ecosystem, packages: [pkg] }, {});
|
|
1821
|
+
legitimacyVerdict = results[0] ? results[0].verdict : null;
|
|
1822
|
+
}
|
|
1823
|
+
const confidence = researchProvider.classifyConfidence({ provider, verifiedAgainstOfficial: verified, legitimacyVerdict });
|
|
1824
|
+
core.output({ provider, package: pkg || null, ecosystem: ecosystem || null, legitimacyVerdict, verified, confidence }, raw);
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// ─── Package Legitimacy ────────────────────────────────────────────────
|
|
1829
|
+
//
|
|
1830
|
+
// package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...
|
|
1831
|
+
//
|
|
1832
|
+
// checkPackages is ASYNC. This entire runCommand function is async, so
|
|
1833
|
+
// we can await directly. On rejection we call error() which exits.
|
|
1834
|
+
|
|
1835
|
+
case 'package-legitimacy': {
|
|
1836
|
+
const pkgLegitimacy = require('./lib/package-legitimacy.cjs');
|
|
1837
|
+
const subcommand = args[1];
|
|
1838
|
+
if (subcommand !== 'check') {
|
|
1839
|
+
error('Unknown package-legitimacy subcommand. Available: check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1840
|
+
}
|
|
1841
|
+
const ecoIdx = args.indexOf('--ecosystem');
|
|
1842
|
+
const ecosystem = ecoIdx !== -1 ? args[ecoIdx + 1] : null;
|
|
1843
|
+
const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
|
|
1844
|
+
if (!ecosystem || !VALID_ECOSYSTEMS.has(ecosystem)) {
|
|
1845
|
+
error('Usage: gsd-tools package-legitimacy check --ecosystem <npm|pypi|crates> <pkg1> ...', ERROR_REASON.USAGE);
|
|
1846
|
+
}
|
|
1847
|
+
// Collect positional package names.
|
|
1848
|
+
// Only --ecosystem takes a value. Every non-flag arg is a package name.
|
|
1849
|
+
// Any unknown --flag is a usage error (do not silently skip+consume the next arg).
|
|
1850
|
+
const packages = [];
|
|
1851
|
+
for (let i = 2; i < args.length; i++) {
|
|
1852
|
+
const a = args[i];
|
|
1853
|
+
if (a === '--ecosystem') { i++; continue; }
|
|
1854
|
+
if (a.startsWith('--')) {
|
|
1855
|
+
error(`package-legitimacy: unknown flag ${a}`, ERROR_REASON.USAGE);
|
|
1856
|
+
}
|
|
1857
|
+
packages.push(a);
|
|
1858
|
+
}
|
|
1859
|
+
if (packages.length === 0) {
|
|
1860
|
+
error('Usage: gsd-tools package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...', ERROR_REASON.USAGE);
|
|
1861
|
+
}
|
|
1862
|
+
let pkgResults;
|
|
1863
|
+
try {
|
|
1864
|
+
pkgResults = await pkgLegitimacy.checkPackages({ ecosystem, packages }, {});
|
|
1865
|
+
} catch (pkgErr) {
|
|
1866
|
+
error(`package-legitimacy: ${pkgErr && pkgErr.message ? pkgErr.message : String(pkgErr)}`, ERROR_REASON.UNKNOWN);
|
|
1867
|
+
}
|
|
1868
|
+
core.output(pkgResults, raw);
|
|
1869
|
+
break;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1666
1872
|
case 'effort': {
|
|
1667
1873
|
const subcommand = args[1];
|
|
1668
1874
|
if (subcommand === 'sync') {
|
|
@@ -1719,4 +1925,4 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1719
1925
|
}
|
|
1720
1926
|
}
|
|
1721
1927
|
|
|
1722
|
-
main
|
|
1928
|
+
runMain(main);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error carrying a process exit code. CLI logic throws this instead of calling
|
|
4
|
+
* process.exit() (banned by n/no-process-exit); runMain() translates it into
|
|
5
|
+
* process.exitCode at the entrypoint.
|
|
6
|
+
*/
|
|
7
|
+
class ExitError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
hasUserMessage;
|
|
10
|
+
constructor(code = 1, message) {
|
|
11
|
+
super(message === undefined ? `process exit ${code}` : message);
|
|
12
|
+
this.name = 'ExitError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.hasUserMessage = message !== undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run a CLI main and translate its outcome into process.exitCode (never
|
|
19
|
+
* process.exit, so n/no-process-exit stays satisfied; output flushes and
|
|
20
|
+
* process.on('exit') cleanup still fires). main may be sync or async:
|
|
21
|
+
* number return -> process.exitCode = it
|
|
22
|
+
* thrown ExitError -> process.exitCode = err.code (+ stderr err.message if hasUserMessage && code!=0)
|
|
23
|
+
* other throw -> stderr stack + process.exitCode = 1
|
|
24
|
+
*/
|
|
25
|
+
function runMain(main) {
|
|
26
|
+
Promise.resolve()
|
|
27
|
+
.then(() => main())
|
|
28
|
+
.then((code) => { if (typeof code === 'number')
|
|
29
|
+
process.exitCode = code; })
|
|
30
|
+
.catch((err) => {
|
|
31
|
+
if (err instanceof ExitError) {
|
|
32
|
+
if (err.hasUserMessage && err.code !== 0)
|
|
33
|
+
process.stderr.write(`${err.message}\n`);
|
|
34
|
+
process.exitCode = err.code;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const e = err;
|
|
38
|
+
process.stderr.write(`${e && e.stack ? e.stack : String(err)}\n`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
module.exports = { ExitError, runMain };
|
|
@@ -189,7 +189,7 @@ function createHub({ cjsRegistry, manifest, logger } = {}) {
|
|
|
189
189
|
}
|
|
190
190
|
// err variant: already has kind + typed payload
|
|
191
191
|
// Double-cast through unknown to satisfy strict index-signature check.
|
|
192
|
-
return hubResult;
|
|
192
|
+
return hubResult;
|
|
193
193
|
}
|
|
194
194
|
/**
|
|
195
195
|
* Emit a DispatchEvent to the injected logger.
|
|
@@ -14,7 +14,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
16
|
const core = require("./core.cjs");
|
|
17
|
-
const { loadConfig, isGitIgnored, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, stripShippedMilestones: _stripShippedMilestones, extractCurrentMilestone, toPosixPath, output, error, findPhaseInternal, extractOneLinerFromBody, getRoadmapPhaseInternal, extractPhaseToken, resolveGranularityInternal, } = core;
|
|
17
|
+
const { loadConfig, isGitIgnored, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, stripShippedMilestones: _stripShippedMilestones, extractCurrentMilestone, toPosixPath, output, error, findPhaseInternal, extractOneLinerFromBody, getRoadmapPhaseInternal, extractPhaseToken, resolveGranularityInternal, assertValidGranularityOverride, } = core;
|
|
18
18
|
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
19
19
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
20
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -244,11 +244,12 @@ function cmdResolveModel(cwd, agentType, raw) {
|
|
|
244
244
|
: { model, profile, effort, unknown_agent: true };
|
|
245
245
|
output(result, raw, model);
|
|
246
246
|
}
|
|
247
|
-
function cmdResolveGranularity(cwd, phaseType, raw) {
|
|
247
|
+
function cmdResolveGranularity(cwd, phaseType, raw, override) {
|
|
248
248
|
if (!phaseType) {
|
|
249
249
|
error('phase-type required');
|
|
250
250
|
}
|
|
251
|
-
|
|
251
|
+
assertValidGranularityOverride(override, error);
|
|
252
|
+
const granularity = resolveGranularityInternal(cwd, phaseType, override);
|
|
252
253
|
const result = (VALID_PHASE_TYPES).has(phaseType)
|
|
253
254
|
? { granularity, phase_type: phaseType }
|
|
254
255
|
: { granularity, phase_type: phaseType, unknown_phase_type: true };
|
|
@@ -1016,7 +1017,7 @@ function cmdStats(cwd, format, raw) {
|
|
|
1016
1017
|
const roadmapContent = extractCurrentMilestone(roadmapRaw, cwd);
|
|
1017
1018
|
// Matches both plain numeric (Phase 1:) and milestone-prefixed (Phase 2-01:) headings.
|
|
1018
1019
|
// Also tolerates optional [bracket-token] scope prefix on phase headings.
|
|
1019
|
-
const headingPattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*
|
|
1020
|
+
const headingPattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:\s*([^\n]+)/gi;
|
|
1020
1021
|
let match;
|
|
1021
1022
|
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
|
1022
1023
|
const key = normalizePhaseName(match[1]);
|
|
@@ -147,6 +147,14 @@ function buildNewProjectConfig(userChoices) {
|
|
|
147
147
|
const hasFirecrawl = !!(process.env['FIRECRAWL_API_KEY'] || node_fs_1.default.existsSync(firecrawlKeyFile));
|
|
148
148
|
const exaKeyFile = node_path_1.default.join(homedir, '.gsd', 'exa_api_key');
|
|
149
149
|
const hasExaSearch = !!(process.env['EXA_API_KEY'] || node_fs_1.default.existsSync(exaKeyFile));
|
|
150
|
+
const tavilyKeyFile = node_path_1.default.join(homedir, '.gsd', 'tavily_api_key');
|
|
151
|
+
const hasTavilySearch = !!(process.env['TAVILY_API_KEY'] || node_fs_1.default.existsSync(tavilyKeyFile));
|
|
152
|
+
const refKeyFile = node_path_1.default.join(homedir, '.gsd', 'ref_api_key');
|
|
153
|
+
const hasRefSearch = !!(process.env['REF_API_KEY'] || node_fs_1.default.existsSync(refKeyFile));
|
|
154
|
+
const perplexityKeyFile = node_path_1.default.join(homedir, '.gsd', 'perplexity_api_key');
|
|
155
|
+
const hasPerplexity = !!(process.env['PERPLEXITY_API_KEY'] || node_fs_1.default.existsSync(perplexityKeyFile));
|
|
156
|
+
const jinaKeyFile = node_path_1.default.join(homedir, '.gsd', 'jina_api_key');
|
|
157
|
+
const hasJina = !!(process.env['JINA_API_KEY'] || node_fs_1.default.existsSync(jinaKeyFile));
|
|
150
158
|
// Load user-level defaults from ~/.gsd/defaults.json if available
|
|
151
159
|
const globalDefaultsPath = node_path_1.default.join(homedir, '.gsd', 'defaults.json');
|
|
152
160
|
let userDefaults = {};
|
|
@@ -176,6 +184,10 @@ function buildNewProjectConfig(userChoices) {
|
|
|
176
184
|
brave_search: hasBraveSearch,
|
|
177
185
|
firecrawl: hasFirecrawl,
|
|
178
186
|
exa_search: hasExaSearch,
|
|
187
|
+
tavily_search: hasTavilySearch,
|
|
188
|
+
ref_search: hasRefSearch,
|
|
189
|
+
perplexity: hasPerplexity,
|
|
190
|
+
jina: hasJina,
|
|
179
191
|
git: {
|
|
180
192
|
branching_strategy: CONFIG_DEFAULTS.branching_strategy,
|
|
181
193
|
create_tag: true,
|
|
@@ -377,18 +389,31 @@ function setConfigValue(cwd, keyPath, parsedValue) {
|
|
|
377
389
|
catch (err) {
|
|
378
390
|
error('Failed to read config.json: ' + err.message, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
379
391
|
}
|
|
380
|
-
// Set nested value using dot notation (e.g., "workflow.research")
|
|
392
|
+
// Set nested value using dot notation (e.g., "workflow.research").
|
|
393
|
+
// Prototype-pollution guard: reject dangerous segments via inline literal
|
|
394
|
+
// comparisons on the exact key used to index `current`, immediately before
|
|
395
|
+
// each write. The inline comparison is the barrier CodeQL's
|
|
396
|
+
// js/prototype-pollution-utility query recognises — the previous Set-based
|
|
397
|
+
// pre-loop check was functionally correct but not traced through, so
|
|
398
|
+
// code-scanning alert #26 kept firing. Behaviour is unchanged from #663.
|
|
381
399
|
const keys = keyPath.split('.');
|
|
382
400
|
let current = config;
|
|
383
401
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
384
402
|
const key = keys[i];
|
|
403
|
+
if (key === '__proto__' || key === 'prototype' || key === 'constructor') {
|
|
404
|
+
error('Invalid config key (prototype pollution guard): ' + keyPath, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
405
|
+
}
|
|
385
406
|
if (current[key] === undefined || typeof current[key] !== 'object') {
|
|
386
407
|
current[key] = {};
|
|
387
408
|
}
|
|
388
409
|
current = current[key];
|
|
389
410
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
411
|
+
const lastKey = keys[keys.length - 1];
|
|
412
|
+
if (lastKey === '__proto__' || lastKey === 'prototype' || lastKey === 'constructor') {
|
|
413
|
+
error('Invalid config key (prototype pollution guard): ' + keyPath, ERROR_REASON.CONFIG_PARSE_FAILED);
|
|
414
|
+
}
|
|
415
|
+
const previousValue = current[lastKey]; // Capture previous value before overwriting
|
|
416
|
+
current[lastKey] = parsedValue;
|
|
392
417
|
// Write back
|
|
393
418
|
try {
|
|
394
419
|
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(config, null, 2));
|
|
@@ -611,7 +636,6 @@ function cmdConfigSetModelProfile(cwd, profile, raw) {
|
|
|
611
636
|
if (!profile) {
|
|
612
637
|
error(`Usage: config-set-model-profile <${VALID_PROFILES.join('|')}>`);
|
|
613
638
|
}
|
|
614
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
615
639
|
const normalizedProfile = profile.toLowerCase().trim();
|
|
616
640
|
if (!VALID_PROFILES.includes(normalizedProfile)) {
|
|
617
641
|
error(`Invalid profile '${String(profile)}'. Valid profiles: ${VALID_PROFILES.join(', ')}`);
|