feat(#656): Research module — content-addressed cache + provider seam + registry-API legitimacy (#664)
* feat(#656): add Research Store module (content-addressed cache, TTL staleness) Content-addressed research cache behind a clock seam: researchKey (sha256, deterministic), putResearch/getResearch ({hit,stale}, never throws), ttlForSource (curated HIGH 30d / MED 7d / web LOW 1d), two-tier resolveStorePath (curated -> ~/.gsd/research-cache, web/synthesis -> project .planning/research/.cache). 28 behavioral + property tests; boundary coverage at ttl-1/ttl/ttl+1. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#656): add Research Provider module (waterfall + confidence + plan) Single source of truth for the Balanced provider waterfall (docs Context7->Ref->Jina, web Exa+Tavily, fallback Perplexity/Brave, Firecrawl scrape-only). classifyConfidence stamps HIGH|MEDIUM|LOW by provider (never throws). providerAvailability maps config flags to usable providers. planResearch checks the Research Store (injected seam) and returns cache-hits + a per-question fetch plan, falling through the waterfall to the always-available websearch terminal. 22 behavioral + property tests. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#656): add Package Legitimacy module (registry-API verdicts, slopcheck optional) Replaces the pip-install-or-degrade slopcheck prose gate with code: classifyPackage (pure, never throws) computes OK|SUS|SLOP from tunable thresholds (minAgeDays 30, minWeeklyDownloads 1000, requireRepo). checkPackages queries injectable npm/PyPI/crates registry adapters (real https with 5s timeout, degraded-not-thrown on failure); slopcheck is one optional adapter that can only escalate severity, never degrade to [ASSUMED]. 34 behavioral + property tests; boundary coverage on age and downloads (limit-1/limit/limit+1). Known follow-up: real npm adapter must add api.npmjs.org last-week downloads fetch (currently null -> unknown-downloads). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#656): detect Tavily/Ref/Perplexity/Jina provider keys; complete npm downloads adapter config: add tavily_search/ref_search/perplexity/jina availability flags (env var or ~/.gsd/<x>_api_key), mirroring brave_search/exa_search/firecrawl, so the Research Provider waterfall can gate them. package-legitimacy: real npm adapter now fetches api.npmjs.org last-week downloads (bounded, degraded-not-thrown) so weeklyDownloads is populated. +12 config tests; 34 legitimacy tests unchanged. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#656): expose Research seam via gsd-tools query (research-plan, research-store, package-legitimacy) Routes the L2-hybrid surface so agents reach it as CLI: 'query research-store get/put' (cache, HOME-sandboxable), 'query research-plan --input' (cache-hits + fetch plan from planResearch), 'query package-legitimacy check --ecosystem' (async registry verdicts). Commands skip .planning root resolution and appear in top-level usage. 5 behavioral runGsdTools tests; command-contract unchanged (335). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#656): document Research module (CONTEXT predicates, ADR-0656, architecture, changeset) Adds GSD-RESEARCH.* + DEFECT.RESEARCH-PROVIDER-PROSE-DRIFT predicates to CONTEXT.md, ADR-0656 recording the L2-hybrid seam decision, a docs/ARCHITECTURE.md Research Module subsection, and an Added changeset fragment (pr:0, backfill on PR). Notes the #657 deferrals (agent collapse + install.js MCP mapping). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#656): sync inventory for research modules Regenerate INVENTORY-MANIFEST.json and bump docs/INVENTORY.md CLI Modules count 82->85 with rows for research-store/research-provider/package-legitimacy (DEFECT.INVENTORY-DRIFT). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#656): eslint-ignore generated research .cjs artifacts (ADR-457) research-store/research-provider/package-legitimacy .cjs are tsc-generated from src/*.cts, so they belong in the ESLint ignore block (lint the .cts source, not the emitted .cjs). Fixes tests/551-eslint-bin-lib-coverage. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#656): backfill changeset pr number to #664 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#656): satisfy eslint lint-tests gate Fix 20 eslint errors in the new research files: use helpers.cleanup() instead of raw fs.rmSync() in tests (local/no-raw-rmsync-in-tests, Windows-EBUSY retry budget); drop redundant '| string' union members and unnecessary type assertions; deterministic object normalization in researchKey (no-base-to-string). Logic unchanged; 6180 tests still green. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): harden package legitimacy per review (W1/W2/I3/I4) W1: httpsGet now reads statusCode; npm/PyPI/crates map 404 -> exists:false -> SLOP (registry-existence is the #1 slopsquatting defense; previously only npm caught it). Transport made injectable (_setHttpGet) for hermetic 404 tests. W2: suspicious-postinstall is now terminal SLOP independent of the optional slopcheck adapter, and the regex drops the bare https?:// arm (over-fired on esbuild/sharp/node-gyp) for shell-exec/download-exec signatures only. I3: checkPackages now threads version to registry.lookup and adapters verify that specific version exists. I4: moreServerVerdict -> moreSevereVerdict. +11 regression tests (all RED-first); 45 total green. Addresses review by @davesienkowski on #664. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): research-store tier coherence + freshness + version TTL (W4/I1/I2/I4) I1: tier now derives from source (curated -> user ~/.gsd, else -> project .planning), not kind, so put-tier and get-tier can't diverge; kind is a key component only. W4: getResearch searches both tiers and returns the freshest (non-stale preferred), never letting a stale curated entry shadow a fresh web one; blank version caps TTL at 1 day (no 30d on version-blind keys). I2: atomic platformWriteSync instead of raw fs.writeFileSync on the shared global path. I4: dropped the dead ttlForSource arm. CLI get now searches both tiers. +5 RED-first regression tests; 38 green. Addresses review by @davesienkowski on #664. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): expose classifyConfidence as a CLI route, killing dead code (W3) Adds 'gsd-tools query classify-confidence --provider X [--verified]' so research agents get the confidence tier FROM CODE (provider waterfall + verification lever) instead of asserting it in prose. classifyConfidence previously had no runtime caller. HIGH means 'trusted provider'; --verified raises web results to MEDIUM (verification semantics documented in ADR-0656). +4 behavioral tests. Addresses review by @davesienkowski on #664 (W3). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): close Codex adversarial-review findings (path-traversal, version-age, malformed-cache) HIGH: research key must be 64-hex sha256 (isValidResearchKey) + resolved-path containment check in put/get + CLI validation -> blocks '../../x' arbitrary-file-write. HIGH: package legitimacy now derives publishedAt from the REQUESTED version (npm time[version], PyPI releases[version] upload_time, crates versions[].created_at) so a new malicious version of an old package can't inherit old age and evade 'too-new'. MEDIUM: getResearch validates entry shape (finite fetched_at + positive ttl + required fields) -> malformed cache entry is a miss, not fresh-forever. +regression tests (RED-first); 111 green. Codex adversarial review (required pre-PR gate). Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): close code-review correctness findings (1) package-legitimacy CLI now rejects unknown --flags instead of silently consuming the following package as a flag value; only --ecosystem takes a value. (2) crates recent_downloads (90-day) normalized to a weekly figure before the minWeeklyDownloads threshold (was ~13x too lenient). (3) research-plan --input validates parsed JSON is an object with an Array questions before destructuring -> clean usage error instead of an uncaught TypeError on null/bad input. (4) research-store put rejects a flag value that is itself a --flag (no more storing '--source' as content). (5) planResearch skips questions whose text is not a non-empty string instead of emitting question:undefined. +13 RED-first regression tests; 143 green. Code-review gate. Issue #656. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(#657): extract researcher documentation_lookup to shared @-reference 6 researcher agents carried a near-duplicate <documentation_lookup> block; consolidate into gsd-core/references/research-documentation-lookup.md (@-included). Unifies the ctx7 CLI fallback to the safer 'command -v ctx7' guard (drops silent 'npx --yes ctx7@latest' execution in 5 agents). Behavior-preserving dedup; inventory 63->64 references. Phase A of the agent collapse. Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(#657): extract researcher philosophy + verification-protocol to shared @-references philosophy and the pitfalls+pre-submission-checklist common-core were near-duplicated in project/phase researchers; consolidate into gsd-core/references/research-{philosophy,verification-protocol}.md (@-included). phase-researcher keeps its 3 extra checklist items inline. Pre-submission domains checklist made agent-agnostic so project-researcher doesn't lose features/architecture coverage. Write-contract intentionally left inline (bug-214 tests assert it verbatim). Inventory 64->66 refs. Behavior-preserving. Phase A. Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#657): wire gsd-phase-researcher to the Research seam (Phase B / S1) The phase researcher now CALLS the code seam instead of carrying inline mechanics: provider waterfall -> 'gsd-tools query research-plan' (+ research-store put to cache digests); confidence-tier prose -> 'gsd-tools query classify-confidence'; slopcheck pip-install protocol -> 'gsd-tools query package-legitimacy check'. This makes the Research module a real runtime consumer (validates the seam end-to-end, addresses reviewer S1) and removes the duplicated waterfall/confidence/slopcheck prose. RESEARCH.md output contract, commit step, structured returns, and Phase-A @-includes unchanged. package-legitimacy-gate.test.cjs rewritten prose-grep -> behavioral (asserts the seam invocation). Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#657): wire gsd-project-researcher to the seam + add tavily/ref/jina MCP tools (Phase C.1) project-researcher now calls gsd-tools query research-plan / classify-confidence (+ research-store put) instead of the inline provider waterfall + confidence-tier prose (mirrors the phase-researcher rewire; no package-legitimacy — phase-only). Output contract (STACK/FEATURES/ARCHITECTURE/PITFALLS/SUMMARY.md + sections, no-commit, structured returns, Phase-A @-includes) unchanged. Adds mcp__tavily/ref/jina__* to the project/phase/ui researcher tools frontmatter (Balanced provider set) so install.js MCP mapping (C.2) has a consumer. Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(#657): cover tavily/ref/jina MCP install handling + frontmatter parity guard (Phase C.2) Investigation: exa/firecrawl have no explicit per-runtime tool-mapping — every mcp__<server>__* except context7 rides the generic passthrough (Copilot lowercases; OpenCode/Cursor/Windsurf/Augment keep as-is; Gemini auto-discovers). tavily/ref/jina are handled identically, no install path broken. Added 12 copilot-install passthrough tests + a mcp-tool-inheritance parity guard (tavily co-declared with exa, jina with firecrawl, ref present across the 3 web researchers) so the MCP set can't drift. No io.github registry ids invented (none sourceable in-repo); documented as a follow-up. 488 tests green. Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#657): profiles as source of truth for researcher agents + drift-guard (Phase C.3) scripts/research-profiles.cjs declares each of the 7 researcher agents' identity + contract (name, description, color, tools, required @-includes, required gsd-tools seam calls, output-contract markers). scripts/gen-research-agents.cjs --check validates every committed agent against its profile; --write regenerates ONLY the frontmatter from profiles (body untouched) and is a verified no-op against the current agents (zero diff = fidelity). tests/research-agent-profiles.test.cjs is the DEFECT.GENERATIVE-FIX drift guard. Design note: profiles govern the generatable/contract surface rather than destructively regenerating the disparate operational prose bodies (those were deduped via @-includes in Phase A). scripts/ is not inventoried (no inventory change). Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#657): complete agent provider-dispatch + parity guard; align legitimacy field; validate profiles Adversarial-review findings: (HIGH) the seam-wired agents' Step-C dispatch only mapped 6 providers, so a planResearch result of jina/ref/perplexity/brave (reachable via the waterfall fallbacks) had no handling -> agent stall; completed both agents' dispatch to all 9 PROVIDER_WATERFALL ids + a catch-all, and added a parity test asserting agent dispatch stays in sync with research-provider PROVIDER_WATERFALL (DEFECT.GENERATIVE-FIX). (MEDIUM) phase-researcher package-legitimacy JSON example used 'package' but the module returns 'name' -> aligned. (LOW) gen-research-agents checkAgent now returns a clear failure for a malformed profile instead of throwing. +parity/validation tests (RED-first). Issue #657. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(#656): make classifyConfidence verification-evidence-driven (W3) Confidence conflated provider authority with claim verification — context7/ref stamped HIGH purely by provider identity, and the only verification lever was a self-set --verified flag. Split into two axes: provider authority (static) + verification evidence (code-computed). HIGH now requires ground-truth corroboration (legitimacyVerdict OK), independent of provider; authority alone caps at MEDIUM; SLOP caps at LOW; the self-reported --verified is demoted to a MEDIUM-only web lever. HIGH = corroborated-against-authoritative-source, not a correctness guarantee. Adds --legitimacy-verdict to the classify-confidence CLI; updates CONTEXT.md predicate + ADR-0656 (tier set unchanged, ADR-consistent). Addresses davesienkowski's W3 review on #664. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#656): bind classify-confidence verdict to code, closing CLI self-grading Adversarial review found the new --legitimacy-verdict flag was caller-supplied, so an agent could self-assert OK->HIGH without any real legitimacy check — reintroducing the exact self-grading hole W3 closes. Remove the free flag; the CLI now computes the verdict via checkPackages only when --package/--ecosystem is given (code-computed, not agent-asserted). Update the stale CLI test (context7 alone -> MEDIUM) and extend the property test to vary legitimacyVerdict. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -378,8 +378,8 @@ async function main() {
|
||||
'current-timestamp, detect-custom-files, docs-init, effort, extract-messages, find-phase, ' +
|
||||
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
||||
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
||||
'learnings, list-todos, milestone, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
||||
'profile-sample, progress, prompt-budget, requirements, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
||||
'classify-confidence, learnings, list-todos, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
||||
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
||||
'task, template, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
||||
'Global flags:\n' +
|
||||
' --raw Emit raw output without post-processing\n' +
|
||||
@@ -423,6 +423,7 @@ async function main() {
|
||||
'generate-slug', 'current-timestamp', 'verify-path-exists',
|
||||
'verify-summary', 'template', 'frontmatter', 'detect-custom-files',
|
||||
'worktree', 'prompt-budget',
|
||||
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
||||
]);
|
||||
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
||||
cwd = findProjectRoot(cwd);
|
||||
@@ -1663,6 +1664,183 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
break;
|
||||
}
|
||||
|
||||
// ─── Research Store ────────────────────────────────────────────────────
|
||||
//
|
||||
// research-store get <key> [--kind <k>]
|
||||
// -> getResearch(cwd, key, { homeDir }); searches both tiers; core.output(result, raw)
|
||||
// (--kind is accepted for backward compatibility but no longer drives tier selection)
|
||||
// research-store put <key> --content <str> --source <s> --provider <p>
|
||||
// --confidence <c> --kind <k>
|
||||
// -> putResearch(cwd, key, { content, source, provider, confidence, kind })
|
||||
//
|
||||
// Tier is derived from source: 'curated' source writes to process.env.HOME/.gsd/research-cache;
|
||||
// all other sources write to cwd/.planning/research/.cache.
|
||||
// Tests may override the home directory by setting the HOME env var.
|
||||
|
||||
case 'research-store': {
|
||||
const researchStore = require('./lib/research-store.cjs');
|
||||
const subcommand = args[1];
|
||||
const homeDir = process.env.HOME || require('os').homedir();
|
||||
if (subcommand === 'get') {
|
||||
const key = args[2];
|
||||
if (!key || key.startsWith('--')) {
|
||||
error('Usage: gsd-tools research-store get <key> [--kind <k>]', ERROR_REASON.USAGE);
|
||||
}
|
||||
if (!researchStore.isValidResearchKey(key)) {
|
||||
error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
|
||||
}
|
||||
// --kind is accepted but no longer drives tier selection; getResearch searches both tiers
|
||||
const result = researchStore.getResearch(cwd, key, { homeDir });
|
||||
core.output(result, raw);
|
||||
} else if (subcommand === 'put') {
|
||||
const key = args[2];
|
||||
if (!key || key.startsWith('--')) {
|
||||
error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
|
||||
}
|
||||
if (!researchStore.isValidResearchKey(key)) {
|
||||
error('research-store: <key> must be a 64-char sha256 hex (use research-plan to obtain keys)', ERROR_REASON.USAGE);
|
||||
}
|
||||
const contentIdx = args.indexOf('--content');
|
||||
const sourceIdx = args.indexOf('--source');
|
||||
const providerIdx = args.indexOf('--provider');
|
||||
const confidenceIdx = args.indexOf('--confidence');
|
||||
const kindIdx = args.indexOf('--kind');
|
||||
// For each flag, if the following value is missing or itself starts with '--', reject.
|
||||
function getFlagValue(idx, flagName) {
|
||||
if (idx === -1) return null;
|
||||
const val = args[idx + 1];
|
||||
if (val === undefined || val.startsWith('--')) {
|
||||
error(`research-store put: missing value for ${flagName}`, ERROR_REASON.USAGE);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
const content = getFlagValue(contentIdx, '--content');
|
||||
const source = getFlagValue(sourceIdx, '--source');
|
||||
const provider = getFlagValue(providerIdx, '--provider');
|
||||
const confidence = getFlagValue(confidenceIdx, '--confidence');
|
||||
const kind = getFlagValue(kindIdx, '--kind');
|
||||
if (!content || !source || !provider || !confidence || !kind) {
|
||||
error('Usage: gsd-tools research-store put <key> --content <str> --source <s> --provider <p> --confidence <c> --kind <k>', ERROR_REASON.USAGE);
|
||||
}
|
||||
const entry = researchStore.putResearch(cwd, key, { content, source, provider, confidence, kind }, { homeDir });
|
||||
core.output(entry, raw);
|
||||
} else {
|
||||
error('Unknown research-store subcommand. Available: get, put', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ─── Research Plan ─────────────────────────────────────────────────────
|
||||
//
|
||||
// research-plan --input <path>
|
||||
// Read+JSON.parse file; call planResearch({ questions, ecosystem, config, cwd })
|
||||
// { ecosystem, config, questions: [{ text, kind, library?, version? }] }
|
||||
|
||||
case 'research-plan': {
|
||||
const researchProvider = require('./lib/research-provider.cjs');
|
||||
const inputIdx = args.indexOf('--input');
|
||||
const inputPath = inputIdx !== -1 ? args[inputIdx + 1] : null;
|
||||
if (!inputPath || inputPath.startsWith('--')) {
|
||||
error('Usage: gsd-tools research-plan --input <path>', ERROR_REASON.USAGE);
|
||||
}
|
||||
let planInput;
|
||||
try {
|
||||
const raw_ = fs.readFileSync(path.resolve(inputPath), 'utf8');
|
||||
planInput = JSON.parse(raw_);
|
||||
} catch (readErr) {
|
||||
error(`research-plan: cannot read/parse --input file: ${inputPath}`, ERROR_REASON.USAGE);
|
||||
}
|
||||
if (planInput === null || typeof planInput !== 'object' || Array.isArray(planInput)) {
|
||||
error('research-plan: --input must be an object with a questions array', ERROR_REASON.USAGE);
|
||||
}
|
||||
if (!Array.isArray(planInput.questions)) {
|
||||
error('research-plan: --input must be an object with a questions array', ERROR_REASON.USAGE);
|
||||
}
|
||||
const { ecosystem = '', config: planConfig = {}, questions } = planInput;
|
||||
const homeDir = process.env.HOME || require('os').homedir();
|
||||
const plan = researchProvider.planResearch({ questions, ecosystem, config: planConfig, cwd, homeDir });
|
||||
core.output(plan, raw);
|
||||
break;
|
||||
}
|
||||
|
||||
// ─── Classify Confidence ──────────────────────────────────────────────
|
||||
//
|
||||
// classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]
|
||||
// -> classifyConfidence({ provider, verifiedAgainstOfficial, legitimacyVerdict }); core.output(result, raw)
|
||||
//
|
||||
// legitimacyVerdict is CODE-COMPUTED via checkPackages — never caller-supplied — so an agent cannot self-assert OK→HIGH.
|
||||
|
||||
case 'classify-confidence': {
|
||||
const researchProvider = require('./lib/research-provider.cjs');
|
||||
const providerIdx = args.indexOf('--provider');
|
||||
const provider = providerIdx !== -1 ? args[providerIdx + 1] : null;
|
||||
if (!provider || provider.startsWith('--')) {
|
||||
error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
|
||||
}
|
||||
const verified = args.includes('--verified');
|
||||
const pkgIdx = args.indexOf('--package');
|
||||
const pkg = pkgIdx !== -1 ? args[pkgIdx + 1] : null;
|
||||
const ecoIdx = args.indexOf('--ecosystem');
|
||||
const ecosystem = ecoIdx !== -1 ? args[ecoIdx + 1] : null;
|
||||
let legitimacyVerdict = null;
|
||||
if (pkg && (!pkg.startsWith('--'))) {
|
||||
const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
|
||||
if (!ecosystem || ecosystem.startsWith('--') || !VALID_ECOSYSTEMS.has(ecosystem)) {
|
||||
error('Usage: gsd-tools query classify-confidence --provider <id> [--package <name> --ecosystem <npm|pypi|crates>] [--verified]', ERROR_REASON.USAGE);
|
||||
}
|
||||
const pkgLegitimacy = require('./lib/package-legitimacy.cjs');
|
||||
const results = await pkgLegitimacy.checkPackages({ ecosystem, packages: [pkg] }, {});
|
||||
legitimacyVerdict = results[0] ? results[0].verdict : null;
|
||||
}
|
||||
const confidence = researchProvider.classifyConfidence({ provider, verifiedAgainstOfficial: verified, legitimacyVerdict });
|
||||
core.output({ provider, package: pkg || null, ecosystem: ecosystem || null, legitimacyVerdict, verified, confidence }, raw);
|
||||
break;
|
||||
}
|
||||
|
||||
// ─── Package Legitimacy ────────────────────────────────────────────────
|
||||
//
|
||||
// package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...
|
||||
//
|
||||
// checkPackages is ASYNC. This entire runCommand function is async, so
|
||||
// we can await directly. On rejection we call error() which exits.
|
||||
|
||||
case 'package-legitimacy': {
|
||||
const pkgLegitimacy = require('./lib/package-legitimacy.cjs');
|
||||
const subcommand = args[1];
|
||||
if (subcommand !== 'check') {
|
||||
error('Unknown package-legitimacy subcommand. Available: check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
||||
}
|
||||
const ecoIdx = args.indexOf('--ecosystem');
|
||||
const ecosystem = ecoIdx !== -1 ? args[ecoIdx + 1] : null;
|
||||
const VALID_ECOSYSTEMS = new Set(['npm', 'pypi', 'crates']);
|
||||
if (!ecosystem || !VALID_ECOSYSTEMS.has(ecosystem)) {
|
||||
error('Usage: gsd-tools package-legitimacy check --ecosystem <npm|pypi|crates> <pkg1> ...', ERROR_REASON.USAGE);
|
||||
}
|
||||
// Collect positional package names.
|
||||
// Only --ecosystem takes a value. Every non-flag arg is a package name.
|
||||
// Any unknown --flag is a usage error (do not silently skip+consume the next arg).
|
||||
const packages = [];
|
||||
for (let i = 2; i < args.length; i++) {
|
||||
const a = args[i];
|
||||
if (a === '--ecosystem') { i++; continue; }
|
||||
if (a.startsWith('--')) {
|
||||
error(`package-legitimacy: unknown flag ${a}`, ERROR_REASON.USAGE);
|
||||
}
|
||||
packages.push(a);
|
||||
}
|
||||
if (packages.length === 0) {
|
||||
error('Usage: gsd-tools package-legitimacy check --ecosystem <eco> <pkg1> <pkg2> ...', ERROR_REASON.USAGE);
|
||||
}
|
||||
let pkgResults;
|
||||
try {
|
||||
pkgResults = await pkgLegitimacy.checkPackages({ ecosystem, packages }, {});
|
||||
} catch (pkgErr) {
|
||||
error(`package-legitimacy: ${pkgErr && pkgErr.message ? pkgErr.message : String(pkgErr)}`, ERROR_REASON.UNKNOWN);
|
||||
}
|
||||
core.output(pkgResults, raw);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'effort': {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'sync') {
|
||||
|
||||
29
gsd-core/references/research-documentation-lookup.md
Normal file
29
gsd-core/references/research-documentation-lookup.md
Normal file
@@ -0,0 +1,29 @@
|
||||
When you need library or framework documentation, check in this order:
|
||||
|
||||
1. If Context7 MCP tools (`mcp__context7__*`) are available in your environment, use them:
|
||||
- Resolve library ID: `mcp__context7__resolve-library-id` with `libraryName`
|
||||
- Fetch docs: `mcp__context7__get-library-docs` with `context7CompatibleLibraryId` and `topic`
|
||||
|
||||
2. If Context7 MCP is not available (upstream bug anthropics/claude-code#13898 strips MCP
|
||||
tools from agents with a `tools:` frontmatter restriction), use the CLI fallback via Bash:
|
||||
|
||||
Step 1 — Resolve library ID:
|
||||
```bash
|
||||
if command -v ctx7 &>/dev/null; then
|
||||
ctx7 library <name> "<query>"
|
||||
else
|
||||
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
|
||||
fi
|
||||
```
|
||||
Step 2 — Fetch documentation:
|
||||
```bash
|
||||
if command -v ctx7 &>/dev/null; then
|
||||
ctx7 docs <libraryId> "<query>"
|
||||
else
|
||||
echo "ctx7 not found — install with: npm install -g ctx7 (verify at npmjs.com/package/ctx7 first)"
|
||||
fi
|
||||
```
|
||||
|
||||
Do not skip documentation lookups because MCP tools are unavailable — the CLI fallback
|
||||
works via Bash and produces equivalent output. Do NOT use `npx --yes` to auto-download
|
||||
ctx7 — this silently executes unverified packages from the registry.
|
||||
29
gsd-core/references/research-philosophy.md
Normal file
29
gsd-core/references/research-philosophy.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Claude's Training as Hypothesis
|
||||
|
||||
Training data is 6-18 months stale. Treat pre-existing knowledge as hypothesis, not fact.
|
||||
|
||||
**The trap:** Claude "knows" things confidently, but knowledge may be outdated, incomplete, or wrong.
|
||||
|
||||
**The discipline:**
|
||||
1. **Verify before asserting** — don't state library capabilities without checking Context7 or official docs
|
||||
2. **Date your knowledge** — "As of my training" is a warning flag
|
||||
3. **Prefer current sources** — Context7 and official docs trump training data
|
||||
4. **Flag uncertainty** — LOW confidence when only training data supports a claim
|
||||
|
||||
## Honest Reporting
|
||||
|
||||
Research value comes from accuracy, not completeness theater.
|
||||
|
||||
**Report honestly:**
|
||||
- "I couldn't find X" is valuable (now we know to investigate differently)
|
||||
- "This is LOW confidence" is valuable (flags for validation)
|
||||
- "Sources contradict" is valuable (surfaces real ambiguity)
|
||||
|
||||
**Avoid:** Padding findings, stating unverified claims as facts, hiding uncertainty behind confident language.
|
||||
|
||||
## Research is Investigation, Not Confirmation
|
||||
|
||||
**Bad research:** Start with hypothesis, find evidence to support it
|
||||
**Good research:** Gather evidence, form conclusions from evidence
|
||||
|
||||
When researching "best library for X": find what the ecosystem actually uses, document tradeoffs honestly, let evidence drive recommendation.
|
||||
27
gsd-core/references/research-verification-protocol.md
Normal file
27
gsd-core/references/research-verification-protocol.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Known Pitfalls
|
||||
|
||||
### Configuration Scope Blindness
|
||||
**Trap:** Assuming global configuration means no project-scoping exists
|
||||
**Prevention:** Verify ALL configuration scopes (global, project, local, workspace)
|
||||
|
||||
### Deprecated Features
|
||||
**Trap:** Finding old documentation and concluding feature doesn't exist
|
||||
**Prevention:** Check current official docs, review changelog, verify version numbers and dates
|
||||
|
||||
### Negative Claims Without Evidence
|
||||
**Trap:** Making definitive "X is not possible" statements without official verification
|
||||
**Prevention:** For any negative claim — is it verified by official docs? Have you checked recent updates? Are you confusing "didn't find it" with "doesn't exist"?
|
||||
|
||||
### Single Source Reliance
|
||||
**Trap:** Relying on a single source for critical claims
|
||||
**Prevention:** Require multiple sources: official docs (primary), release notes (currency), additional source (verification)
|
||||
|
||||
## Pre-Submission Checklist
|
||||
|
||||
- [ ] All research domains in this agent's scope investigated (e.g. stack, features, architecture, patterns, pitfalls — whichever apply)
|
||||
- [ ] Negative claims verified with official docs
|
||||
- [ ] Multiple sources cross-referenced for critical claims
|
||||
- [ ] URLs provided for authoritative sources
|
||||
- [ ] Publication dates checked (prefer recent/current)
|
||||
- [ ] Confidence levels assigned honestly
|
||||
- [ ] "What might I have missed?" review completed
|
||||
Reference in New Issue
Block a user