diff --git a/CHANGELOG.md b/CHANGELOG.md index 548e1c9f3..efeb4580d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,7 +92,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - **Your earlier agent sessions are searchable: `codegraph sessions` and the `codegraph_sessions` tool.** A code graph answers "how does X work"; it cannot answer "why is X like this" or "what did the last session decide about Y" — that history lives in the transcripts the agent already wrote, hundreds of megabytes of JSONL nobody greps. CodeGraph now indexes the prose of a project's Claude Code sessions (`~/.claude/projects//`: prompts, replies and compaction summaries; tool calls, results and thinking stay out) into an FTS5 table with porter stemming and BM25 rank, in its own `.codegraph/sessions.db` beside the graph. The index refreshes on each call for files whose size or mtime moved, so a query after one live session costs tens of milliseconds and the first index of a few hundred transcripts about a second. A hit names its session id, title, role, time and the matching passage; `role`, `sinceDays`, `session` (an id prefix) and `any` (OR the words) narrow or widen it. The tool joins `codegraph_explore` in the default MCP surface — a different question over a different corpus, so it cannot steer a mis-pick against explore — and the CLI command prints the same text for subagents without MCP. `"sessions": false` in `codegraph.json` opts a project out; `CODEGRAPH_SESSIONS_DIR` points at another transcript directory. Readers are one module per agent host, Claude Code first. -- **Markdown is indexed, and a documentation question gets the section, not the graph.** Every `.md` file's headings, sections, tables and links are nodes (the extractor from #361), and a doc-shaped `codegraph_explore` query that names a markdown file now renders that file's best sections first and whole — the top three by idf-weighted line hits, a heading the query covers word for word counted as named, 8k characters per file — with the blast-radius, relationships and "additional files" blocks held back unless a code file rendered too. Measured on a 109-file docs corpus under headless Claude Code, 36 cells over three rounds: the right file and section in every call, median 1 tool call against 4 for Grep-then-Read, 36 of 36 correct. Code answers keep their shape: markdown nodes leave a subgraph the doc tier did not seed, a markdown body is never mistaken for a generated-file header, and the explore budget tiers count code files only, so a README-heavy repo does not cross a breakpoint. The server instructions say markdown is indexed, which the branch's own text still denied. (#361, #1439) +- **Markdown is indexed, and a documentation question gets the section, not the graph.** Every `.md` file's headings, sections and links are nodes, with tables, commands and list items kept in the section's searchable text (the extractor from #361), and a doc-shaped `codegraph_explore` query that names a markdown file now renders that file's best sections first and whole — the top three by idf-weighted line hits, a heading the query covers word for word counted as named, 8k characters per file — with the blast-radius, relationships and "additional files" blocks held back unless a code file rendered too. Measured on a 109-file docs corpus under headless Claude Code, 36 cells over three rounds: the right file and section in every call, median 1 tool call against 4 for Grep-then-Read, 36 of 36 correct. Code answers keep their shape: markdown nodes leave a subgraph the doc tier did not seed, a markdown body is never mistaken for a generated-file header, and the explore budget tiers count code files only, so a README-heavy repo does not cross a breakpoint. The server instructions say markdown is indexed, which the branch's own text still denied. (#361, #1439) - Installers can start or reuse a checkout's active watcher without replacing existing writers, and MCP launchers can preserve existing daemons across reconnects with `--preserve-existing`. @@ -102,6 +102,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - **A documentation link to a file that is not there no longer gets its own graph node.** A Markdown link or path mention whose target basename no indexed file has minted an import node and a `contains` edge, even though the resolver's file-path arm could not match it. On supabase a full index now skips 10,394 of 14,038 Markdown import nodes and their `contains` edges. The reference is always kept, so the resolver's route, module and table arms still match one (a relative `api/users.json` link included), `codegraph sync` can resolve a target added later from the parked failed reference, and a skipped target stays in a separate `links:` part of the file digest so it remains searchable past the 12-item display digest. Resolved Markdown edges are byte-identical on supabase (3,058), n8n, pretix and discourse, and the failed Markdown reference count is unchanged (11,368 on supabase). A full supabase index is a median 16.176 s to 15.953 s (Hyperfine 2.0.0, 8 runs per build, two ABBA rounds, 0.223 s/1.4%, interquartile ranges not overlapping). Re-index Markdown-heavy projects after upgrading. +- **A Markdown table row, list item or fenced command is section text, not its own node.** Each row created a `constant` or `function` node plus a `contains` edge even though the text already lived in the section's docstring and searchable text. A full index now keeps those rows in the section, attaches their references to it (a script a command runs keeps its `calls` edge), and appends a short `commands:` summary so a command past the docstring's 600-character cap stays searchable. An unlabeled fence no longer turns output, trees or JSON fragments into commands; a labeled shell fence keeps every line. Text the 600-character section preview drops — late rows, table headers, commands past the 8-command summary, and unrecognized fence lines — is kept on the owning node's new `search_text` column (indexed by the full-text table, not displayed), so a search hit still lands on the section that owns the line. An unlabeled fence recognizes every interpreter `extractScriptPath` supports (`node`, `powershell`, ...). On supabase that removes 6,222 of 27,380 Markdown nodes and 6,247 edges; index time shows no clear change (15.969 s against 16.047 s median, Hyperfine 2.0.0, 8 runs per build, two ABBA rounds, ranges overlapping). n8n drops 3,719 nodes and discourse 869. References that had resolved to a removed row/command node now fail instead of pointing at it (25 on supabase, 3 on n8n, 1 on discourse); references from a row or command move to its section. Doc search still returns the section for a command or table query, and the README, the languages reference and the MCP instructions now describe the section-text model. Re-index Markdown-heavy projects after upgrading. + - **`codegraph sessions` and `codegraph_sessions` can search history before a date.** A search could only look back a number of days (`--since`, `sinceDays`), so its best 100 hits always came from the newest matching sessions and older context could not be reached. `--until ` and `until` keep only docs before a date (read as UTC) or a timestamp that names its zone; together with `--since` they bound a window. - A background sync after editing a widely imported file no longer leaves the MCP daemon holding about 19 GB (n8n, one new export in a barrel imported from 335 files). Re-checking the importers built a native resolver per binding and the process kept about 2.5 MB of each; they now share one. That check went from 38.7 s to 0.5 s, and idle memory after the edit from 19.4 GB to 1.4 GB. diff --git a/README.md b/README.md index ae9cce436..c96121653 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Compared with upstream `main` at `cd04b21a` (after v1.6.2), re-checked against t | Feature | Upstream | Fork | What it does | |---|:-:|:-:|---| | Session search (`codegraph sessions`, `codegraph_sessions`) | — | ✓ | Searches this project's earlier Claude Code, Codex, Cursor/T3, OpenCode, AGY, Devin and Grok transcripts and its git commit messages, so an agent can find what a past session decided. `codegraph_explore` also names the sessions that mentioned the symbols it returns. `"sessions": false` in `codegraph.json` turns it off. Experimental: `projects` / `--project` searches up to five projects' histories in one call. | -| Markdown indexing | — | ✓ | Headings, sections, tables and links become graph nodes; a documentation question gets the matching section. Explicit code-file pins remain eligible in mixed documentation/code requests. | +| Markdown indexing | — | ✓ | Headings, sections and local links become graph nodes, with tables, commands and list items kept in the section's searchable text; a documentation question gets the matching section. Explicit code-file pins remain eligible in mixed documentation/code requests. | | Missing names reported | Reports them | Reports them, and proves absence | Upstream's note says a requested name is not in the index and the defining file may be unindexed. The fork keeps that note and additionally lists a name only when a bounded (300 ms) scan of every hand-written source file — plus file stems and bindings — completes without finding it as any word, so a listed name is absent from the project outright, not just unindexed. An incomplete scan lists nothing rather than guess. | | Requested-source limits reported | — | ✓ | Explore reports requested files that did not receive pin priority within the file limit, unexamined path-like references beyond its bounded scan, and uncovered indexed continuation ranges after a capped file gather. Existing output limits still apply. | | Local callee source with file pins | — | ✓ | When a query names functions in pinned files, `codegraph_explore` prioritizes local callee bodies through two call or callback hops, within the file and output budgets. | @@ -1134,7 +1134,7 @@ command: | Solidity | `.sol` | Full support (contracts, libraries, interfaces, structs, enums, modifiers, events, errors, state variables, `import`/`using` directives, `emit`/`revert` calls) | | Terraform / OpenTofu | `.tf`, `.tfvars`, `.tofu` | Full support (resources, data sources, modules, variables, outputs, providers incl. aliases, `locals`; `var.`/`local.`/`module.`/resource references with Terraform's per-directory scoping enforced; module calls bridged across the boundary — inputs to the child module's variables, `module.M.out` to the child's output, `source` to the module's files; cloudposse/atmos `remote-state` cross-component wiring when the component is statically named; `provider = aws.east` selections resolved up the module tree; `moved`/`import`/`removed`/`check` block references; `.tfvars` assignments linked to the variables they set) | | Nix | `.nix` | Full support (functions with simple/destructured/curried params, `let`/attrset bindings, `inherit`, `import ./path` file edges — `./dir` resolving through `default.nix` — plus NixOS module `imports = [ ./x.nix ]` lists and `callPackage ./pkg.nix` file edges; call edges; module-system option wiring — a config write like `launchd.user.agents.x = { ... }` links to the module declaring `options.launchd.user.agents`, so option flows trace across modules) | -| Markdown | `.md`, `.mdx`, `.markdown` | Documentation structure (headings, sections, local links, selected table rows and list items, shell command references); this fork only | +| Markdown | `.md`, `.mdx`, `.markdown` | Documentation structure (headings, sections and local links as nodes; tables, commands and list items as searchable section text); this fork only | | Razor / Blazor | `.cshtml`, `.razor` | Markup linked to the C# it names (`@model`, `@inherits`, components, `@inject`) | | XML | `.xml` | MyBatis mapper statements linked to their Java mapper methods | | YAML | `.yml`, `.yaml` | File tracking; Drupal `*.routing.yml` routes and Spring config keys | diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index ed453ff71..9bb2044bb 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -360,8 +360,11 @@ node scripts/release.mjs expect(headings.map((n) => n.name)).toContain('Project Guide'); expect(headings.map((n) => n.name)).toContain('Release'); - const commandNode = result.nodes.find((n) => n.kind === 'function' && n.signature === 'node scripts/release.mjs'); - expect(commandNode).toBeDefined(); + // The command lives in the Release section's searchable summary, not as + // its own node. + const release = headings.find((n) => n.name === 'Release'); + expect(release?.docstring).toContain('commands:'); + expect(release?.docstring).toContain('node scripts/release.mjs'); expect(result.unresolvedReferences).toEqual( expect.arrayContaining([ @@ -394,19 +397,13 @@ node scripts/release.mjs const result = extractFromSource('phases/phase4.md', markdown); - const tableRows = result.nodes.filter((n) => n.kind === 'constant' && n.qualifiedName.includes('table-row')); - expect(tableRows.map((n) => n.name)).toEqual(expect.arrayContaining(['P4-S1', 'P4-S2'])); - - const p4s1 = tableRows.find((n) => n.name === 'P4-S1'); - expect(p4s1?.signature).toContain('Template: P4-S1'); - expect(p4s1?.signature).toContain('Dispatcher: scripts/csv_search.py::run_p4'); - - const commandNode = result.nodes.find((n) => - n.kind === 'function' && - n.language === 'markdown' && - n.signature?.includes('python "{script_path}" p4') - ); - expect(commandNode).toBeDefined(); + // Rows and commands are section text now: no constant/function nodes. + expect(result.nodes.filter((n) => n.kind === 'constant' || n.kind === 'function')).toEqual([]); + const phase4 = result.nodes.find((n) => n.kind === 'module' && n.name === 'Phase 4'); + expect(phase4?.docstring).toContain('P4-S1'); + expect(phase4?.docstring).toContain('scripts/csv_search.py::run_p4'); + expect(phase4?.docstring).toContain('commands:'); + expect(phase4?.docstring).toContain('python "{script_path}" p4'); expect(result.unresolvedReferences).toEqual( expect.arrayContaining([ @@ -439,12 +436,12 @@ node scripts/release.mjs `; const result = extractFromSource('docs/runbook.md', markdown); - const constants = result.nodes.filter((n) => n.kind === 'constant'); - - expect(constants).toEqual(expect.arrayContaining([ - expect.objectContaining({ docstring: 'POST-FENCE references src/auth.ts::login.' }), - expect.objectContaining({ name: 'POST-TABLE' }), - ])); + // The list item and the table row are section text, not nodes. + expect(result.nodes.filter((n) => n.kind === 'constant' || n.kind === 'function')).toEqual([]); + const runbook = result.nodes.find((n) => n.kind === 'module' && n.name === 'Runbook'); + expect(runbook?.docstring).toContain('POST-FENCE references src/auth.ts::login.'); + expect(runbook?.docstring).toContain('POST-TABLE'); + expect(result.unresolvedReferences.filter((ref) => ref.referenceName === 'docs/src/auth.ts::login').length).toBeGreaterThanOrEqual(2); }); it('indexes Setext (underline) headings and skips frontmatter / code fences', () => { diff --git a/__tests__/fixtures/golden/payroll-go.dump b/__tests__/fixtures/golden/payroll-go.dump index cd7381ff6..cdacf2eca 100644 --- a/__tests__/fixtures/golden/payroll-go.dump +++ b/__tests__/fixtures/golden/payroll-go.dump @@ -1,4 +1,4 @@ -== nodes (309) +== nodes (289) {"id":"constant:03cf22f9cca66ee928171cceccbf44b1","kind":"constant","name":"LineEarning","qualified_name":"LineEarning","file_path":"internal/domain/payroll/payslip.go","language":"go","start_line":25,"end_line":25,"start_column":1,"end_column":35,"docstring":null,"signature":"= \"earning\"","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"constant:49f451a6950b1d7e90eb6cfe2776192b","kind":"constant","name":"LineDeduction","qualified_name":"LineDeduction","file_path":"internal/domain/payroll/payslip.go","language":"go","start_line":26,"end_line":26,"start_column":1,"end_column":37,"docstring":null,"signature":"= \"deduction\"","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"constant:56977e604248e3674f9296a2f68154bb","kind":"constant","name":"PayrollService_GetPayrollCycle_FullMethodName","qualified_name":"PayrollService_GetPayrollCycle_FullMethodName","file_path":"internal/gen/payrollpb/payroll_grpc.pb.go","language":"go","start_line":17,"end_line":17,"start_column":1,"end_column":93,"docstring":null,"signature":"= \"/payroll.v1.PayrollService/GetPayrollCycle\"","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} @@ -32,34 +32,25 @@ {"id":"function:00884e47e06de8395363f17646801d9e","kind":"function","name":"RegisterPayrollServiceServer","qualified_name":"RegisterPayrollServiceServer","file_path":"internal/gen/payrollpb/payroll_grpc.pb.go","language":"go","start_line":90,"end_line":92,"start_column":0,"end_column":1,"docstring":"RegisterPayrollServiceServer registers the generated service.","signature":"(s grpc.ServiceRegistrar, srv PayrollServiceServer)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:031a254efe4755a89bab7f6d9209b4ea","kind":"function","name":"NewPayrollServiceClient","qualified_name":"NewPayrollServiceClient","file_path":"internal/gen/payrollpb/payroll_grpc.pb.go","language":"go","start_line":33,"end_line":35,"start_column":0,"end_column":1,"docstring":"NewPayrollServiceClient returns a generated client.","signature":"(cc grpc.ClientConnInterface) PayrollServiceClient","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollServiceClient"} {"id":"function:03a41d6c149ff7b81fd46b0c79fe7ce4","kind":"function","name":"NewRouter","qualified_name":"NewRouter","file_path":"internal/transport/httpapi/router.go","language":"go","start_line":7,"end_line":14,"start_column":0,"end_column":1,"docstring":"NewRouter wires the HTTP surface. The payroll cycle endpoint is the only\nentry point into the hand-written use-case layer.","signature":"(h *PayrollHandler) http.Handler","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"http.Handler"} -{"id":"function:0414eb07a8b96d1a74af19b17a88be3d","kind":"function","name":"calculate.go CalculatePayrollCycleTotals, CalculatePayslipNet,","qualified_name":"README.md::command:26:2:calculate.go CalculatePayrollCycleTotals, CalculatePayslipNet, …","file_path":"README.md","language":"markdown","start_line":26,"end_line":26,"start_column":2,"end_column":71,"docstring":null,"signature":"calculate.go CalculatePayrollCycleTotals, CalculatePayslipNet, …","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:05eca0e54aeccbabbb69152ff002657c","kind":"function","name":"GetEmployee","qualified_name":"GetEmployee","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":45,"end_line":48,"start_column":0,"end_column":1,"docstring":"GetEmployee selects one employee row by primary key.","signature":"(ctx context.Context, db *sql.DB, id string) (EmployeeRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"EmployeeRow"} {"id":"function:0709805de829555a8b769b387496185d","kind":"function","name":"DeletePayslip","qualified_name":"DeletePayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":75,"end_line":79,"start_column":0,"end_column":1,"docstring":"DeletePayslip removes one payslip row.","signature":"(ctx context.Context, db *sql.DB, id string) error","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"error"} {"id":"function:0ae098aebe4dbe9d71e324db178702ba","kind":"function","name":"UpdatePayrollCycle","qualified_name":"UpdatePayrollCycle","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":52,"end_line":57,"start_column":0,"end_column":1,"docstring":"UpdatePayrollCycle patches one payroll_cycle row.","signature":"(ctx context.Context, db *sql.DB, id string, req PayrollCycleUpdateRequest) (PayrollCycleRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleRow"} {"id":"function:0e6d6dd34d39c0ac54c9a7084b8d8ca8","kind":"function","name":"scanEmployee","qualified_name":"scanEmployee","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":84,"end_line":88,"start_column":0,"end_column":1,"docstring":null,"signature":"(row *sql.Row) (EmployeeRow, error)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"EmployeeRow"} {"id":"function:0f4c51f6d4475eb21570beb83cf54ff4","kind":"function","name":"NewStore","qualified_name":"NewStore","file_path":"internal/gen/fkit/payroll/store.go","language":"go","start_line":19,"end_line":19,"start_column":0,"end_column":58,"docstring":"NewStore returns a generated store bound to db.","signature":"(db *sql.DB) *Store","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"Store"} {"id":"function:13c7c9632913a7b5c4cb3853ae46f74a","kind":"function","name":"UpdatePayslip","qualified_name":"UpdatePayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":65,"end_line":72,"start_column":0,"end_column":1,"docstring":"UpdatePayslip patches one payslip row.","signature":"(ctx context.Context, db *sql.DB, id string, req PayslipUpdateRequest) (PayslipRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} -{"id":"function:1dcd8cad196a824c650d48f180f6d822","kind":"function","name":"cmd/payrolld/main.go wires the","qualified_name":"README.md::command:12:0:cmd/payrolld/main.go wires the service","file_path":"README.md","language":"markdown","start_line":12,"end_line":12,"start_column":0,"end_column":61,"docstring":null,"signature":"cmd/payrolld/main.go wires the service","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:1de2380afa5ea252aaabed82c8149f04","kind":"function","name":"NewService","qualified_name":"NewService","file_path":"internal/usecase/payroll/cycle.go","language":"go","start_line":50,"end_line":52,"start_column":0,"end_column":1,"docstring":null,"signature":"(store *payslipstore.Store, c clock.Clock) *Service","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"Service"} -{"id":"function:1deff88eaf3b3b6189345ce5cf39f95f","kind":"function","name":"payslip_builder.go BuildPayslip —","qualified_name":"README.md::command:16:2:payslip_builder.go BuildPayslip — the actual pay calculation","file_path":"README.md","language":"markdown","start_line":16,"end_line":16,"start_column":2,"end_column":87,"docstring":null,"signature":"payslip_builder.go BuildPayslip — the actual pay calculation","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:209f5fe0d451b33db0d6395638a62fe8","kind":"function","name":"scanPayrollCycle","qualified_name":"scanPayrollCycle","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":111,"end_line":116,"start_column":0,"end_column":1,"docstring":null,"signature":"(row *sql.Row) (PayrollCycleRow, error)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleRow"} {"id":"function:2b4f03c93cfc20f726251980faf21e4b","kind":"function","name":"PayrollCycleToDTO","qualified_name":"PayrollCycleToDTO","file_path":"internal/gen/fkit/payroll/dto.go","language":"go","start_line":69,"end_line":71,"start_column":0,"end_column":1,"docstring":"PayrollCycleToDTO converts a payroll_cycle row to its wire form.","signature":"(r PayrollCycleRow) PayrollCycleDTO","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleDTO"} -{"id":"function:2b54c509c897b69fda45f099b03ddecc","kind":"function","name":"internal/store/payslipstore/store.go the real","qualified_name":"README.md::command:19:0:internal/store/payslipstore/store.go the real Upsert","file_path":"README.md","language":"markdown","start_line":19,"end_line":19,"start_column":0,"end_column":59,"docstring":null,"signature":"internal/store/payslipstore/store.go the real Upsert","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:2b593a9d67adcbcc2f8157621f1b08f4","kind":"function","name":"CreatePayrollCycle","qualified_name":"CreatePayrollCycle","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":40,"end_line":43,"start_column":0,"end_column":1,"docstring":"CreatePayrollCycle inserts one payroll_cycle row.","signature":"(ctx context.Context, db *sql.DB, req PayrollCycleCreateRequest) (PayrollCycleRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleRow"} {"id":"function:2de35841246f09a810efb472eacc8bdb","kind":"function","name":"laterOf","qualified_name":"laterOf","file_path":"internal/usecase/payroll/prorate.go","language":"go","start_line":41,"end_line":46,"start_column":0,"end_column":1,"docstring":null,"signature":"(a, b time.Time) time.Time","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"time.Time"} {"id":"function:2f7a92e2ac264db827c2eedf3fe7c448","kind":"function","name":"earlierOf","qualified_name":"earlierOf","file_path":"internal/usecase/payroll/prorate.go","language":"go","start_line":48,"end_line":53,"start_column":0,"end_column":1,"docstring":null,"signature":"(a, b time.Time) time.Time","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"time.Time"} -{"id":"function:314d7259385972532d50bfb35a88f851","kind":"function","name":"prorate.go","qualified_name":"README.md::command:17:2:prorate.go","file_path":"README.md","language":"markdown","start_line":17,"end_line":17,"start_column":2,"end_column":12,"docstring":null,"signature":"prorate.go","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:34b9bf3532aea2d43bc29ca82a937846","kind":"function","name":"CalculatePayslipNet","qualified_name":"CalculatePayslipNet","file_path":"internal/gen/fkit/payroll/calculate.go","language":"go","start_line":37,"end_line":39,"start_column":0,"end_column":1,"docstring":"CalculatePayslipNet recomputes net from the stored gross and deduction\ncolumns of one row. Pure column arithmetic — no pay rules.","signature":"(row PayslipRow) int64","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} {"id":"function:371445b42f0b89d0e09b6c77c0cae1c9","kind":"function","name":"CreatePayslip","qualified_name":"CreatePayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":51,"end_line":56,"start_column":0,"end_column":1,"docstring":"CreatePayslip inserts one payslip row.","signature":"(ctx context.Context, db *sql.DB, req PayslipCreateRequest) (PayslipRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} {"id":"function:3de0ed87c97e45d857a0ea66c1b2e97d","kind":"function","name":"DeleteEmployee","qualified_name":"DeleteEmployee","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":58,"end_line":62,"start_column":0,"end_column":1,"docstring":"DeleteEmployee removes one employee row.","signature":"(ctx context.Context, db *sql.DB, id string) error","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"error"} -{"id":"function:452bab8507b181181f43618779fe1c9b","kind":"function","name":"payslip.go CreatePayslip, GetPayslip,","qualified_name":"README.md::command:23:2:payslip.go CreatePayslip, GetPayslip, UpdatePayslip, a second BuildPayslip","file_path":"README.md","language":"markdown","start_line":23,"end_line":23,"start_column":2,"end_column":83,"docstring":null,"signature":"payslip.go CreatePayslip, GetPayslip, UpdatePayslip, a second BuildPayslip","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:46dc118057024483ca51e11a06ae35b5","kind":"function","name":"internal/gen/payrollpb/*.pb.go generated, detectable","qualified_name":"README.md::command:29:0:internal/gen/payrollpb/*.pb.go generated, detectable by PATH","file_path":"README.md","language":"markdown","start_line":29,"end_line":29,"start_column":0,"end_column":73,"docstring":null,"signature":"internal/gen/payrollpb/*.pb.go generated, detectable by PATH","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:4827032314863eb2b99f791098f8668c","kind":"function","name":"probe-allocation.mjs","qualified_name":"README.md::command:75:0:node scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design","file_path":"README.md","language":"markdown","start_line":75,"end_line":75,"start_column":0,"end_column":84,"docstring":null,"signature":"node scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:4bfd3234b60c7cb018e09fa29aea24df","kind":"function","name":"health","qualified_name":"health","file_path":"internal/transport/httpapi/router.go","language":"go","start_line":16,"end_line":19,"start_column":0,"end_column":1,"docstring":null,"signature":"(w http.ResponseWriter, _ *http.Request)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:4ebe28bcc1b041576b79ad26309dd0b1","kind":"function","name":"writeJSON","qualified_name":"writeJSON","file_path":"internal/transport/httpapi/payroll_handler.go","language":"go","start_line":88,"end_line":92,"start_column":0,"end_column":1,"docstring":null,"signature":"(w http.ResponseWriter, status int, body any)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:516e3daec7ae43f6891cfbcf82d1ee0f","kind":"function","name":"ApproveTimesheet","qualified_name":"ApproveTimesheet","file_path":"internal/gen/fkit/timesheet/timesheet.go","language":"go","start_line":46,"end_line":49,"start_column":0,"end_column":1,"docstring":"ApproveTimesheet flips the approved column on one timesheet row.","signature":"(ctx context.Context, db *sql.DB, id string) (TimesheetRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"TimesheetRow"} {"id":"function:5d48feb09a7faedf052939df2141b437","kind":"function","name":"CalculatePayrollCycleTotals","qualified_name":"CalculatePayrollCycleTotals","file_path":"internal/gen/fkit/payroll/calculate.go","language":"go","start_line":25,"end_line":33,"start_column":0,"end_column":1,"docstring":"CalculatePayrollCycleTotals runs the generated SUM aggregate over the\npayslip rows of one cycle. It totals what is already stored; it does not\ncalculate any payslip.","signature":"(ctx context.Context, db *sql.DB, cycleID string) (PayrollCycleTotals, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleTotals"} -{"id":"function:5dcd3514b7efa1f5e466655bd84a5f3f","kind":"function","name":"internal/transport/httpapi/ HTTP entry","qualified_name":"README.md::command:13:0:internal/transport/httpapi/ HTTP entry point → use-case","file_path":"README.md","language":"markdown","start_line":13,"end_line":13,"start_column":0,"end_column":71,"docstring":null,"signature":"internal/transport/httpapi/ HTTP entry point → use-case","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:5e2796c1fbc4081e6b6f6a4307fe4bc0","kind":"function","name":"key","qualified_name":"key","file_path":"internal/store/payslipstore/store.go","language":"go","start_line":31,"end_line":31,"start_column":0,"end_column":81,"docstring":null,"signature":"(cycleID, employeeID string) string","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"string"} {"id":"function:63f3bd99966467470cbabcb097ad36dd","kind":"function","name":"ListPayslipsByCycle","qualified_name":"ListPayslipsByCycle","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":82,"end_line":100,"start_column":0,"end_column":1,"docstring":"ListPayslipsByCycle selects every payslip row for a cycle.","signature":"(ctx context.Context, db *sql.DB, cycleID string) ([]PayslipRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:680f46cf762a57779b8a8dbe6de8c989","kind":"function","name":"scanContract","qualified_name":"scanContract","file_path":"internal/gen/fkit/employee/contract.go","language":"go","start_line":79,"end_line":84,"start_column":0,"end_column":1,"docstring":null,"signature":"(row *sql.Row) (ContractRow, error)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"ContractRow"} @@ -70,35 +61,25 @@ {"id":"function:74eb87268b2ffe345f9ef8496c90d926","kind":"function","name":"PayslipFromDTO","qualified_name":"PayslipFromDTO","file_path":"internal/gen/fkit/payroll/dto.go","language":"go","start_line":54,"end_line":66,"start_column":0,"end_column":1,"docstring":"PayslipFromDTO converts a wire payslip back to a row.","signature":"(d PayslipDTO) PayslipRow","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} {"id":"function:79440e7be4589fa5d1d77c252e3f1fe0","kind":"function","name":"CalculateEmployeeYearToDate","qualified_name":"CalculateEmployeeYearToDate","file_path":"internal/gen/fkit/payroll/calculate.go","language":"go","start_line":54,"end_line":60,"start_column":0,"end_column":1,"docstring":"CalculateEmployeeYearToDate sums an employee's stored payslips for a year.","signature":"(ctx context.Context, db *sql.DB, employeeID string, year int) (int64, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} {"id":"function:7b3e5cb66aaaa7349a6ec9bd7f401259","kind":"function","name":"UpdateEmployee","qualified_name":"UpdateEmployee","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":51,"end_line":55,"start_column":0,"end_column":1,"docstring":"UpdateEmployee patches one employee row.","signature":"(ctx context.Context, db *sql.DB, id string, req EmployeeUpdateRequest) (EmployeeRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"EmployeeRow"} -{"id":"function:7ed76b62f3223020612f80cf9c2e6ee7","kind":"function","name":"internal/gen/fkit/employee/, timesheet/ more","qualified_name":"README.md::command:28:0:internal/gen/fkit/employee/, timesheet/ more generated CRUD","file_path":"README.md","language":"markdown","start_line":28,"end_line":28,"start_column":0,"end_column":63,"docstring":null,"signature":"internal/gen/fkit/employee/, timesheet/ more generated CRUD","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:80943211ed2b432c66489bcda8e6d0a4","kind":"function","name":"prorateSalary","qualified_name":"prorateSalary","file_path":"internal/usecase/payroll/prorate.go","language":"go","start_line":11,"end_line":24,"start_column":0,"end_column":1,"docstring":"prorateSalary scales a full period rate down when the contract covers only\npart of the cycle window (a mid-period joiner or leaver).","signature":"(fullCents int64, contract payroll.Contract, cycle payroll.Cycle) int64","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} {"id":"function:81328c12525c00ff7d9778f2e988eab3","kind":"function","name":"ListEmployeesByStatus","qualified_name":"ListEmployeesByStatus","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":65,"end_line":82,"start_column":0,"end_column":1,"docstring":"ListEmployeesByStatus selects employee rows in one status.","signature":"(ctx context.Context, db *sql.DB, status string) ([]EmployeeRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:82bd498c77e3b6ab5b76ae283f737fde","kind":"function","name":"CreateContract","qualified_name":"CreateContract","file_path":"internal/gen/fkit/employee/contract.go","language":"go","start_line":39,"end_line":44,"start_column":0,"end_column":1,"docstring":"CreateContract inserts one contract row.","signature":"(ctx context.Context, db *sql.DB, req ContractCreateRequest) (ContractRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"ContractRow"} {"id":"function:83cf216ee860f62e450ed62c0977ab07","kind":"function","name":"DeletePayrollCycle","qualified_name":"DeletePayrollCycle","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":60,"end_line":64,"start_column":0,"end_column":1,"docstring":"DeletePayrollCycle removes one payroll_cycle row.","signature":"(ctx context.Context, db *sql.DB, id string) error","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"error"} {"id":"function:840b0285a601a69c58d31e483b0ab2c8","kind":"function","name":"ListTimesheetsByCycle","qualified_name":"ListTimesheetsByCycle","file_path":"internal/gen/fkit/timesheet/timesheet.go","language":"go","start_line":52,"end_line":69,"start_column":0,"end_column":1,"docstring":"ListTimesheetsByCycle selects timesheet rows for one cycle.","signature":"(ctx context.Context, db *sql.DB, cycleID string) ([]TimesheetRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:852fbd5b1a3434558ad769f0855ecfae","kind":"function","name":"sumKind","qualified_name":"sumKind","file_path":"internal/usecase/payroll/payslip_builder.go","language":"go","start_line":142,"end_line":150,"start_column":0,"end_column":1,"docstring":null,"signature":"(lines []payroll.Line, kind payroll.LineKind) int64","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} -{"id":"function:8e554156c22d75e629b346ea4ed215d1","kind":"function","name":"npx vitest run","qualified_name":"README.md::command:76:0:npx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design","file_path":"README.md","language":"markdown","start_line":76,"end_line":76,"start_column":0,"end_column":82,"docstring":null,"signature":"npx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:90eb51f51a3d9a397d4a53f26f2019aa","kind":"function","name":"main","qualified_name":"main","file_path":"cmd/payrolld/main.go","language":"go","start_line":15,"end_line":35,"start_column":0,"end_column":1,"docstring":null,"signature":"()","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:90eedb8d136d41001abe73a32e54de98","kind":"function","name":"internal/gen/fkit/payroll/ ← THE","qualified_name":"README.md::command:22:0:internal/gen/fkit/payroll/ ← THE NOISE. Generated CRUD, ORDINARY names:","file_path":"README.md","language":"markdown","start_line":22,"end_line":22,"start_column":0,"end_column":88,"docstring":null,"signature":"internal/gen/fkit/payroll/ ← THE NOISE. Generated CRUD, ORDINARY names:","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:97d5bc1460c9dc0379c4c2016964d601","kind":"function","name":"scanTimesheet","qualified_name":"scanTimesheet","file_path":"internal/gen/fkit/timesheet/timesheet.go","language":"go","start_line":71,"end_line":76,"start_column":0,"end_column":1,"docstring":null,"signature":"(row *sql.Row) (TimesheetRow, error)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"TimesheetRow"} {"id":"function:9a1275209a1a60e56b21de5aba04f222","kind":"function","name":"GetPayslip","qualified_name":"GetPayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":59,"end_line":62,"start_column":0,"end_column":1,"docstring":"GetPayslip selects one payslip row by primary key.","signature":"(ctx context.Context, db *sql.DB, id string) (PayslipRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} {"id":"function:9ce32ae86fd8d01bb45dfcd56982a11b","kind":"function","name":"calendarDays","qualified_name":"calendarDays","file_path":"internal/usecase/payroll/prorate.go","language":"go","start_line":34,"end_line":39,"start_column":0,"end_column":1,"docstring":null,"signature":"(from, to time.Time) int","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int"} -{"id":"function:a141d7d33a5f910db19e688e88c75b19","kind":"function","name":"internal/platform/clock/clock.go","qualified_name":"README.md::command:20:0:internal/platform/clock/clock.go","file_path":"README.md","language":"markdown","start_line":20,"end_line":20,"start_column":0,"end_column":32,"docstring":null,"signature":"internal/platform/clock/clock.go","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:a2d9b9035f29c64480f1644da96bc32f","kind":"function","name":"New","qualified_name":"New","file_path":"internal/store/payslipstore/store.go","language":"go","start_line":22,"end_line":29,"start_column":0,"end_column":1,"docstring":null,"signature":"() *Store","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"Store"} {"id":"function:b11ab260a99f087321b978dfbbcbccbb","kind":"function","name":"GetContract","qualified_name":"GetContract","file_path":"internal/gen/fkit/employee/contract.go","language":"go","start_line":47,"end_line":50,"start_column":0,"end_column":1,"docstring":"GetContract selects one contract row by primary key.","signature":"(ctx context.Context, db *sql.DB, id string) (ContractRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"ContractRow"} {"id":"function:b3b725a56ce5377ac34ef157db4d1170","kind":"function","name":"PayslipToDTO","qualified_name":"PayslipToDTO","file_path":"internal/gen/fkit/payroll/dto.go","language":"go","start_line":39,"end_line":51,"start_column":0,"end_column":1,"docstring":"PayslipToDTO converts a payslip row to its wire form.","signature":"(r PayslipRow) PayslipDTO","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipDTO"} -{"id":"function:b40d73f7b2cc84c05e79c724863974f7","kind":"function","name":"store.go a second","qualified_name":"README.md::command:25:2:store.go a second Upsert","file_path":"README.md","language":"markdown","start_line":25,"end_line":25,"start_column":2,"end_column":35,"docstring":null,"signature":"store.go a second Upsert","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:b50e5df611274d41388f746ef35951e0","kind":"function","name":"NewPayrollHandler","qualified_name":"NewPayrollHandler","file_path":"internal/transport/httpapi/payroll_handler.go","language":"go","start_line":16,"end_line":18,"start_column":0,"end_column":1,"docstring":null,"signature":"(svc *payroll.Service) *PayrollHandler","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollHandler"} -{"id":"function:b60f2c33d5eaa6738bcdf88143f099af","kind":"function","name":"payroll_cycle.go CreatePayrollCycle, PayrollCycleCreateRequest,","qualified_name":"README.md::command:24:2:payroll_cycle.go CreatePayrollCycle, PayrollCycleCreateRequest, …","file_path":"README.md","language":"markdown","start_line":24,"end_line":24,"start_column":2,"end_column":68,"docstring":null,"signature":"payroll_cycle.go CreatePayrollCycle, PayrollCycleCreateRequest, …","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:b89f64f70418e754ec4cd1fef07bbf86","kind":"function","name":"GetPayrollCycle","qualified_name":"GetPayrollCycle","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":46,"end_line":49,"start_column":0,"end_column":1,"docstring":"GetPayrollCycle selects one payroll_cycle row by primary key.","signature":"(ctx context.Context, db *sql.DB, id string) (PayrollCycleRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayrollCycleRow"} {"id":"function:be35e9b1f03a5492d14e3bf3cc5dc87b","kind":"function","name":"BuildPayslip","qualified_name":"BuildPayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":112,"end_line":121,"start_column":0,"end_column":1,"docstring":"BuildPayslip maps a create request onto a row. Field copy only — the\ngenerator has no knowledge of pay rules.","signature":"(req PayslipCreateRequest) PayslipRow","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} -{"id":"function:c2b743a41c78c6d39eb292c4ecd1138d","kind":"function","name":"npm build","qualified_name":"README.md::command:74:0:npm run build","file_path":"README.md","language":"markdown","start_line":74,"end_line":74,"start_column":0,"end_column":13,"docstring":null,"signature":"npm run build","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:c304f28b41560a488fa25f4c0bdf4c46","kind":"function","name":"prorateAllowance","qualified_name":"prorateAllowance","file_path":"internal/usecase/payroll/prorate.go","language":"go","start_line":27,"end_line":32,"start_column":0,"end_column":1,"docstring":"prorateAllowance applies the same window rule to a recurring allowance.","signature":"(a payroll.Allowance, cycle payroll.Cycle, e payroll.Employee) int64","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} -{"id":"function:c58e8b67983f5b87ff8b159278954265","kind":"function","name":"internal/domain/payroll/payslip.go hand-written domain","qualified_name":"README.md::command:18:0:internal/domain/payroll/payslip.go hand-written domain types","file_path":"README.md","language":"markdown","start_line":18,"end_line":18,"start_column":0,"end_column":69,"docstring":null,"signature":"internal/domain/payroll/payslip.go hand-written domain types","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:c6392c858f3b95686ef12895ec918b6e","kind":"function","name":"CreateEmployee","qualified_name":"CreateEmployee","file_path":"internal/gen/fkit/employee/employee.go","language":"go","start_line":39,"end_line":42,"start_column":0,"end_column":1,"docstring":"CreateEmployee inserts one employee row.","signature":"(ctx context.Context, db *sql.DB, req EmployeeCreateRequest) (EmployeeRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"EmployeeRow"} {"id":"function:c68d01c236174f79631f8e8222523fa4","kind":"function","name":"ListPayrollCycles","qualified_name":"ListPayrollCycles","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":67,"end_line":84,"start_column":0,"end_column":1,"docstring":"ListPayrollCycles selects every payroll_cycle row.","signature":"(ctx context.Context, db *sql.DB) ([]PayrollCycleRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:cf3f266e9ee5695886d6eff55fe33457","kind":"function","name":"cycle.go runPayrollCycleAll (227","qualified_name":"README.md::command:15:2:cycle.go runPayrollCycleAll (227 lines)","file_path":"README.md","language":"markdown","start_line":15,"end_line":15,"start_column":2,"end_column":76,"docstring":null,"signature":"cycle.go runPayrollCycleAll (227 lines)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:d08f890a663db7d10ae4f6cb67b95970","kind":"function","name":"internal/usecase/payroll/ ← THE","qualified_name":"README.md::command:14:0:internal/usecase/payroll/ ← THE ANSWER. Hand-written workflow:","file_path":"README.md","language":"markdown","start_line":14,"end_line":14,"start_column":0,"end_column":80,"docstring":null,"signature":"internal/usecase/payroll/ ← THE ANSWER. Hand-written workflow:","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:d82ab6b26c7b41b587b8d940d1c57034","kind":"function","name":"scanPayslip","qualified_name":"scanPayslip","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":123,"end_line":129,"start_column":0,"end_column":1,"docstring":null,"signature":"(row *sql.Row) (PayslipRow, error)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"PayslipRow"} {"id":"function:dafc49538832924fdd27fb95ee61d479","kind":"function","name":"DeleteContract","qualified_name":"DeleteContract","file_path":"internal/gen/fkit/employee/contract.go","language":"go","start_line":53,"end_line":57,"start_column":0,"end_column":1,"docstring":"DeleteContract removes one contract row.","signature":"(ctx context.Context, db *sql.DB, id string) error","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"error"} {"id":"function:de0b4136eaf549d0e68341f4123657ba","kind":"function","name":"ListPayrollCyclesByStatus","qualified_name":"ListPayrollCyclesByStatus","file_path":"internal/gen/fkit/payroll/payroll_cycle.go","language":"go","start_line":87,"end_line":104,"start_column":0,"end_column":1,"docstring":"ListPayrollCyclesByStatus selects payroll_cycle rows in one status.","signature":"(ctx context.Context, db *sql.DB, status string) ([]PayrollCycleRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} @@ -106,7 +87,6 @@ {"id":"function:ec95f80b7e074efca14308b96f0fae41","kind":"function","name":"httpError","qualified_name":"httpError","file_path":"internal/transport/httpapi/payroll_handler.go","language":"go","start_line":94,"end_line":96,"start_column":0,"end_column":1,"docstring":null,"signature":"(w http.ResponseWriter, status int, msg string)","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:ef7fb056c49eb36d083213308b4cbbb1","kind":"function","name":"CountPayslipsByCycle","qualified_name":"CountPayslipsByCycle","file_path":"internal/gen/fkit/payroll/payslip.go","language":"go","start_line":103,"end_line":108,"start_column":0,"end_column":1,"docstring":"CountPayslipsByCycle counts payslip rows for a cycle.","signature":"(ctx context.Context, db *sql.DB, cycleID string) (int64, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"int64"} {"id":"function:f56104b93009b7d690676165ae7f0fd0","kind":"function","name":"ListContractsForEmployee","qualified_name":"ListContractsForEmployee","file_path":"internal/gen/fkit/employee/contract.go","language":"go","start_line":60,"end_line":77,"start_column":0,"end_column":1,"docstring":"ListContractsForEmployee selects contract rows for one employee.","signature":"(ctx context.Context, db *sql.DB, employeeID string) ([]ContractRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"function:f5bb323394b903d448de3194d3804241","kind":"function","name":"dto.go","qualified_name":"README.md::command:27:2:dto.go","file_path":"README.md","language":"markdown","start_line":27,"end_line":27,"start_column":2,"end_column":8,"docstring":null,"signature":"dto.go","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"function:fd984744d95388a5603b37d63b6b7035","kind":"function","name":"GetTimesheet","qualified_name":"GetTimesheet","file_path":"internal/gen/fkit/timesheet/timesheet.go","language":"go","start_line":40,"end_line":43,"start_column":0,"end_column":1,"docstring":"GetTimesheet selects one timesheet row by primary key.","signature":"(ctx context.Context, db *sql.DB, id string) (TimesheetRow, error)","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":"TimesheetRow"} {"id":"import:00f1a8a378ccaf8584e4437b6729d26a","kind":"import","name":"time","qualified_name":"time","file_path":"internal/platform/clock/clock.go","language":"go","start_line":3,"end_line":3,"start_column":7,"end_column":13,"docstring":null,"signature":"\"time\"","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"import:0327a76404238369f2f1f1bc67c6a633","kind":"import","name":"google.golang.org/grpc","qualified_name":"google.golang.org/grpc","file_path":"internal/gen/payrollpb/payroll_grpc.pb.go","language":"go","start_line":12,"end_line":12,"start_column":1,"end_column":30,"docstring":null,"signature":"grpc \"google.golang.org/grpc\"","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} @@ -247,8 +227,8 @@ {"id":"module:1be454da0953954aef45c525ee8f8b51","kind":"module","name":"payroll-go — the #1500 regression fixture","qualified_name":"README.md#payroll-go-the-1500-regression-fixture","file_path":"README.md","language":"markdown","start_line":1,"end_line":96,"start_column":0,"end_column":43,"docstring":"A synthetic Go service reproducing the repo shape from issue #1500:\ngenerated CRUD sitting beside the hand-written use-case that does the real work.\nThis tree is a fixture, not a program. It never compiles or runs — it exists to be\nindexed. Keep it valid, idiomatic Go anyway: the extractor's output is the whole point.\nThe shape\ncmd/payrolld/main.go wires the service\ninternal/transport/httpapi/ HTTP entry point → use-case\ninternal/usecase/payroll/ ← THE ANSWER. Hand-written workflow:\ncycle.go runPayrollC...","signature":"# payroll-go — the #1500 regression fixture","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"module:39730d6601ee76ddcd5e78d50abfe5a2","kind":"module","name":"The three properties that make it a regression fixture","qualified_name":"README.md#the-three-properties-that-make-it-a-regression-fixture","file_path":"README.md","language":"markdown","start_line":35,"end_line":54,"start_column":0,"end_column":57,"docstring":"1. Generated files that only a CONTENT header betrays. The internal/gen/fkit/**\nfiles have ordinary names (payslip.go, store.go) and carry\n// Code generated by fkit v3.11.0. DO NOT EDIT.. Path-only detection misses every\none of them — that is the #1500 case, and why CG-5 added the content check. The\npayrollpb/*.pb.go files cover the path-detectable channel beside them.\n2. Deliberate name collisions. BuildPayslip, Upsert and Store each exist twice,\nonce generated and once hand-written. The generated layer also name-collides on every\nterm of the question below — CreatePayslip, PayrollCycleCreate...","signature":"## The three properties that make it a regression fixture","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"module:900ad55709598429050c36805696291a","kind":"module","name":"The assertion","qualified_name":"README.md#the-assertion","file_path":"README.md","language":"markdown","start_line":55,"end_line":70,"start_column":0,"end_column":16,"docstring":"Query: \"how does payroll cycle create and calculate payslips?\" — an architecture\nquestion that names none of the symbols that answer it. The budget should concentrate on\nthe hand-written workflow. As of 2026-08-03 it does not:\n| | allocated | delivered |\n|---|---|---|\n| hand-written | 48.4% | 25.6% (all of it domain types) |\n| generated CRUD | 39.9% | 57.4% |\ncycle.go is allocated the single largest slice (7,052 chars, 30.6%) and delivers\nzero — the hard ceiling drops its whole section. payslip_builder.go (rank #8) never\nrenders at all. runPayrollCycleAll, the hand-written BuildPayslip and the...","signature":"## The assertion","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"module:a6bc3fe976757dd8aebda5a6c6475961","kind":"module","name":"The shape","qualified_name":"README.md#the-shape","file_path":"README.md","language":"markdown","start_line":9,"end_line":34,"start_column":0,"end_column":12,"docstring":"cmd/payrolld/main.go wires the service\ninternal/transport/httpapi/ HTTP entry point → use-case\ninternal/usecase/payroll/ ← THE ANSWER. Hand-written workflow:\ncycle.go runPayrollCycleAll (227 lines)\npayslip_builder.go BuildPayslip — the actual pay calculation\nprorate.go\ninternal/domain/payroll/payslip.go hand-written domain types\ninternal/store/payslipstore/store.go the real Upsert\ninternal/platform/clock/clock.go\ninternal/gen/fkit/payroll/ ← THE ...","signature":"## The shape","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -{"id":"module:a883d05f3bb8edb262fd18c8c2849417","kind":"module","name":"Running it","qualified_name":"README.md#running-it","file_path":"README.md","language":"markdown","start_line":71,"end_line":83,"start_column":0,"end_column":13,"docstring":"npm run build\nnode scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design\nnpx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design\nThe probe reports per-file budget share against scripts/agent-eval/allocation-fixtures.json.\nThe vitest suite pins the fixture's shape and holds the allocation assertion as it.fails —\ngreen while the bug is open, red the moment it is fixed. See\ndocs/design/explore-budget-allocation.md.","signature":"## Running it","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} +{"id":"module:a6bc3fe976757dd8aebda5a6c6475961","kind":"module","name":"The shape","qualified_name":"README.md#the-shape","file_path":"README.md","language":"markdown","start_line":9,"end_line":34,"start_column":0,"end_column":12,"docstring":"cmd/payrolld/main.go wires the service\ninternal/transport/httpapi/ HTTP entry point → use-case\ninternal/usecase/payroll/ ← THE ANSWER. Hand-written workflow:\ncycle.go runPayrollCycleAll (227 lines)\npayslip_builder.go BuildPayslip — the actual pay calculation\nprorate.go\ninternal/domain/payroll/payslip.go hand-written domain types\ninternal/store/payslipstore/store.go the real Upsert\ninternal/platform/clock/clock.go\ninternal/gen/fkit/payroll/ ← THE ...","signature":"## The shape","search_text":"cmd/payrolld/main.go wires the service · internal/transport/httpapi/ HTTP entry point → use-case · internal/usecase/payroll/ ← THE ANSWER. Hand-written workflow: · cycle.go runPayrollCycleAll (227 lines) · payslip_builder.go BuildPayslip — the actual pay calculation · internal/domain/payroll/payslip.go hand-written domain types · internal/store/payslipstore/store.go the real Upsert · internal/gen/fkit/payroll/ ← THE NOISE. Generated CRUD, ORDINARY names: · payslip.go CreatePayslip, GetPayslip, UpdatePayslip, a second BuildPayslip · payroll_cycle.go CreatePayrollCycle, PayrollCycleCreateRequest, … · store.go a second Upsert · calculate.go CalculatePayrollCycleTotals, CalculatePayslipNet, … · dto.go · internal/gen/fkit/employee/, timesheet/ more generated CRUD · internal/gen/payrollpb/.pb.go generated, detectable by PATH","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} +{"id":"module:a883d05f3bb8edb262fd18c8c2849417","kind":"module","name":"Running it","qualified_name":"README.md#running-it","file_path":"README.md","language":"markdown","start_line":71,"end_line":83,"start_column":0,"end_column":13,"docstring":"npm run build\nnode scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design\nnpx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design\nThe probe reports per-file budget share against scripts/agent-eval/allocation-fixtures.json.\nThe vitest suite pins the fixture's shape and holds the allocation assertion as it.fails —\ngreen while the bug is open, red the moment it is fixed. See\ndocs/design/explore-budget-allocation.md. · commands: npm run build, node scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design, npx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design","signature":"## Running it","search_text":"node scripts/agent-eval/probe-allocation.mjs payroll-go # exits 1 today, by design · npx vitest run __tests__/explore-allocation-1500.test.ts # green today, by design","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"module:f8d63ed69e573ade27e2d1d4fc0ae8b4","kind":"module","name":"Known finding: the chain's Upsert edge resolves to the generated store","qualified_name":"README.md#known-finding-the-chains-upsert-edge-resolves-to-the-generated-store","file_path":"README.md","language":"markdown","start_line":84,"end_line":96,"start_column":0,"end_column":75,"docstring":"runPayrollCycleAll calls s.store.Upsert(ctx, slip), where s.store is a\n*payslipstore.Store. The graph resolves that edge to internal/gen/fkit/payroll/store.go\n— the generated Store.Upsert — not the hand-written one. Same-name method resolution\nacross two packages that both define Store.Upsert picks the wrong receiver.\nThis is a resolution defect, not a budget one, and it is left unfixed on purpose: it is\nupstream of the allocation bug (a wrong edge pulls the generated store into the subgraph\nand inflates its score), so it belongs with the scoring work in CG-10 rather than here.\nThe test assert...","signature":"## Known finding: the chain's Upsert edge resolves to the generated store","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"route:internal/transport/httpapi/router.go:10:GET:/v1/payroll/cycles/{cycleID}","kind":"route","name":"GET /v1/payroll/cycles/{cycleID}","qualified_name":"internal/transport/httpapi/router.go::route:/v1/payroll/cycles/{cycleID}","file_path":"internal/transport/httpapi/router.go","language":"go","start_line":10,"end_line":10,"start_column":0,"end_column":62,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"route:internal/transport/httpapi/router.go:11:GET:/v1/payroll/cycles/{cycleID}/payslips","kind":"route","name":"GET /v1/payroll/cycles/{cycleID}/payslips","qualified_name":"internal/transport/httpapi/router.go::route:/v1/payroll/cycles/{cycleID}/payslips","file_path":"internal/transport/httpapi/router.go","language":"go","start_line":11,"end_line":11,"start_column":0,"end_column":75,"docstring":null,"signature":null,"visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} @@ -308,7 +288,7 @@ {"id":"variable:6b7bef84822056845bdabeb41a821f0a","kind":"variable","name":"ErrCycleClosed","qualified_name":"ErrCycleClosed","file_path":"internal/usecase/payroll/cycle.go","language":"go","start_line":16,"end_line":16,"start_column":4,"end_column":58,"docstring":"ErrCycleClosed is returned when a cycle has already been finalized.","signature":"= errors.New(\"payroll cycle is closed\")","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"variable:9c3a0c9681a69504be5d3af6ddb5378c","kind":"variable","name":"file_payroll_v1_payroll_proto_goTypes","qualified_name":"file_payroll_v1_payroll_proto_goTypes","file_path":"internal/gen/payrollpb/payroll.pb.go","language":"go","start_line":132,"end_line":137,"start_column":4,"end_column":1,"docstring":null,"signature":"= []any{\n\t(*RunPayrollCycleRequest)(nil),\n\t(*RunPayrollCycleResponse)(nil),\n\t(*Payslip)(nil),\n\t(*Payro...","visibility":null,"is_exported":0,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} {"id":"variable:baa4aee814b380ae99f1b1829407b106","kind":"variable","name":"ErrNoEmployees","qualified_name":"ErrNoEmployees","file_path":"internal/usecase/payroll/cycle.go","language":"go","start_line":19,"end_line":19,"start_column":4,"end_column":72,"docstring":"ErrNoEmployees is returned when a cycle resolves to an empty roster.","signature":"= errors.New(\"payroll cycle has no active employees\")","visibility":null,"is_exported":1,"is_async":0,"is_static":0,"is_abstract":0,"decorators":null,"type_parameters":null,"return_type":null} -== edges (653) +== edges (633) {"source":"file:69f63e0581b8138f256b63d2aac60e8a","target":"module:1be454da0953954aef45c525ee8f8b51","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} {"source":"file:cmd/payrolld/main.go","target":"function:90eb51f51a3d9a397d4a53f26f2019aa","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null} {"source":"file:cmd/payrolld/main.go","target":"import:0a9ee69fa4c64208200c3bebcbbf0b75","kind":"contains","metadata":null,"line":null,"col":null,"provenance":null} @@ -846,26 +826,6 @@ {"source":"module:39730d6601ee76ddcd5e78d50abfe5a2","target":"import:fa23f47e6bf27f154f8044ad15346d18","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} {"source":"module:900ad55709598429050c36805696291a","target":"import:44b0fa4765bb8c262fb8b290698b9c05","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} {"source":"module:900ad55709598429050c36805696291a","target":"import:e2b363aa11b1fe2c65cb016ea68a0948","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:0414eb07a8b96d1a74af19b17a88be3d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:1dcd8cad196a824c650d48f180f6d822","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:1deff88eaf3b3b6189345ce5cf39f95f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:2b54c509c897b69fda45f099b03ddecc","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:314d7259385972532d50bfb35a88f851","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:452bab8507b181181f43618779fe1c9b","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:46dc118057024483ca51e11a06ae35b5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:5dcd3514b7efa1f5e466655bd84a5f3f","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:7ed76b62f3223020612f80cf9c2e6ee7","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:90eedb8d136d41001abe73a32e54de98","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:a141d7d33a5f910db19e688e88c75b19","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:b40d73f7b2cc84c05e79c724863974f7","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:b60f2c33d5eaa6738bcdf88143f099af","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:c58e8b67983f5b87ff8b159278954265","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:cf3f266e9ee5695886d6eff55fe33457","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:d08f890a663db7d10ae4f6cb67b95970","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a6bc3fe976757dd8aebda5a6c6475961","target":"function:f5bb323394b903d448de3194d3804241","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a883d05f3bb8edb262fd18c8c2849417","target":"function:4827032314863eb2b99f791098f8668c","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a883d05f3bb8edb262fd18c8c2849417","target":"function:8e554156c22d75e629b346ea4ed215d1","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} -{"source":"module:a883d05f3bb8edb262fd18c8c2849417","target":"function:c2b743a41c78c6d39eb292c4ecd1138d","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} {"source":"module:f8d63ed69e573ade27e2d1d4fc0ae8b4","target":"import:76a964b6134242af279f95c049ac84c5","kind":"contains","metadata":null,"line":null,"col":null,"provenance":"heuristic"} {"source":"route:internal/transport/httpapi/router.go:10:GET:/v1/payroll/cycles/{cycleID}","target":"method:8baae5c5fb59db7a88c19bb295635d18","kind":"references","metadata":"{\"confidence\":0.9,\"resolvedBy\":\"exact-match\",\"refName\":\"GetCycle\"}","line":10,"col":0,"provenance":null} {"source":"route:internal/transport/httpapi/router.go:11:GET:/v1/payroll/cycles/{cycleID}/payslips","target":"method:6e0fe8d0272ee955601347cf37b5a383","kind":"references","metadata":"{\"confidence\":0.7,\"resolvedBy\":\"exact-match\",\"refName\":\"ListPayslips\"}","line":11,"col":0,"provenance":null} @@ -1082,7 +1042,6 @@ {"from_node_id":"function:3de0ed87c97e45d857a0ea66c1b2e97d","reference_name":"Context","reference_kind":"references","line":58,"col":32,"candidates":null,"file_path":"internal/gen/fkit/employee/employee.go","language":"go","status":"failed","name_tail":"Context"} {"from_node_id":"function:3de0ed87c97e45d857a0ea66c1b2e97d","reference_name":"DB","reference_kind":"references","line":58,"col":49,"candidates":null,"file_path":"internal/gen/fkit/employee/employee.go","language":"go","status":"failed","name_tail":"DB"} {"from_node_id":"function:3de0ed87c97e45d857a0ea66c1b2e97d","reference_name":"db.ExecContext","reference_kind":"calls","line":60,"col":11,"candidates":null,"file_path":"internal/gen/fkit/employee/employee.go","language":"go","status":"failed","name_tail":"ExecContext","failure_reason":"unknown-receiver"} -{"from_node_id":"function:4827032314863eb2b99f791098f8668c","reference_name":"scripts/agent-eval/probe-allocation.mjs","reference_kind":"calls","line":75,"col":0,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"mjs"} {"from_node_id":"function:4bfd3234b60c7cb018e09fa29aea24df","reference_name":"Request","reference_kind":"references","line":16,"col":43,"candidates":null,"file_path":"internal/transport/httpapi/router.go","language":"go","status":"failed","name_tail":"Request"} {"from_node_id":"function:4bfd3234b60c7cb018e09fa29aea24df","reference_name":"ResponseWriter","reference_kind":"references","line":16,"col":19,"candidates":null,"file_path":"internal/transport/httpapi/router.go","language":"go","status":"failed","name_tail":"ResponseWriter"} {"from_node_id":"function:4bfd3234b60c7cb018e09fa29aea24df","reference_name":"http.StatusOK","reference_kind":"function_ref","line":17,"col":20,"candidates":null,"file_path":"internal/transport/httpapi/router.go","language":"go","status":"failed","name_tail":"StatusOK"} @@ -1500,6 +1459,7 @@ {"from_node_id":"module:900ad55709598429050c36805696291a","reference_name":"payslip_builder.go","reference_kind":"references","line":67,"col":54,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"go"} {"from_node_id":"module:a883d05f3bb8edb262fd18c8c2849417","reference_name":"docs/design/explore-budget-allocation.md","reference_kind":"references","line":82,"col":1,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"explore-budget-allocation.md"} {"from_node_id":"module:a883d05f3bb8edb262fd18c8c2849417","reference_name":"scripts/agent-eval/allocation-fixtures.js","reference_kind":"references","line":79,"col":49,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"allocation-fixtures.js"} +{"from_node_id":"module:a883d05f3bb8edb262fd18c8c2849417","reference_name":"scripts/agent-eval/probe-allocation.mjs","reference_kind":"calls","line":75,"col":0,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"mjs"} {"from_node_id":"module:f8d63ed69e573ade27e2d1d4fc0ae8b4","reference_name":"internal/gen/fkit/payroll/store.go","reference_kind":"references","line":87,"col":56,"candidates":null,"file_path":"README.md","language":"markdown","status":"failed","name_tail":"store.go"} {"from_node_id":"struct:09a019d5ab5eefb1f1bc3b25ad95d81c","reference_name":"Time","reference_kind":"references","line":38,"col":22,"candidates":null,"file_path":"internal/usecase/payroll/cycle.go","language":"go","status":"failed","name_tail":"Time"} {"from_node_id":"struct:13bfc2acbe1de6557e9a28c0be484ffa","reference_name":"DB","reference_kind":"references","line":15,"col":9,"candidates":null,"file_path":"internal/gen/fkit/payroll/store.go","language":"go","status":"failed","name_tail":"DB"} @@ -2274,7 +2234,7 @@ {"file_path":"internal/usecase/payroll/prorate.go","name":"to","kind":"param","node_id":null,"target_spec":null,"target_name":null,"exported_as":null,"export_form":null,"scope_start":34,"scope_end":39,"storage":null,"line":34} {"file_path":"internal/usecase/payroll/prorate.go","name":"window","kind":"local","node_id":null,"target_spec":null,"target_name":null,"exported_as":null,"export_form":null,"scope_start":11,"scope_end":24,"storage":null,"line":12} == files (21) -{"path":"README.md","language":"markdown","node_count":33} +{"path":"README.md","language":"markdown","node_count":13} {"path":"cmd/payrolld/main.go","language":"go","node_count":10} {"path":"internal/domain/payroll/payslip.go","language":"go","node_count":25} {"path":"internal/gen/fkit/employee/contract.go","language":"go","node_count":11} diff --git a/__tests__/integration/full-pipeline.test.ts b/__tests__/integration/full-pipeline.test.ts index 6d3209901..dbc8a1098 100644 --- a/__tests__/integration/full-pipeline.test.ts +++ b/__tests__/integration/full-pipeline.test.ts @@ -108,10 +108,11 @@ node scripts/release.mjs const guide = cg.searchNodes('Project Guide').find((r) => r.node.language === 'markdown'); expect(guide).toBeDefined(); - const releaseCommand = cg + // The command is section text and summary now, not its own node. + const releaseSection = cg .searchNodes('release.mjs') - .find((r) => r.node.language === 'markdown' && r.node.kind === 'function'); - expect(releaseCommand).toBeDefined(); + .find((r) => r.node.language === 'markdown' && r.node.kind === 'module'); + expect(releaseSection).toBeDefined(); const guideEdges = cg.getOutgoingEdges(guide!.node.id).filter((e) => e.kind === 'imports'); const guideTargets = guideEdges.map((e) => cg.getNode(e.target)); @@ -123,7 +124,7 @@ node scripts/release.mjs startLine: 1, }); - const commandEdges = cg.getOutgoingEdges(releaseCommand!.node.id).filter((e) => e.kind === 'calls'); + const commandEdges = cg.getOutgoingEdges(releaseSection!.node.id).filter((e) => e.kind === 'calls'); const commandTargets = commandEdges.map((e) => cg.getNode(e.target)?.filePath); expect(commandTargets).toContain('scripts/release.mjs'); } finally { @@ -196,10 +197,14 @@ See [Setup](docs/setup.md#install). try { await cg.indexAll(); - const p4s1Row = cg.searchNodes('P4-S1').find((r) => r.node.language === 'markdown'); - expect(p4s1Row?.node.kind).toBe('constant'); + // The table rows are section text now; the section owns their refs. + const p4s1Row = cg + .searchNodes('P4-S1') + .map((r) => r.node) + .find((n) => n.language === 'markdown' && n.qualifiedName === 'phases/phase4.md#fixed-script-templates'); + expect(p4s1Row?.kind).toBe('module'); - const edges = cg.getOutgoingEdges(p4s1Row!.node.id).filter((e) => e.kind === 'references'); + const edges = cg.getOutgoingEdges(p4s1Row!.id).filter((e) => e.kind === 'references'); const targets = edges.map((e) => cg.getNode(e.target)); expect(targets).toEqual( expect.arrayContaining([ diff --git a/__tests__/markdown-section-model.test.ts b/__tests__/markdown-section-model.test.ts new file mode 100644 index 000000000..13fdcc8ed --- /dev/null +++ b/__tests__/markdown-section-model.test.ts @@ -0,0 +1,144 @@ +/** + * Markdown keeps file and heading nodes plus the import nodes its links need. + * Commands, table rows and list items are text inside their section: the + * section docstring is searchable and already carries the text, so a node per + * command/row/item only added a `contains` edge and resolution work. A section + * command summary keeps a command past the docstring cap searchable, and a + * script a command runs still keeps its `calls` reference from the section. + */ +import { describe, it, expect } from 'vitest'; +import { extractFromSource } from '../src/extraction/tree-sitter'; +import type { MarkdownExtractorContext } from '../src/extraction/markdown-extractor'; + +const CONTEXT: MarkdownExtractorContext = { + knownFileNames: new Set(['guide.md', 'widget.ts', 'exists.sh', 'release.mjs']), + pruneMissingTargets: true, +}; + +const SOURCE = [ + '```sh', + 'npx create-app', + '```', + '', + '# Guide', + '', + 'Intro about the guide.', + '', + '## Usage', + '', + 'Run `npm install` first.', + '', + '```sh', + 'npm install', + 'bash scripts/exists.sh', + '```', + '', + '```', + '│ │ │', + 'PR opened / updated', + '```', + '', + '| Tool | Path |', + '| --- | --- |', + '| widget | src/widget.ts |', + '', + '- see src/widget.ts::createWidget', + '- XCG-713 quietbudgets feature', + '', + 'This paragraph is deliberately long so the section preview fills its six', + 'hundred character budget before the next table and fence. It repeats a', + 'little prose about the widget, its installation, and how the pipeline', + 'moves from the CLI to the widget renderer and back again, to push the', + 'useful rows well past the preview cap. The widget guide keeps going so the', + 'preview is full: install, configure, render, inspect, repeat. The widget', + 'guide keeps going so the preview is full: install, configure, render.', + 'It keeps going a little further so the table header and the late list', + 'item are clearly outside the six hundred character preview budget.', + '', + '| RollbackWindow | Description |', + '| --- | --- |', + '| CG-713 | quietbudgets feature |', + '', + '- CG-713 quietbudgets feature', + '', + '```sh', + 'npm install', + 'npm run build', + 'npm run lint', + 'npm run test', + 'npm run typecheck', + 'npm run format', + 'npm run docs', + 'npm run e2e', + 'npm run rescueall', + '```', + '', + '```', + 'node scripts/release.mjs', + 'codegraph init --no-seed', + '```', +].join('\n'); + +function extract() { + return extractFromSource('docs/guide.md', SOURCE, 'markdown', [], CONTEXT); +} + +describe('Markdown section model', () => { + it('keeps only file, heading and import nodes', () => { + const kinds = new Set(extract().nodes.map((node) => node.kind)); + expect(kinds.has('file')).toBe(true); + expect(kinds.has('module')).toBe(true); + expect(kinds.has('function')).toBe(false); + expect(kinds.has('constant')).toBe(false); + }); + + it('keeps commands searchable on the owning section', () => { + const usage = extract().nodes.find((node) => node.kind === 'module' && node.name === 'Usage'); + expect(usage?.docstring).toContain('commands: npm install, bash scripts/exists.sh'); + // Output and tree lines in an unlabeled fence are not commands. (The + // section's own text still carries them; only the summary must not.) + const summary = usage?.docstring?.split('commands:')[1] ?? ''; + expect(summary).not.toContain('│'); + expect(summary).not.toContain('PR opened'); + }); + + it('keeps a file-level command in the file digest', () => { + const file = extract().nodes.find((node) => node.kind === 'file'); + expect(file?.docstring).toContain('commands: npx create-app'); + }); + + it('keeps late rows, headers and commands searchable on the owning section', () => { + const result = extract(); + const usage = result.nodes.find((node) => node.kind === 'module' && node.name === 'Usage')!; + // The preview and the 8-command summary are bounded... + expect(usage.docstring).not.toContain('rescueall'); + expect(usage.docstring).not.toContain('RollbackWindow'); + expect(usage.docstring).not.toContain('no-seed'); + // ...while the search text keeps the late row, header, 9th command and the + // unrecognized unlabeled line. `XCG-713` is in the preview, so the late + // `CG-713` also proves the token-boundary dedupe. + expect(usage.searchText).toContain('RollbackWindow'); + expect(usage.searchText).toContain('CG-713'); + expect(usage.searchText).toContain('quietbudgets'); + expect(usage.searchText).toContain('rescueall'); + expect(usage.searchText).toContain('no-seed'); + }); + + it('keeps an unlabeled Node.js command as a script reference', () => { + const result = extract(); + const calls = result.unresolvedReferences.filter((ref) => ref.referenceKind === 'calls'); + expect(calls.some((ref) => ref.referenceName === 'docs/scripts/release.mjs')).toBe(true); + }); + + it('attaches command and table/list references to the section', () => { + const result = extract(); + const usage = result.nodes.find((node) => node.kind === 'module' && node.name === 'Usage')!; + const calls = result.unresolvedReferences.filter((ref) => ref.referenceKind === 'calls'); + expect(calls.some((ref) => ref.referenceName === 'docs/scripts/exists.sh' && ref.fromNodeId === usage.id)).toBe(true); + const refs = result.unresolvedReferences.filter( + (ref) => ref.referenceKind === 'references' && ref.referenceName.startsWith('docs/src/widget.ts') + ); + expect(refs.length).toBeGreaterThan(0); + expect(refs.every((ref) => ref.fromNodeId === usage.id)).toBe(true); + }); +}); diff --git a/scripts/dump-graph.mjs b/scripts/dump-graph.mjs index 830e3b6d6..d6b35aff3 100644 --- a/scripts/dump-graph.mjs +++ b/scripts/dump-graph.mjs @@ -32,7 +32,8 @@ const db = new DatabaseSync(dbPath, { readOnly: true }); function dump(title, sql) { const rows = db.prepare(sql).all(); - const lines = rows.map((r) => JSON.stringify(r, (key, value) => key === 'failure_reason' && value == null ? undefined : value)).sort(); + const lines = rows.map((r) => JSON.stringify(r, (key, value) => + (key === 'failure_reason' || key === 'search_text') && value == null ? undefined : value)).sort(); process.stdout.write(`== ${title} (${lines.length})\n`); for (const l of lines) process.stdout.write(l + '\n'); } @@ -40,7 +41,7 @@ function dump(title, sql) { dump( 'nodes', `SELECT id, kind, name, qualified_name, file_path, language, start_line, end_line, - start_column, end_column, docstring, signature, visibility, is_exported, + start_column, end_column, docstring, signature, search_text, visibility, is_exported, is_async, is_static, is_abstract, decorators, type_parameters, return_type FROM nodes` ); diff --git a/site/src/content/docs/reference/languages.md b/site/src/content/docs/reference/languages.md index fcf1be4f6..63819928c 100644 --- a/site/src/content/docs/reference/languages.md +++ b/site/src/content/docs/reference/languages.md @@ -43,7 +43,7 @@ A COBOL copybook named in an explore query prioritizes its indexed source and li | R | `.R`, `.r` | Full support (functions, S4/R5/R6 classes with methods, `library`/`require` imports, `source()` file references, call edges) | | Luau | `.luau` | Full support (Lua, plus typed signatures, `type` aliases, Roblox `require`) | | CFML | `.cfc`, `.cfm`, `.cfs` | Full support (tag-based ``/``/`` and bare-script `component { ... }` styles, `extends`/`implements`, embedded ``, calls written in tags such as ``, ``, `` and `#…#` expressions) | -| Markdown | `.md`, `.mdx`, `.markdown` | Documentation structure (headings, sections, local links, selected table rows/list items, shell command references) | +| Markdown | `.md`, `.mdx`, `.markdown` | Documentation structure (headings, sections and local links as nodes; tables, commands and list items as searchable section text) | Vue, Svelte and Astro files have one file node containing their component. Top-level script members belong to that component, while nested symbols retain their own parents. Vue `