sourcecode 0.29.0__tar.gz → 0.31.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. sourcecode-0.31.0/.agents/skills/source-command-gsd-join-discord/SKILL.md +24 -0
  2. sourcecode-0.31.0/.agents/skills/source-command-gsd-review-backlog/SKILL.md +63 -0
  3. sourcecode-0.31.0/.agents/skills/source-command-gsd-workstreams/SKILL.md +72 -0
  4. {sourcecode-0.29.0 → sourcecode-0.31.0}/PKG-INFO +1 -1
  5. {sourcecode-0.29.0 → sourcecode-0.31.0}/pyproject.toml +1 -1
  6. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/__init__.py +1 -1
  7. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/architecture_analyzer.py +9 -5
  8. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/architecture_summary.py +4 -8
  9. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/classifier.py +5 -1
  10. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/cli.py +24 -34
  11. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/confidence_analyzer.py +33 -20
  12. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/nodejs.py +60 -18
  13. sourcecode-0.31.0/src/sourcecode/entrypoint_classifier.py +106 -0
  14. sourcecode-0.31.0/src/sourcecode/file_classifier.py +215 -0
  15. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/prepare_context.py +12 -7
  16. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/schema.py +6 -4
  17. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/serializer.py +268 -87
  18. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/summarizer.py +10 -7
  19. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_architecture_analyzer.py +3 -2
  20. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_architecture_summary.py +11 -3
  21. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_classifier.py +17 -0
  22. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_nodejs.py +2 -2
  23. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_pipeline_integrity.py +20 -9
  24. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_real_projects.py +4 -2
  25. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_signal_hierarchy.py +5 -3
  26. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_summarizer.py +4 -4
  27. {sourcecode-0.29.0 → sourcecode-0.31.0}/.gitignore +0 -0
  28. {sourcecode-0.29.0 → sourcecode-0.31.0}/.ruff.toml +0 -0
  29. {sourcecode-0.29.0 → sourcecode-0.31.0}/CONTRIBUTING.md +0 -0
  30. {sourcecode-0.29.0 → sourcecode-0.31.0}/LICENSE +0 -0
  31. {sourcecode-0.29.0 → sourcecode-0.31.0}/README.md +0 -0
  32. {sourcecode-0.29.0 → sourcecode-0.31.0}/SECURITY.md +0 -0
  33. {sourcecode-0.29.0 → sourcecode-0.31.0}/docs/privacy.md +0 -0
  34. {sourcecode-0.29.0 → sourcecode-0.31.0}/docs/schema.md +0 -0
  35. {sourcecode-0.29.0 → sourcecode-0.31.0}/raw +0 -0
  36. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/code_notes_analyzer.py +0 -0
  37. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/context_summarizer.py +0 -0
  38. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/coverage_parser.py +0 -0
  39. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/dependency_analyzer.py +0 -0
  40. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/__init__.py +0 -0
  41. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/base.py +0 -0
  42. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
  43. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/dart.py +0 -0
  44. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/dotnet.py +0 -0
  45. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/elixir.py +0 -0
  46. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/go.py +0 -0
  47. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/heuristic.py +0 -0
  48. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/hybrid.py +0 -0
  49. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/java.py +0 -0
  50. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
  51. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/parsers.py +0 -0
  52. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/php.py +0 -0
  53. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/project.py +0 -0
  54. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/python.py +0 -0
  55. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/ruby.py +0 -0
  56. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/rust.py +0 -0
  57. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/systems.py +0 -0
  58. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/terraform.py +0 -0
  59. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/detectors/tooling.py +0 -0
  60. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/doc_analyzer.py +0 -0
  61. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/env_analyzer.py +0 -0
  62. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/git_analyzer.py +0 -0
  63. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/graph_analyzer.py +0 -0
  64. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/metrics_analyzer.py +0 -0
  65. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/redactor.py +0 -0
  66. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/relevance_scorer.py +0 -0
  67. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/runtime_classifier.py +0 -0
  68. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/scanner.py +0 -0
  69. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/semantic_analyzer.py +0 -0
  70. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/__init__.py +0 -0
  71. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/config.py +0 -0
  72. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/consent.py +0 -0
  73. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/events.py +0 -0
  74. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/filters.py +0 -0
  75. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/telemetry/transport.py +0 -0
  76. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/tree_utils.py +0 -0
  77. {sourcecode-0.29.0 → sourcecode-0.31.0}/src/sourcecode/workspace.py +0 -0
  78. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/__init__.py +0 -0
  79. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/conftest.py +0 -0
  80. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/coverage.xml +0 -0
  81. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
  82. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
  83. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
  84. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/go_service/go.mod +0 -0
  85. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/jacoco.xml +0 -0
  86. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/lcov.info +0 -0
  87. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
  88. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/nextjs_app/package.json +0 -0
  89. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
  90. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
  91. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
  92. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
  93. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
  94. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
  95. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_cli.py +0 -0
  96. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_code_notes_analyzer.py +0 -0
  97. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_coverage_parser.py +0 -0
  98. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_cross_consistency.py +0 -0
  99. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_dependency_analyzer_node_python.py +0 -0
  100. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
  101. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_dependency_schema.py +0 -0
  102. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_dotnet.py +0 -0
  103. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_go_rust_java.py +0 -0
  104. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_php_ruby_dart.py +0 -0
  105. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_python.py +0 -0
  106. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_universal_managed.py +0 -0
  107. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detector_universal_systems.py +0 -0
  108. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_detectors_base.py +0 -0
  109. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_doc_analyzer_jsdom.py +0 -0
  110. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_doc_analyzer_python.py +0 -0
  111. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_graph_analyzer_polyglot.py +0 -0
  112. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_graph_analyzer_python_node.py +0 -0
  113. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_graph_schema.py +0 -0
  114. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_hybrid_inference.py +0 -0
  115. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration.py +0 -0
  116. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_dependencies.py +0 -0
  117. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_detection.py +0 -0
  118. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_docs.py +0 -0
  119. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_graph_modules.py +0 -0
  120. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_lqn.py +0 -0
  121. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_metrics.py +0 -0
  122. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_multistack.py +0 -0
  123. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_semantics.py +0 -0
  124. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_integration_universal.py +0 -0
  125. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_metrics_analyzer.py +0 -0
  126. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_packaging.py +0 -0
  127. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_phase1_improvements.py +0 -0
  128. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_redactor.py +0 -0
  129. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_scanner.py +0 -0
  130. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_schema.py +0 -0
  131. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_schema_normalization.py +0 -0
  132. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_semantic_analyzer_node.py +0 -0
  133. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_semantic_analyzer_python.py +0 -0
  134. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_semantic_import_resolution.py +0 -0
  135. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_semantic_schema.py +0 -0
  136. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_telemetry.py +0 -0
  137. {sourcecode-0.29.0 → sourcecode-0.31.0}/tests/test_workspace_analyzer.py +0 -0
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: "source-command-gsd-join-discord"
3
+ description: "Join the GSD Discord community"
4
+ ---
5
+
6
+ # source-command-gsd-join-discord
7
+
8
+ Use this skill when the user asks to run the migrated source command `gsd-join-discord`.
9
+
10
+ ## Command Template
11
+
12
+ <objective>
13
+ Display the Discord invite link for the GSD community server.
14
+ </objective>
15
+
16
+ <output>
17
+ # Join the GSD Discord
18
+
19
+ Connect with other GSD users, get help, share what you're building, and stay updated.
20
+
21
+ **Invite link:** https://discord.gg/mYgfVNfA2r
22
+
23
+ Click the link or paste it into your browser to join.
24
+ </output>
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: "source-command-gsd-review-backlog"
3
+ description: "Review and promote backlog items to active milestone"
4
+ ---
5
+
6
+ # source-command-gsd-review-backlog
7
+
8
+ Use this skill when the user asks to run the migrated source command `gsd-review-backlog`.
9
+
10
+ ## Command Template
11
+
12
+ <objective>
13
+ Review all 999.x backlog items and optionally promote them into the active
14
+ milestone sequence or remove stale entries.
15
+ </objective>
16
+
17
+ <process>
18
+
19
+ 1. **List backlog items:**
20
+ ```bash
21
+ ls -d .planning/phases/999* 2>/dev/null || echo "No backlog items found"
22
+ ```
23
+
24
+ 2. **Read ROADMAP.md** and extract all 999.x phase entries:
25
+ ```bash
26
+ cat .planning/ROADMAP.md
27
+ ```
28
+ Show each backlog item with its description, any accumulated context (CONTEXT.md, RESEARCH.md), and creation date.
29
+
30
+ 3. **Present the list to the user** via AskUserQuestion:
31
+ - For each backlog item, show: phase number, description, accumulated artifacts
32
+ - Options per item: **Promote** (move to active), **Keep** (leave in backlog), **Remove** (delete)
33
+
34
+ 4. **For items to PROMOTE:**
35
+ - Find the next sequential phase number in the active milestone
36
+ - Rename the directory from `999.x-slug` to `{new_num}-slug`:
37
+ ```bash
38
+ NEW_NUM=$(node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" phase add "${DESCRIPTION}" --raw)
39
+ ```
40
+ - Move accumulated artifacts to the new phase directory
41
+ - Update ROADMAP.md: move the entry from `## Backlog` section to the active phase list
42
+ - Remove `(BACKLOG)` marker
43
+ - Add appropriate `**Depends on:**` field
44
+
45
+ 5. **For items to REMOVE:**
46
+ - Delete the phase directory
47
+ - Remove the entry from ROADMAP.md `## Backlog` section
48
+
49
+ 6. **Commit changes:**
50
+ ```bash
51
+ node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" commit "docs: review backlog — promoted N, removed M" --files .planning/ROADMAP.md
52
+ ```
53
+
54
+ 7. **Report summary:**
55
+ ```
56
+ ## 📋 Backlog Review Complete
57
+
58
+ Promoted: {list of promoted items with new phase numbers}
59
+ Kept: {list of items remaining in backlog}
60
+ Removed: {list of deleted items}
61
+ ```
62
+
63
+ </process>
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: "source-command-gsd-workstreams"
3
+ description: "Manage parallel workstreams — list, create, switch, status, progress, complete, and resume"
4
+ ---
5
+
6
+ # source-command-gsd-workstreams
7
+
8
+ Use this skill when the user asks to run the migrated source command `gsd-workstreams`.
9
+
10
+ ## Command Template
11
+
12
+ # /gsd-workstreams
13
+
14
+ Manage parallel workstreams for concurrent milestone work.
15
+
16
+ ## Usage
17
+
18
+ `/gsd-workstreams [subcommand] [args]`
19
+
20
+ ### Subcommands
21
+
22
+ | Command | Description |
23
+ |---------|-------------|
24
+ | `list` | List all workstreams with status |
25
+ | `create <name>` | Create a new workstream |
26
+ | `status <name>` | Detailed status for one workstream |
27
+ | `switch <name>` | Set active workstream |
28
+ | `progress` | Progress summary across all workstreams |
29
+ | `complete <name>` | Archive a completed workstream |
30
+ | `resume <name>` | Resume work in a workstream |
31
+
32
+ ## Step 1: Parse Subcommand
33
+
34
+ Parse the user's input to determine which workstream operation to perform.
35
+ If no subcommand given, default to `list`.
36
+
37
+ ## Step 2: Execute Operation
38
+
39
+ ### list
40
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream list --raw --cwd "$CWD"`
41
+ Display the workstreams in a table format showing name, status, current phase, and progress.
42
+
43
+ ### create
44
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream create <name> --raw --cwd "$CWD"`
45
+ After creation, display the new workstream path and suggest next steps:
46
+ - `/gsd-new-milestone --ws <name>` to set up the milestone
47
+
48
+ ### status
49
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream status <name> --raw --cwd "$CWD"`
50
+ Display detailed phase breakdown and state information.
51
+
52
+ ### switch
53
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream set <name> --raw --cwd "$CWD"`
54
+ Also set `GSD_WORKSTREAM` for the current session when the runtime supports it.
55
+ If the runtime exposes a session identifier, GSD also stores the active workstream
56
+ session-locally so concurrent sessions do not overwrite each other.
57
+
58
+ ### progress
59
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream progress --raw --cwd "$CWD"`
60
+ Display a progress overview across all workstreams.
61
+
62
+ ### complete
63
+ Run: `node "C:/Users/dominique.haroun_m3i/Documents/workspace/test-gsd/.Codex/get-shit-done/bin/gsd-tools.cjs" workstream complete <name> --raw --cwd "$CWD"`
64
+ Archive the workstream to milestones/.
65
+
66
+ ### resume
67
+ Set the workstream as active and suggest `/gsd-resume-work --ws <name>`.
68
+
69
+ ## Step 3: Display Results
70
+
71
+ Format the JSON output from gsd-tools into a human-readable display.
72
+ Include the `${GSD_WS}` flag in any routing suggestions.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 0.29.0
3
+ Version: 0.31.0
4
4
  Summary: Deterministic codebase context for AI coding agents
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sourcecode"
7
- version = "0.29.0"
7
+ version = "0.31.0"
8
8
  description = "Deterministic codebase context for AI coding agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """sourcecode — Genera mapas de contexto estructurado para agentes IA."""
2
2
 
3
- __version__ = "0.29.0"
3
+ __version__ = "0.31.0"
@@ -215,18 +215,22 @@ class ArchitectureAnalyzer:
215
215
  if pattern not in (None, "unknown", "flat"):
216
216
  if all_layers_weak:
217
217
  # Layers came from file-naming heuristic only, not directory structure
218
- confidence = "medium"
218
+ confidence = "low"
219
219
  limitations.append(
220
- "Patron inferido de nombres de archivo sin estructura de directorios confirmatoria"
220
+ "Low confidence inference: pattern inferred from filenames only, without import graph confirmation"
221
221
  )
222
222
  else:
223
- confidence = "high" if len(strong_domains) >= 3 else "medium"
223
+ confidence = "medium" if len(strong_domains) >= 3 else "low"
224
+ if graph is None:
225
+ limitations.append(
226
+ "Pattern not confirmed by module import graph; run with --graph-modules for structural validation"
227
+ )
224
228
  elif len(strong_domains) >= 1:
225
229
  confidence = "medium"
226
230
  else:
227
231
  confidence = "low"
228
232
 
229
- method = "graph+heuristic" if graph is not None else "heuristic"
233
+ method = "graph+structure" if graph is not None else "filesystem_inference"
230
234
 
231
235
  return ArchitectureAnalysis(
232
236
  requested=True,
@@ -339,7 +343,7 @@ class ArchitectureAnalyzer:
339
343
  best_matched = matched
340
344
 
341
345
  if best_score >= 2:
342
- layer_confidence: Literal["high", "medium", "low"] = "high" if best_score >= 3 else "medium"
346
+ layer_confidence: Literal["high", "medium", "low"] = "medium" if best_score >= 3 else "low"
343
347
  layers: list[ArchitectureLayer] = []
344
348
  for layer_key, matched_dirs in best_matched.items():
345
349
  matched_files = [
@@ -5,6 +5,7 @@ import re
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
8
+ from sourcecode.entrypoint_classifier import is_production_entry_point
8
9
  from sourcecode.schema import EntryPoint, SourceMap, StackDetection
9
10
  from sourcecode.tree_utils import flatten_file_tree
10
11
 
@@ -63,11 +64,8 @@ class ArchitectureSummarizer:
63
64
  entry for entry in sm.entry_points
64
65
  if not self._is_tooling_path(entry.path)
65
66
  and not self._is_auxiliary_path(entry.path)
66
- and entry.entrypoint_type not in ("benchmark", "example")
67
+ and is_production_entry_point(entry)
67
68
  ]
68
- if not entry_points:
69
- fallback = self._infer_fallback_entry_points(file_paths, sm.stacks)
70
- entry_points = fallback[:1]
71
69
 
72
70
  lang_lines: list[str] = []
73
71
  if entry_points:
@@ -280,8 +278,7 @@ class ArchitectureSummarizer:
280
278
  if modules:
281
279
  formatted = self._format_module_list([self._module_label(module) for module in modules])
282
280
  if formatted:
283
- lines.append(f"Orquesta modulos internos: {formatted}.")
284
- lines.append("Produce la salida principal del entry point JavaScript/TypeScript detectado.")
281
+ lines.append(f"Imports internos del entry point: {formatted}.")
285
282
  return lines
286
283
 
287
284
  def _summarize_java_entry(self, path: str, content: str, stacks: list[StackDetection]) -> list[str]:
@@ -344,8 +341,7 @@ class ArchitectureSummarizer:
344
341
  if internal:
345
342
  formatted = self._format_module_list([self._module_label(module) for module in internal])
346
343
  if formatted:
347
- lines.append(f"Orquesta paquetes internos: {formatted}.")
348
- lines.append("Produce la salida principal del binario Go detectado.")
344
+ lines.append(f"Imports internos del binario Go: {formatted}.")
349
345
  return lines
350
346
 
351
347
  def _describe_entry_point(self, entry_point: EntryPoint, project_type: str | None) -> str:
@@ -45,8 +45,12 @@ class TypeClassifier:
45
45
  primary_stack = self._select_primary_stack(enriched, project_type)
46
46
 
47
47
  final_stacks: list[StackDetection] = []
48
+ primary_assigned = False
48
49
  for stack in enriched:
49
- final_stacks.append(replace(stack, primary=(stack.stack == primary_stack)))
50
+ is_primary = stack.stack == primary_stack and not primary_assigned
51
+ if is_primary:
52
+ primary_assigned = True
53
+ final_stacks.append(replace(stack, primary=is_primary))
50
54
  return final_stacks, project_type
51
55
 
52
56
  def _enrich_stack(
@@ -6,9 +6,10 @@ import time
6
6
  from pathlib import Path
7
7
  from typing import Any, Optional, cast
8
8
 
9
- import typer
10
-
11
- from sourcecode import __version__
9
+ import typer
10
+
11
+ from sourcecode import __version__
12
+ from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
12
13
 
13
14
 
14
15
  # ---------------------------------------------------------------------------
@@ -117,11 +118,11 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
117
118
  )
118
119
 
119
120
  # overall:high requires at least one production entry point
120
- if cs.overall == "high":
121
- prod_eps = [
122
- ep for ep in sm.entry_points
123
- if ep.entrypoint_type in ("production", None)
124
- ]
121
+ if cs.overall == "high":
122
+ prod_eps = [
123
+ ep for ep in sm.entry_points
124
+ if is_production_entry_point(ep)
125
+ ]
125
126
  if not prod_eps and sm.entry_points:
126
127
  issues.append(
127
128
  "[coherence] overall=high but no production entry points exist — "
@@ -134,21 +135,7 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
134
135
  "[coherence] entry_point_confidence=high but entry_points is empty"
135
136
  )
136
137
 
137
- # Contradictory EP classification: EPs with entrypoint_type=benchmark must not
138
- # appear in agent_view output (checked post-facto via produced_by + type)
139
- benchmark_eps = [
140
- ep for ep in sm.entry_points
141
- if ep.entrypoint_type in ("benchmark", "example")
142
- ]
143
- if benchmark_eps and sm.entry_points and all(
144
- ep.entrypoint_type in ("benchmark", "example") for ep in sm.entry_points
145
- ):
146
- issues.append(
147
- f"[coherence] all {len(sm.entry_points)} entry point(s) are benchmark/example — "
148
- "no production entry detected; analysis_gaps should reflect impact=high"
149
- )
150
-
151
- return issues
138
+ return issues
152
139
 
153
140
  _HELP = """\
154
141
  Deterministic codebase context for AI coding agents.
@@ -909,11 +896,13 @@ def main(
909
896
  if dependency_analyzer is not None:
910
897
  from sourcecode.dependency_analyzer import _ROLE_PRIORITY
911
898
 
912
- primary_ecosystem = sm.stacks[0].stack if sm.stacks else ""
913
- direct_deps = [
914
- d for d in sm.dependencies
915
- if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
916
- ]
899
+ primary_ecosystem = sm.stacks[0].stack if sm.stacks else ""
900
+ direct_deps = [
901
+ d for d in sm.dependencies
902
+ if d.scope != "transitive" and d.source in {"manifest", "lockfile"}
903
+ and (d.role or "unknown") in {"runtime", "parsing", "serialization", "observability", "infra"}
904
+ and d.scope not in {"dev"}
905
+ ]
917
906
 
918
907
  def _dep_sort_key(d: Any) -> tuple[int, int, str]:
919
908
  role_order = _ROLE_PRIORITY.get(d.role or "runtime", 5)
@@ -993,12 +982,13 @@ def main(
993
982
  "example", "examples", "docs", "doc", "fixtures", "fixture",
994
983
  })
995
984
  for _ep in sm.entry_points:
996
- _ep_type = _ep.entrypoint_type
997
- _path_parts = _ep.path.replace("\\", "/").lower().split("/")
998
- _filtered = (
999
- _ep_type in ("benchmark", "example")
1000
- or any(p in _aux_parts for p in _path_parts)
1001
- )
985
+ _normalized_ep = normalize_entry_point(_ep)
986
+ _ep_type = _normalized_ep.entrypoint_type
987
+ _path_parts = _ep.path.replace("\\", "/").lower().split("/")
988
+ _filtered = (
989
+ _normalized_ep.classification != "production"
990
+ or any(p in _aux_parts for p in _path_parts)
991
+ )
1002
992
  if _filtered:
1003
993
  _trace.emit("output", "agent_view", "filter_ep",
1004
994
  target=_ep.path,
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  from pathlib import Path
13
13
  from typing import TYPE_CHECKING
14
14
 
15
+ from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
15
16
  from sourcecode.schema import AnalysisGap, ConfidenceSummary, SourceMap
16
17
 
17
18
  if TYPE_CHECKING:
@@ -59,8 +60,15 @@ class ConfidenceAnalyzer:
59
60
  hard_signals.append(sig)
60
61
 
61
62
  # ── Entry point signals ───────────────────────────────────────────────
62
- for ep in sm.entry_points:
63
- if ep.source in _HARD_SOURCES or ep.reason == "console_script":
63
+ normalized_entry_points = [normalize_entry_point(ep) for ep in sm.entry_points]
64
+
65
+ for ep in normalized_entry_points:
66
+ if ep.classification != "production":
67
+ sig = f"entry:{ep.path} ({ep.classification}, {ep.reason or ep.source})"
68
+ if sig not in ignored_signals:
69
+ ignored_signals.append(sig)
70
+ continue
71
+ if ep.source in _HARD_SOURCES or ep.reason == "console_script" or ep.runtime_relevance == "high":
64
72
  sig = f"entry:{ep.path} ({ep.reason or ep.source})"
65
73
  if sig not in hard_signals:
66
74
  hard_signals.append(sig)
@@ -95,13 +103,13 @@ class ConfidenceAnalyzer:
95
103
  anomalies.append("All stacks detected via heuristic only — no manifest found")
96
104
 
97
105
  # ── Anomaly: entry points all low-confidence ──────────────────────────
98
- if sm.entry_points and all(ep.confidence == "low" for ep in sm.entry_points):
106
+ if normalized_entry_points and all(ep.confidence == "low" for ep in normalized_entry_points):
99
107
  anomalies.append("All entry points are low-confidence (heuristic/code_signal only)")
100
108
 
101
109
  # ── Anomaly: all production EPs are convention-only (no manifest evidence) ──
102
110
  production_eps_check = [
103
- ep for ep in sm.entry_points
104
- if ep.entrypoint_type in ("production", None)
111
+ ep for ep in normalized_entry_points
112
+ if is_production_entry_point(ep)
105
113
  ]
106
114
  if production_eps_check and all(
107
115
  ep.source in ("convention", "heuristic") or ep.reason in ("convention", "entry_file_pattern")
@@ -113,40 +121,40 @@ class ConfidenceAnalyzer:
113
121
  )
114
122
 
115
123
  # ── Anomaly: no production entry points ───────────────────────────────
116
- if sm.entry_points:
124
+ if normalized_entry_points:
117
125
  production_eps = [
118
- ep for ep in sm.entry_points
119
- if ep.entrypoint_type in ("production", None)
126
+ ep for ep in normalized_entry_points
127
+ if is_production_entry_point(ep)
120
128
  ]
121
129
  if not production_eps:
122
130
  anomalies.append(
123
- "No production entry points — all detected entries are dev/benchmark/example"
131
+ "No production entry points — all detected entries are development/auxiliary"
124
132
  )
125
133
 
126
134
  # ── Gaps ──────────────────────────────────────────────────────────────
127
- if not sm.entry_points:
135
+ if not normalized_entry_points:
128
136
  gaps.append(AnalysisGap(
129
137
  area="entry_points",
130
- reason="No entry point detected project may use non-standard structure or be a library",
138
+ reason="Critical: no runtime entrypoint detected; system cannot be executed without manual inference",
131
139
  impact="high",
132
140
  ))
133
141
  elif all(
134
- ep.entrypoint_type in ("benchmark", "example", "development")
135
- for ep in sm.entry_points
142
+ ep.classification in ("development", "auxiliary")
143
+ for ep in normalized_entry_points
136
144
  ):
137
145
  gaps.append(AnalysisGap(
138
146
  area="entry_points",
139
147
  reason=(
140
- "All detected entry points are auxiliary (benchmark/example/dev) "
141
- "no production entry point found. Verify project has a 'start'/'serve' "
142
- "script or production binary."
148
+ "Critical: no production runtime entrypoint detected; detected entries are "
149
+ "development or auxiliary only. Add/verify a start/serve script, CLI bin, "
150
+ "or server bootstrap before using this context for automation."
143
151
  ),
144
152
  impact="high",
145
153
  ))
146
- elif all(ep.confidence == "low" for ep in sm.entry_points):
154
+ elif all(ep.confidence == "low" for ep in normalized_entry_points):
147
155
  gaps.append(AnalysisGap(
148
156
  area="entry_points",
149
- reason="Entry points inferred from code patterns only, no manifest declaration found",
157
+ reason="Entry points inferred from code patterns only; no manifest script, CLI bin, or server bootstrap declaration found",
150
158
  impact="medium",
151
159
  ))
152
160
 
@@ -196,12 +204,17 @@ class ConfidenceAnalyzer:
196
204
  # Entry points: only consider production EPs for confidence scoring.
197
205
  # Benchmark/example/dev-only entries are not evidence of production readiness.
198
206
  production_eps = [
199
- ep for ep in sm.entry_points
200
- if ep.entrypoint_type in ("production", None)
207
+ ep for ep in normalized_entry_points
208
+ if is_production_entry_point(ep)
201
209
  ]
202
210
  ep_conf = _max_confidence([ep.confidence for ep in production_eps] or ["low"])
203
211
  overall = _min_confidence([stack_conf, ep_conf])
204
212
 
213
+ if normalized_entry_points and not production_eps:
214
+ overall = "low"
215
+ elif production_eps and all(ep.runtime_relevance == "low" for ep in production_eps):
216
+ overall = _min_confidence([overall, "low"])
217
+
205
218
  # Factor in architecture confidence when available
206
219
  arch = sm.architecture
207
220
  if arch is not None and arch.requested:
@@ -58,7 +58,7 @@ class NodejsDetector(AbstractDetector):
58
58
 
59
59
  from sourcecode.detectors.hybrid import merge_framework_detections, scan_for_frameworks
60
60
 
61
- dependency_names = self._collect_dependency_names(package_json)
61
+ dependency_names = self._collect_dependency_names(package_json, runtime_only=True)
62
62
  seen_fw: set[str] = set()
63
63
  manifest_frameworks = []
64
64
  for pkg_name, label in _FRAMEWORK_MAP.items():
@@ -98,9 +98,17 @@ class NodejsDetector(AbstractDetector):
98
98
  signals.append("monorepo:npm-workspaces")
99
99
  return signals
100
100
 
101
- def _collect_dependency_names(self, package_json: dict[str, Any]) -> set[str]:
101
+ def _collect_dependency_names(
102
+ self,
103
+ package_json: dict[str, Any],
104
+ *,
105
+ runtime_only: bool = False,
106
+ ) -> set[str]:
102
107
  names: set[str] = set()
103
- for field in ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies"):
108
+ fields = ("dependencies", "peerDependencies", "optionalDependencies")
109
+ if not runtime_only:
110
+ fields = fields + ("devDependencies",)
111
+ for field in fields:
104
112
  raw = package_json.get(field, {})
105
113
  if isinstance(raw, dict):
106
114
  names.update(str(name) for name in raw)
@@ -125,6 +133,9 @@ class NodejsDetector(AbstractDetector):
125
133
  "playground", "playgrounds",
126
134
  "fixture", "fixtures",
127
135
  "sandbox", "e2e", "docs",
136
+ "test", "tests", "__tests__", "spec", "specs",
137
+ "scripts", "script", "tools", "tooling", "ci",
138
+ ".storybook", "storybook",
128
139
  })
129
140
 
130
141
  def _collect_entry_points(
@@ -144,19 +155,20 @@ class NodejsDetector(AbstractDetector):
144
155
  continue
145
156
  # Extract file path from script command
146
157
  path = self._extract_script_path(script_cmd, context)
158
+ if path is None:
159
+ path = self._infer_tool_script_path(script_name, script_cmd, context)
147
160
  if path and path not in seen and path_exists_in_tree(context.file_tree, path):
148
161
  seen.add(path)
149
- if not self._is_auxiliary_path(path):
150
- entry_points.append(EntryPoint(
151
- path=path,
152
- stack="nodejs",
153
- kind=kind,
154
- source="package.json#scripts",
155
- confidence="high",
156
- reason=f"script:{script_name}",
157
- evidence=f"scripts.{script_name} = {script_cmd!r:.80}",
158
- entrypoint_type=ep_type,
159
- ))
162
+ entry_points.append(EntryPoint(
163
+ path=path,
164
+ stack="nodejs",
165
+ kind=kind,
166
+ source="package.json#scripts",
167
+ confidence="high",
168
+ reason=f"script:{script_name}",
169
+ evidence=f"scripts.{script_name} = {script_cmd!r:.80}",
170
+ entrypoint_type=self._path_entrypoint_type(path, fallback=ep_type),
171
+ ))
160
172
 
161
173
  # Priority 2: package.json bin — CLI production entry points
162
174
  bin_field = package_json.get("bin")
@@ -233,7 +245,7 @@ class NodejsDetector(AbstractDetector):
233
245
  def _classify_script(self, script_name: str) -> tuple[str | None, str]:
234
246
  """Map script name → (entrypoint_type, kind). Returns (None, '') to skip."""
235
247
  lower = script_name.lower()
236
- if lower in ("start", "serve"):
248
+ if lower in ("start", "serve", "server"):
237
249
  return "production", "server"
238
250
  if lower in ("dev", "develop", "watch"):
239
251
  return "development", "server"
@@ -243,6 +255,12 @@ class NodejsDetector(AbstractDetector):
243
255
  return "benchmark", "script"
244
256
  if lower.startswith("example") or lower.startswith("demo"):
245
257
  return "example", "script"
258
+ if lower in {"docs", "doc", "storybook", "playground"} or any(
259
+ marker in lower for marker in ("rspress", "vite", "storybook", "playground")
260
+ ):
261
+ return "development", "server"
262
+ if lower in {"test", "e2e", "spec", "lint", "format", "typecheck", "build"}:
263
+ return "development", "script"
246
264
  return None, ""
247
265
 
248
266
  def _extract_script_path(self, cmd: str, context: DetectionContext) -> str | None:
@@ -264,12 +282,36 @@ class NodejsDetector(AbstractDetector):
264
282
  return p
265
283
  return None
266
284
 
285
+ def _infer_tool_script_path(
286
+ self,
287
+ script_name: str,
288
+ script_cmd: str,
289
+ context: DetectionContext,
290
+ ) -> str | None:
291
+ text = f"{script_name} {script_cmd}".lower()
292
+ candidates: list[str] = []
293
+ if "rspress" in text or "docs" in text or "doc" in text:
294
+ candidates.extend(["docs/rspress.mjs", "docs/rspress.config.mjs"])
295
+ if "storybook" in text:
296
+ candidates.extend([".storybook/main.js", ".storybook/main.ts"])
297
+ if "vite" in text or "playground" in text:
298
+ candidates.extend(["playground/vite.config.ts", "vite.config.ts"])
299
+ for candidate in candidates:
300
+ if path_exists_in_tree(context.file_tree, candidate):
301
+ return candidate
302
+ return None
303
+
267
304
  def _is_auxiliary_path(self, path: str) -> bool:
268
305
  norm = path.replace("\\", "/")
269
306
  parts = norm.split("/")
270
307
  return any(p.lower() in self._AUXILIARY_DIRS for p in parts)
271
308
 
272
- def _path_entrypoint_type(self, path: str) -> str:
273
- if self._is_auxiliary_path(path):
309
+ def _path_entrypoint_type(self, path: str, *, fallback: str = "production") -> str:
310
+ parts = {p.lower() for p in path.replace("\\", "/").split("/")}
311
+ if parts & {"benchmark", "benchmarks", "bench", "benches"}:
312
+ return "benchmark"
313
+ if parts & {"example", "examples", "demo", "demos", "fixture", "fixtures"}:
274
314
  return "example"
275
- return "production"
315
+ if self._is_auxiliary_path(path):
316
+ return "development"
317
+ return fallback