Session-start code-intelligence indexing offer for large repos (v1.62.0.0)#23
Conversation
shouldOfferIndexing() offers indexing at most once per machine: only for a large repo (tracked-file count >= 1000), never after a provider selection or an explicit decline. `select none` now persists the decline; the new `gstack-code-intelligence suggest [path] [--json]` command emits the verdict plus the provider options with reasons for the AskUserQuestion flow. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…face Adds the launcher to DEFAULT_RUNTIME_HELPERS and lib/code-intelligence to the helper dependency closure, so standard installs can run the session-start indexing offer from $GSTACK_HOME/bin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… repos Every dispatcher now reads the generated references/CODE-INTELLIGENCE.md once per repository invocation: run `gstack-code-intelligence suggest` (silent skip when unavailable), and only when it reports a large repo with no prior decision, ask the user whether to index — GBrain (semantic, consent-gated), Sourcebot (self-hosted search), Graphify (local graph, never auto-installed), or No indexing (persisted, never re-asked). Parity pins the silent-degrade, decline, and no-auto-install behavior per tree (5027 -> 5045 checks); tree regenerated via gen:gstack2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # evals/parity/transcripts/policy-units.json
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
There was a problem hiding this comment.
11 issues found across 25 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/gstack2/generate-skill-tree.ts">
<violation number="1" location="scripts/gstack2/generate-skill-tree.ts:522">
P2: A large-repository session can pause for up to 30 seconds before specialist work starts because the newly mandatory offer command performs a live Sourcebot probe with a 30-second timeout. A short, non-blocking availability probe (or skipping live probes in the gate and checking availability only after the user chooses a provider) would preserve the offer without making startup depend on provider reachability.</violation>
<violation number="2" location="scripts/gstack2/generate-skill-tree.ts:522">
P2: The generated session-start instruction conflicts with the dispatcher’s existing authority policy by requiring shell redirection and `|| true`. This can make the offer gate non-compliant or prevent agents from executing it reliably; the failure handling would be clearer as a direct invocation with a silent-failure rule handled by the agent.</violation>
<violation number="3" location="scripts/gstack2/generate-skill-tree.ts:522">
P1: Repository targets outside the process working directory get the wrong offer decision because `suggest` receives no target path and defaults to `process.cwd()`. Pass the resolved Target repository path so a large requested repo is not silently skipped—or a different cwd repo offered instead.</violation>
<violation number="4" location="scripts/gstack2/generate-skill-tree.ts:531">
P2: Accepting indexing can block specialist work even though this contract promises it never will: the `index` CLI waits for synchronous provider refreshes before returning. The flow should defer indexing or use a genuinely asynchronous launcher/status path before directing the dispatcher to proceed with grep.</violation>
</file>
<file name="CHANGELOG.md">
<violation number="1" location="CHANGELOG.md:19">
P3: The user-facing release entry exposes internal parity/eval and runtime implementation details in the summary and itemized sections. Replacing these with observable indexing behavior would keep the changelog within its documented audience and content rules.</violation>
<violation number="2" location="CHANGELOG.md:21">
P3: The release metrics omit the required `Δ` column, so this entry does not follow the repository's changelog format. Adding the column also makes the +18 parity checks and +6 tests immediately visible.</violation>
</file>
<file name="scripts/gstack2/run-parity.ts">
<violation number="1" location="scripts/gstack2/run-parity.ts:173">
P3: Late or repeated indexing offers can pass this parity check because `includes` verifies only that the reference appears somewhere. Consider asserting the reference's ordering and single-load form in each dispatcher.</violation>
<violation number="2" location="scripts/gstack2/run-parity.ts:178">
P3: A contract that displays “No indexing” but never records the choice still passes this purported decline regression. Consider pinning the `select none` command and the persisted/no-repeat semantics instead of only the option label.</violation>
</file>
<file name="runtime/install.js">
<violation number="1" location="runtime/install.js:101">
P3: The new stable helper is installed but remains absent from the audited runtime-helper closure, so parity cannot verify its source path or consumers. Consider extending `runtimeHelperClosure` to include generated `CODE-INTELLIGENCE.md` consumers and adding a reverse assertion that every `DEFAULT_RUNTIME_HELPERS` entry is declared.</violation>
</file>
<file name="lib/code-intelligence/suggest.ts">
<violation number="1" location="lib/code-intelligence/suggest.ts:34">
P1: Invoking a skill from a nested directory can suppress the indexing offer because `git -C <subdir> ls-files` counts only files below that directory, not the whole repository. Use a top-level pathspec such as `-- :/` or resolve the Git root before counting.</violation>
<violation number="2" location="lib/code-intelligence/suggest.ts:36">
P2: Repositories whose tracked-path output exceeds 64 MiB are silently classified as `not-a-repo`, so very large repos can miss the offer. Count a compact constant-size record per file or stream the output instead of buffering all path names under a fixed cap.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| '', | ||
| 'gstack works fully without an index: grep and the file-only decision store are the default and never depend on a provider. Indexing is an optional enhancement that pays off in large repositories.', | ||
| '', | ||
| 'Once per invocation, before substantive specialist work inside a repository, run `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json 2>/dev/null || true`. If the helper is unavailable or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.', |
There was a problem hiding this comment.
P1: Repository targets outside the process working directory get the wrong offer decision because suggest receives no target path and defaults to process.cwd(). Pass the resolved Target repository path so a large requested repo is not silently skipped—or a different cwd repo offered instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gstack2/generate-skill-tree.ts, line 522:
<comment>Repository targets outside the process working directory get the wrong offer decision because `suggest` receives no target path and defaults to `process.cwd()`. Pass the resolved Target repository path so a large requested repo is not silently skipped—or a different cwd repo offered instead.</comment>
<file context>
@@ -513,6 +513,26 @@ function webContextContract(): string {
+ '',
+ 'gstack works fully without an index: grep and the file-only decision store are the default and never depend on a provider. Indexing is an optional enhancement that pays off in large repositories.',
+ '',
+ 'Once per invocation, before substantive specialist work inside a repository, run `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json 2>/dev/null || true`. If the helper is unavailable or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.',
+ '',
+ 'When the result says `offer: true`, pause and ask via AskUserQuestion whether to index the repository, presenting exactly these options with these reasons and each option\'s availability from the suggest output:',
</file context>
|
|
||
| /** Count of git-tracked files, or null when the path is not a git repo. */ | ||
| export function trackedFileCount(repoPath: string): number | null { | ||
| const result = spawnSync("git", ["-C", resolve(repoPath), "ls-files"], { |
There was a problem hiding this comment.
P1: Invoking a skill from a nested directory can suppress the indexing offer because git -C <subdir> ls-files counts only files below that directory, not the whole repository. Use a top-level pathspec such as -- :/ or resolve the Git root before counting.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/code-intelligence/suggest.ts, line 34:
<comment>Invoking a skill from a nested directory can suppress the indexing offer because `git -C <subdir> ls-files` counts only files below that directory, not the whole repository. Use a top-level pathspec such as `-- :/` or resolve the Git root before counting.</comment>
<file context>
@@ -0,0 +1,55 @@
+
+/** Count of git-tracked files, or null when the path is not a git repo. */
+export function trackedFileCount(repoPath: string): number | null {
+ const result = spawnSync("git", ["-C", resolve(repoPath), "ls-files"], {
+ encoding: "utf-8",
+ maxBuffer: 64 * 1024 * 1024,
</file context>
| '- **Graphify**: local tree-sitter code graph (definitions, references, structure); nothing ever leaves the machine and no consent is needed, but you install it yourself — it is never auto-installed.', | ||
| '- **No indexing**: gstack continues with grep and file-only state, fully supported. This choice is remembered and the question is not asked again.', | ||
| '', | ||
| 'Persist only the explicit choice: `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence select <gbrain|sourcebot|graphify|none>`. For a non-local provider, run `consent` for this repository only after the user explicitly approves sending its content off-machine, then `index`. Never infer a choice, never auto-install a provider, never treat an unavailable provider as forbidden (the user may set it up later), and never block or delay specialist work on indexing: if the user declines or the index is still building, proceed with grep.', |
There was a problem hiding this comment.
P2: Accepting indexing can block specialist work even though this contract promises it never will: the index CLI waits for synchronous provider refreshes before returning. The flow should defer indexing or use a genuinely asynchronous launcher/status path before directing the dispatcher to proceed with grep.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gstack2/generate-skill-tree.ts, line 531:
<comment>Accepting indexing can block specialist work even though this contract promises it never will: the `index` CLI waits for synchronous provider refreshes before returning. The flow should defer indexing or use a genuinely asynchronous launcher/status path before directing the dispatcher to proceed with grep.</comment>
<file context>
@@ -513,6 +513,26 @@ function webContextContract(): string {
+ '- **Graphify**: local tree-sitter code graph (definitions, references, structure); nothing ever leaves the machine and no consent is needed, but you install it yourself — it is never auto-installed.',
+ '- **No indexing**: gstack continues with grep and file-only state, fully supported. This choice is remembered and the question is not asked again.',
+ '',
+ 'Persist only the explicit choice: `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence select <gbrain|sourcebot|graphify|none>`. For a non-local provider, run `consent` for this repository only after the user explicitly approves sending its content off-machine, then `index`. Never infer a choice, never auto-install a provider, never treat an unavailable provider as forbidden (the user may set it up later), and never block or delay specialist work on indexing: if the user declines or the index is still building, proceed with grep.',
+ '',
+ ].join('\n');
</file context>
| export function trackedFileCount(repoPath: string): number | null { | ||
| const result = spawnSync("git", ["-C", resolve(repoPath), "ls-files"], { | ||
| encoding: "utf-8", | ||
| maxBuffer: 64 * 1024 * 1024, |
There was a problem hiding this comment.
P2: Repositories whose tracked-path output exceeds 64 MiB are silently classified as not-a-repo, so very large repos can miss the offer. Count a compact constant-size record per file or stream the output instead of buffering all path names under a fixed cap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/code-intelligence/suggest.ts, line 36:
<comment>Repositories whose tracked-path output exceeds 64 MiB are silently classified as `not-a-repo`, so very large repos can miss the offer. Count a compact constant-size record per file or stream the output instead of buffering all path names under a fixed cap.</comment>
<file context>
@@ -0,0 +1,55 @@
+export function trackedFileCount(repoPath: string): number | null {
+ const result = spawnSync("git", ["-C", resolve(repoPath), "ls-files"], {
+ encoding: "utf-8",
+ maxBuffer: 64 * 1024 * 1024,
+ });
+ if (result.status !== 0 || typeof result.stdout !== "string") return null;
</file context>
| '', | ||
| 'gstack works fully without an index: grep and the file-only decision store are the default and never depend on a provider. Indexing is an optional enhancement that pays off in large repositories.', | ||
| '', | ||
| 'Once per invocation, before substantive specialist work inside a repository, run `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json 2>/dev/null || true`. If the helper is unavailable or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.', |
There was a problem hiding this comment.
P2: The generated session-start instruction conflicts with the dispatcher’s existing authority policy by requiring shell redirection and || true. This can make the offer gate non-compliant or prevent agents from executing it reliably; the failure handling would be clearer as a direct invocation with a silent-failure rule handled by the agent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gstack2/generate-skill-tree.ts, line 522:
<comment>The generated session-start instruction conflicts with the dispatcher’s existing authority policy by requiring shell redirection and `|| true`. This can make the offer gate non-compliant or prevent agents from executing it reliably; the failure handling would be clearer as a direct invocation with a silent-failure rule handled by the agent.</comment>
<file context>
@@ -513,6 +513,26 @@ function webContextContract(): string {
+ '',
+ 'gstack works fully without an index: grep and the file-only decision store are the default and never depend on a provider. Indexing is an optional enhancement that pays off in large repositories.',
+ '',
+ 'Once per invocation, before substantive specialist work inside a repository, run `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json 2>/dev/null || true`. If the helper is unavailable or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.',
+ '',
+ 'When the result says `offer: true`, pause and ask via AskUserQuestion whether to index the repository, presenting exactly these options with these reasons and each option\'s availability from the suggest output:',
</file context>
| 'Once per invocation, before substantive specialist work inside a repository, run `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json 2>/dev/null || true`. If the helper is unavailable or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.', | |
| 'Once per invocation, before substantive specialist work inside a repository, invoke `"${GSTACK_HOME:-$HOME/.gstack}"/bin/gstack-code-intelligence suggest --json` directly. If the helper is unavailable, the invocation fails, or the result says `offer: false`, continue silently; pure judgment never requires it and the user is never nagged. The helper offers only for a large repository (tracked-file count at or above its threshold) with no prior decision, and an explicit decline is persisted so the question is never repeated.', |
| | Metric | Before | After | | ||
| |---|---|---| | ||
| | Skills that surface the indexing option | 0 | all 6 dispatchers | | ||
| | Times the question is asked per machine | n/a | at most 1 | | ||
| | Large-repo threshold | n/a | 1,000 tracked files | | ||
| | Parity checks pinning the behavior | 5,027 | 5,045 | | ||
| | code-intelligence tests | 25 | 31 | |
There was a problem hiding this comment.
P3: The release metrics omit the required Δ column, so this entry does not follow the repository's changelog format. Adding the column also makes the +18 parity checks and +6 tests immediately visible.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CHANGELOG.md, line 21:
<comment>The release metrics omit the required `Δ` column, so this entry does not follow the repository's changelog format. Adding the column also makes the +18 parity checks and +6 tests immediately visible.</comment>
<file context>
@@ -7,6 +7,43 @@
+
+Source: `bun run scripts/gstack2/run-parity.ts` and `bun test test/code-intelligence.test.ts` on this release.
+
+| Metric | Before | After |
+|---|---|---|
+| Skills that surface the indexing option | 0 | all 6 dispatchers |
</file context>
| | Metric | Before | After | | |
| |---|---|---| | |
| | Skills that surface the indexing option | 0 | all 6 dispatchers | | |
| | Times the question is asked per machine | n/a | at most 1 | | |
| | Large-repo threshold | n/a | 1,000 tracked files | | |
| | Parity checks pinning the behavior | 5,027 | 5,045 | | |
| | code-intelligence tests | 25 | 31 | | |
| | Metric | Before | After | Δ | | |
| |---|---|---|---| | |
| | Skills that surface the indexing option | 0 | all 6 dispatchers | +6 dispatchers | | |
| | Times the question is asked per machine | n/a | at most 1 | new | | |
| | Large-repo threshold | n/a | 1,000 tracked files | new | | |
| | Parity checks pinning the behavior | 5,027 | 5,045 | +18 | | |
| | code-intelligence tests | 25 | 31 | +6 | |
| const offer = fs.readFileSync(codeIntel, 'utf8'); | ||
| check( | ||
| offer.includes('offer: false`, continue silently') | ||
| && offer.includes('No indexing') |
There was a problem hiding this comment.
P3: A contract that displays “No indexing” but never records the choice still passes this purported decline regression. Consider pinning the select none command and the persisted/no-repeat semantics instead of only the option label.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gstack2/run-parity.ts, line 178:
<comment>A contract that displays “No indexing” but never records the choice still passes this purported decline regression. Consider pinning the `select none` command and the persisted/no-repeat semantics instead of only the option label.</comment>
<file context>
@@ -168,6 +168,18 @@ export function runParity(): ParityResult {
+ const offer = fs.readFileSync(codeIntel, 'utf8');
+ check(
+ offer.includes('offer: false`, continue silently')
+ && offer.includes('No indexing')
+ && offer.includes('never auto-install a provider'),
+ `${tree} code-intelligence offer lost its silent-degrade, decline, or no-auto-install behavior`,
</file context>
| check(dispatcher.includes('references/AUTHORITY-POLICY.md'), `${tree} dispatcher does not load the authority/evidence policy`); | ||
| const codeIntel = path.join(ROOT, 'skills', tree, 'references', 'CODE-INTELLIGENCE.md'); | ||
| check(fs.existsSync(codeIntel), `${tree} lacks the optional code-intelligence offer contract`); | ||
| check(dispatcher.includes('references/CODE-INTELLIGENCE.md'), `${tree} dispatcher does not load the code-intelligence offer`); |
There was a problem hiding this comment.
P3: Late or repeated indexing offers can pass this parity check because includes verifies only that the reference appears somewhere. Consider asserting the reference's ordering and single-load form in each dispatcher.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/gstack2/run-parity.ts, line 173:
<comment>Late or repeated indexing offers can pass this parity check because `includes` verifies only that the reference appears somewhere. Consider asserting the reference's ordering and single-load form in each dispatcher.</comment>
<file context>
@@ -168,6 +168,18 @@ export function runParity(): ParityResult {
check(dispatcher.includes('references/AUTHORITY-POLICY.md'), `${tree} dispatcher does not load the authority/evidence policy`);
+ const codeIntel = path.join(ROOT, 'skills', tree, 'references', 'CODE-INTELLIGENCE.md');
+ check(fs.existsSync(codeIntel), `${tree} lacks the optional code-intelligence offer contract`);
+ check(dispatcher.includes('references/CODE-INTELLIGENCE.md'), `${tree} dispatcher does not load the code-intelligence offer`);
+ if (fs.existsSync(codeIntel)) {
+ const offer = fs.readFileSync(codeIntel, 'utf8');
</file context>
| "gstack-brain-cache": helper("bin/gstack-brain-cache"), | ||
| "gstack-brain-sync": helper("bin/gstack-brain-sync"), | ||
| "gstack-builder-profile": helper("bin/gstack-builder-profile"), | ||
| "gstack-code-intelligence": helper("bin/gstack-code-intelligence"), |
There was a problem hiding this comment.
P3: The new stable helper is installed but remains absent from the audited runtime-helper closure, so parity cannot verify its source path or consumers. Consider extending runtimeHelperClosure to include generated CODE-INTELLIGENCE.md consumers and adding a reverse assertion that every DEFAULT_RUNTIME_HELPERS entry is declared.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At runtime/install.js, line 101:
<comment>The new stable helper is installed but remains absent from the audited runtime-helper closure, so parity cannot verify its source path or consumers. Consider extending `runtimeHelperClosure` to include generated `CODE-INTELLIGENCE.md` consumers and adding a reverse assertion that every `DEFAULT_RUNTIME_HELPERS` entry is declared.</comment>
<file context>
@@ -98,6 +98,7 @@ export const DEFAULT_RUNTIME_HELPERS = Object.freeze({
"gstack-brain-cache": helper("bin/gstack-brain-cache"),
"gstack-brain-sync": helper("bin/gstack-brain-sync"),
"gstack-builder-profile": helper("bin/gstack-builder-profile"),
+ "gstack-code-intelligence": helper("bin/gstack-code-intelligence"),
"gstack-codex-probe": helper("bin/gstack-codex-probe"),
"gstack-config": helper("bin/gstack-config"),
</file context>
# Conflicts: # CHANGELOG.md # VERSION # docs/gstack-2/JUDGMENT-PARITY.md # scripts/gstack2/run-parity.ts
# Conflicts: # package.json
What
Every dispatcher now detects a large codebase at the start of work in a repository and makes a one-time offer to index it, letting the user pick a provider with the reasons for each (Phase 3 of the code-intelligence provider contract).
gstack-code-intelligence suggest [path] [--json]is the offer gate: fires only for a git repo with >= 1,000 tracked files AND no provider selected AND no prior decline. Missing helper / small repo / non-repo are silent skips.references/CODE-INTELLIGENCE.mdper tree (mirrors the WEB-CONTEXT/Context.dev consent pattern); dispatch protocol reads it once per repository invocation.select nonerecords the decline so the question is never repeated).gstack-code-intelligenceships in the managed runtime helper surface so standard installs have the launcher at$GSTACK_HOME/bin.Boundaries kept
No provider selected by default; grep / file-only decision store remain the always-working path; consent gating unchanged; no auto-install; specialist work never blocks on indexing.
Evidence
bun testgreen (includes 31 code-intelligence tests, 6 new).bun run test:gstack2green; parity 5,027 -> 5,045 checks (per-tree pins for existence, dispatcher wiring, silent-degrade, decline, no-auto-install).GSTACK_HOME.🤖 Generated with Claude Code
Summary by cubic
Adds a one-time, session-start offer to index large repositories across all skills, powered by a new
gstack-code-intelligence suggestgate and a per-treeCODE-INTELLIGENCEcontract; declines are remembered so you’re not asked again. The helper now ships in the managed runtime.gstack-code-intelligence suggest [path] [--json]emits offer/no-offer and live provider options;select nonepersists a decline; selecting a provider clears it.references/CODE-INTELLIGENCE.mdonce per repo to present GBrain, Sourcebot, Graphify, or No indexing with reasons; no auto-install, and work never blocks on indexing.gstack-code-intelligenceis included in the runtime at$GSTACK_HOME/bin.Written for commit 4c62edd. Summary will update on new commits.