sourcecode 0.28.0__tar.gz → 0.30.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.
- sourcecode-0.30.0/.agents/skills/source-command-gsd-join-discord/SKILL.md +24 -0
- sourcecode-0.30.0/.agents/skills/source-command-gsd-review-backlog/SKILL.md +63 -0
- sourcecode-0.30.0/.agents/skills/source-command-gsd-workstreams/SKILL.md +72 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/PKG-INFO +1 -1
- {sourcecode-0.28.0 → sourcecode-0.30.0}/pyproject.toml +1 -1
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/__init__.py +1 -1
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/architecture_analyzer.py +76 -11
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/architecture_summary.py +4 -8
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/classifier.py +5 -1
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/cli.py +193 -3
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/confidence_analyzer.py +72 -7
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/heuristic.py +19 -1
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/nodejs.py +70 -26
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/project.py +7 -0
- sourcecode-0.30.0/src/sourcecode/entrypoint_classifier.py +106 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/env_analyzer.py +25 -13
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/git_analyzer.py +57 -7
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/schema.py +31 -3
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/serializer.py +83 -58
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_architecture_summary.py +11 -3
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_classifier.py +17 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detectors_base.py +10 -9
- sourcecode-0.30.0/tests/test_pipeline_integrity.py +452 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_real_projects.py +4 -2
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_signal_hierarchy.py +5 -3
- {sourcecode-0.28.0 → sourcecode-0.30.0}/.gitignore +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/.ruff.toml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/CONTRIBUTING.md +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/LICENSE +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/README.md +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/SECURITY.md +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/docs/privacy.md +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/docs/schema.md +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/raw +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/redactor.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/scanner.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/src/sourcecode/workspace.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/__init__.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/conftest.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/coverage.xml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/fastapi_app/pyproject.toml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/fastapi_app/src/main.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/go_service/cmd/api/main.go +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/go_service/go.mod +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/jacoco.xml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/lcov.info +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/nextjs_app/app/page.tsx +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/nextjs_app/package.json +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/nextjs_app/pnpm-lock.yaml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/pnpm_monorepo/apps/web/app/page.tsx +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/pnpm_monorepo/apps/web/package.json +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/pnpm_monorepo/packages/api/main.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/pnpm_monorepo/packages/api/pyproject.toml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/fixtures/pnpm_monorepo/pnpm-workspace.yaml +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_architecture_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_cli.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_code_notes_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_coverage_parser.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_cross_consistency.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_dependency_analyzer_node_python.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_dependency_analyzer_polyglot.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_dependency_schema.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_dotnet.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_go_rust_java.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_nodejs.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_php_ruby_dart.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_python.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_universal_managed.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_detector_universal_systems.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_doc_analyzer_jsdom.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_doc_analyzer_python.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_graph_analyzer_polyglot.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_graph_analyzer_python_node.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_graph_schema.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_hybrid_inference.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_dependencies.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_detection.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_docs.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_graph_modules.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_lqn.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_metrics.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_multistack.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_semantics.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_integration_universal.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_metrics_analyzer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_packaging.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_phase1_improvements.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_redactor.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_scanner.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_schema.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_schema_normalization.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_semantic_analyzer_node.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_semantic_analyzer_python.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_semantic_import_resolution.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_semantic_schema.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_summarizer.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.0}/tests/test_telemetry.py +0 -0
- {sourcecode-0.28.0 → sourcecode-0.30.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.
|
|
@@ -12,6 +12,10 @@ from sourcecode.schema import (
|
|
|
12
12
|
SourceMap,
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
_WORKSPACE_CONFIG_FILES: frozenset[str] = frozenset({
|
|
16
|
+
"turbo.json", "nx.json", "pnpm-workspace.yaml", "lerna.json", "rush.json",
|
|
17
|
+
})
|
|
18
|
+
|
|
15
19
|
_TOOLING_PREFIXES = (
|
|
16
20
|
".claude/",
|
|
17
21
|
".vscode/",
|
|
@@ -34,6 +38,18 @@ _CODE_EXTENSIONS = {
|
|
|
34
38
|
_GENERIC_NAMES = {"utils", "helpers", "common", "shared", "misc", "core", "root", ""}
|
|
35
39
|
|
|
36
40
|
_TEST_DIRS: frozenset[str] = frozenset({"tests", "test", "spec", "specs", "__tests__", "e2e"})
|
|
41
|
+
_BENCHMARK_DIRS: frozenset[str] = frozenset({
|
|
42
|
+
"benchmark", "benchmarks", "bench",
|
|
43
|
+
"example", "examples",
|
|
44
|
+
"demo", "demos",
|
|
45
|
+
"playground", "playgrounds",
|
|
46
|
+
"fixture", "fixtures",
|
|
47
|
+
"sandbox",
|
|
48
|
+
})
|
|
49
|
+
_DOCS_DIRS: frozenset[str] = frozenset({"docs", "doc", "documentation", "wiki"})
|
|
50
|
+
_TOOLING_DIRS: frozenset[str] = frozenset({"scripts", "script", "tools", "tool", "ci"})
|
|
51
|
+
# All dirs that are not part of the runtime source architecture
|
|
52
|
+
_NON_SOURCE_DIRS: frozenset[str] = _TEST_DIRS | _BENCHMARK_DIRS | _DOCS_DIRS | _TOOLING_DIRS
|
|
37
53
|
|
|
38
54
|
# Exact file stems that signal a specific architectural layer
|
|
39
55
|
_LAYER_STEM_EXACT: dict[str, str] = {
|
|
@@ -177,15 +193,35 @@ class ArchitectureAnalyzer:
|
|
|
177
193
|
elif pattern == "unknown":
|
|
178
194
|
limitations.append("Patron de capas no reconocido: estructura de directorios sin senales claras")
|
|
179
195
|
|
|
196
|
+
# Step 3b: monorepo override — workspace config is hard evidence
|
|
197
|
+
if self._has_workspace_config(sm.file_paths) and pattern not in (
|
|
198
|
+
"monorepo", "cqrs", "clean", "onion", "hexagonal"
|
|
199
|
+
):
|
|
200
|
+
mono_layers = self._detect_monorepo_packages(filtered)
|
|
201
|
+
if mono_layers or pattern in (None, "unknown", "flat", "modular", "layered"):
|
|
202
|
+
pattern = "monorepo"
|
|
203
|
+
layers = mono_layers
|
|
204
|
+
limitations.append(
|
|
205
|
+
"Workspace config detectado — arquitectura refleja topologia de paquetes"
|
|
206
|
+
)
|
|
207
|
+
|
|
180
208
|
# Step 4: bounded context inference
|
|
181
209
|
bounded_contexts = self._infer_bounded_contexts(domains, graph)
|
|
182
210
|
|
|
183
|
-
# Overall confidence
|
|
211
|
+
# Overall confidence — based on domain quality, not raw count
|
|
184
212
|
confidence: Literal["high", "medium", "low"]
|
|
213
|
+
strong_domains = [d for d in domains if d.confidence in ("high", "medium")]
|
|
214
|
+
all_layers_weak = layers and all(l.confidence == "low" for l in layers)
|
|
185
215
|
if pattern not in (None, "unknown", "flat"):
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
if all_layers_weak:
|
|
217
|
+
# Layers came from file-naming heuristic only, not directory structure
|
|
218
|
+
confidence = "medium"
|
|
219
|
+
limitations.append(
|
|
220
|
+
"Patron inferido de nombres de archivo — sin estructura de directorios confirmatoria"
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
confidence = "high" if len(strong_domains) >= 3 else "medium"
|
|
224
|
+
elif len(strong_domains) >= 1:
|
|
189
225
|
confidence = "medium"
|
|
190
226
|
else:
|
|
191
227
|
confidence = "low"
|
|
@@ -217,6 +253,10 @@ class ArchitectureAnalyzer:
|
|
|
217
253
|
norm = p.replace("\\", "/")
|
|
218
254
|
if self._is_tooling(norm):
|
|
219
255
|
continue
|
|
256
|
+
# Exclude non-source dirs at every path segment (benchmarks, docs, tests, scripts…)
|
|
257
|
+
parts = norm.split("/")
|
|
258
|
+
if any(part.lower() in _NON_SOURCE_DIRS for part in parts[:-1]):
|
|
259
|
+
continue
|
|
220
260
|
ext = Path(norm).suffix.lower()
|
|
221
261
|
if ext not in _CODE_EXTENSIONS:
|
|
222
262
|
continue
|
|
@@ -250,6 +290,8 @@ class ArchitectureAnalyzer:
|
|
|
250
290
|
for name, files in groups.items():
|
|
251
291
|
if len(files) < 2:
|
|
252
292
|
continue
|
|
293
|
+
if name.lower() in _NON_SOURCE_DIRS:
|
|
294
|
+
continue
|
|
253
295
|
role = DOMAIN_ROLES.get(name, "")
|
|
254
296
|
domain_confidence: Literal["high", "medium", "low"]
|
|
255
297
|
if name in DOMAIN_ROLES:
|
|
@@ -262,10 +304,10 @@ class ArchitectureAnalyzer:
|
|
|
262
304
|
return domains
|
|
263
305
|
|
|
264
306
|
def _detect_layers(self, paths: list[str]) -> tuple[str, list[ArchitectureLayer]]:
|
|
265
|
-
# Exclude
|
|
307
|
+
# Exclude non-source paths (tests, benchmarks, docs, tooling) from layer scoring
|
|
266
308
|
source_paths = [
|
|
267
309
|
p for p in paths
|
|
268
|
-
if not any(part.lower() in
|
|
310
|
+
if not any(part.lower() in _NON_SOURCE_DIRS for part in p.replace("\\", "/").split("/"))
|
|
269
311
|
]
|
|
270
312
|
if not source_paths:
|
|
271
313
|
return "unknown", []
|
|
@@ -360,7 +402,7 @@ class ArchitectureAnalyzer:
|
|
|
360
402
|
parts = p.replace("\\", "/").split("/")
|
|
361
403
|
if len(parts) >= 2 and parts[-1].lower() in _ENTRY_FILES:
|
|
362
404
|
top = parts[0]
|
|
363
|
-
if top.lower() not in _SRC_TRANSPARENT and top.lower() not in
|
|
405
|
+
if top.lower() not in _SRC_TRANSPARENT and top.lower() not in _NON_SOURCE_DIRS:
|
|
364
406
|
entry_dirs.setdefault(top, []).append(p)
|
|
365
407
|
if len(entry_dirs) >= 4:
|
|
366
408
|
return "microservices", [
|
|
@@ -394,7 +436,7 @@ class ArchitectureAnalyzer:
|
|
|
394
436
|
non_empty = {k: v for k, v in layer_files.items() if v}
|
|
395
437
|
if len(non_empty) >= 2:
|
|
396
438
|
return "layered", [
|
|
397
|
-
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="
|
|
439
|
+
ArchitectureLayer(name=k, pattern="layered", files=v, confidence="low")
|
|
398
440
|
for k, v in non_empty.items()
|
|
399
441
|
]
|
|
400
442
|
return None
|
|
@@ -412,19 +454,42 @@ class ArchitectureAnalyzer:
|
|
|
412
454
|
parts = p.replace("\\", "/").split("/")
|
|
413
455
|
for part in parts[:-1]:
|
|
414
456
|
if (part not in _SRC_TRANSPARENT
|
|
415
|
-
and part.lower() not in
|
|
457
|
+
and part.lower() not in _NON_SOURCE_DIRS
|
|
416
458
|
and part.lower() not in _GENERIC_NAMES):
|
|
417
459
|
module_files.setdefault(part, []).append(p)
|
|
418
460
|
break
|
|
419
461
|
|
|
420
|
-
meaningful = {k: v for k, v in module_files.items() if len(v) >=
|
|
462
|
+
meaningful = {k: v for k, v in module_files.items() if len(v) >= 3}
|
|
421
463
|
if len(meaningful) >= 2:
|
|
422
464
|
return "modular", [
|
|
423
|
-
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="
|
|
465
|
+
ArchitectureLayer(name=k, pattern="modular", files=v, confidence="low")
|
|
424
466
|
for k, v in meaningful.items()
|
|
425
467
|
]
|
|
426
468
|
return None
|
|
427
469
|
|
|
470
|
+
def _has_workspace_config(self, file_paths: list[str]) -> bool:
|
|
471
|
+
for path in file_paths:
|
|
472
|
+
parts = path.replace("\\", "/").split("/")
|
|
473
|
+
if len(parts) == 1 and parts[0] in _WORKSPACE_CONFIG_FILES:
|
|
474
|
+
return True
|
|
475
|
+
return False
|
|
476
|
+
|
|
477
|
+
def _detect_monorepo_packages(self, paths: list[str]) -> list[ArchitectureLayer]:
|
|
478
|
+
"""Find workspace packages (packages/*, apps/*, libs/*) in a monorepo."""
|
|
479
|
+
_WORKSPACE_ROOTS = {"packages", "apps", "libs", "applications"}
|
|
480
|
+
groups: dict[str, list[str]] = {}
|
|
481
|
+
for p in paths:
|
|
482
|
+
parts = p.replace("\\", "/").split("/")
|
|
483
|
+
if len(parts) >= 2 and parts[0].lower() in _WORKSPACE_ROOTS:
|
|
484
|
+
key = f"{parts[0]}/{parts[1]}"
|
|
485
|
+
groups.setdefault(key, []).append(p)
|
|
486
|
+
result = [
|
|
487
|
+
ArchitectureLayer(name=k, pattern="monorepo", files=v, confidence="medium")
|
|
488
|
+
for k, v in groups.items()
|
|
489
|
+
if len(v) >= 2
|
|
490
|
+
]
|
|
491
|
+
return result[:16]
|
|
492
|
+
|
|
428
493
|
def _infer_bounded_contexts(
|
|
429
494
|
self,
|
|
430
495
|
domains: list[ArchitectureDomain],
|
|
@@ -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
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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(
|
|
@@ -1,13 +1,141 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
import time
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any, Optional, cast
|
|
7
8
|
|
|
8
|
-
import typer
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
from sourcecode import __version__
|
|
12
|
+
from sourcecode.entrypoint_classifier import is_production_entry_point, normalize_entry_point
|
|
9
13
|
|
|
10
|
-
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Analyzer fingerprints — short hashes of each analyzer's key rule constants.
|
|
17
|
+
# A change in heuristics, filter lists, or pattern maps changes the hash,
|
|
18
|
+
# making it immediately visible that two runs used different rule versions
|
|
19
|
+
# even if the semver string is the same.
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
def _fingerprint(*objects: object) -> str:
|
|
23
|
+
raw = json.dumps([repr(o) for o in objects], sort_keys=True)
|
|
24
|
+
return hashlib.sha256(raw.encode()).hexdigest()[:8]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _compute_analyzer_fingerprints() -> dict[str, str]:
|
|
28
|
+
from sourcecode.detectors.heuristic import (
|
|
29
|
+
_AUXILIARY_DIRS as _HEUR_AUX,
|
|
30
|
+
_ENTRYPOINT_NAMES,
|
|
31
|
+
_EXTENSION_MAP,
|
|
32
|
+
)
|
|
33
|
+
from sourcecode.detectors.nodejs import _FRAMEWORK_MAP, NodejsDetector
|
|
34
|
+
from sourcecode.confidence_analyzer import (
|
|
35
|
+
_AUXILIARY_DIR_PREFIXES,
|
|
36
|
+
_HARD_SOURCES,
|
|
37
|
+
_SOFT_SOURCES,
|
|
38
|
+
)
|
|
39
|
+
from sourcecode.architecture_analyzer import (
|
|
40
|
+
_BENCHMARK_DIRS,
|
|
41
|
+
_NON_SOURCE_DIRS,
|
|
42
|
+
LAYER_PATTERNS,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"heuristic": _fingerprint(_EXTENSION_MAP, _ENTRYPOINT_NAMES, sorted(_HEUR_AUX)),
|
|
47
|
+
"nodejs": _fingerprint(_FRAMEWORK_MAP, sorted(NodejsDetector._AUXILIARY_DIRS)),
|
|
48
|
+
"confidence": _fingerprint(sorted(_AUXILIARY_DIR_PREFIXES), sorted(_HARD_SOURCES), sorted(_SOFT_SOURCES)),
|
|
49
|
+
"architecture": _fingerprint(sorted(_BENCHMARK_DIRS), sorted(_NON_SOURCE_DIRS), list(LAYER_PATTERNS.keys())),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
# Pipeline trace collector
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
class _TraceCollector:
|
|
58
|
+
"""Lightweight collector for pipeline trace events."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, enabled: bool = False) -> None:
|
|
61
|
+
self._enabled = enabled
|
|
62
|
+
self._events: list[dict[str, Any]] = []
|
|
63
|
+
|
|
64
|
+
def emit(
|
|
65
|
+
self,
|
|
66
|
+
stage: str,
|
|
67
|
+
component: str,
|
|
68
|
+
action: str,
|
|
69
|
+
target: Optional[str] = None,
|
|
70
|
+
reason: Optional[str] = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
if not self._enabled:
|
|
73
|
+
return
|
|
74
|
+
self._events.append({
|
|
75
|
+
"stage": stage,
|
|
76
|
+
"component": component,
|
|
77
|
+
"action": action,
|
|
78
|
+
**({"target": target} if target else {}),
|
|
79
|
+
**({"reason": reason} if reason else {}),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
def build_trace(self) -> "PipelineTrace":
|
|
83
|
+
from sourcecode.schema import PipelineEvent, PipelineTrace
|
|
84
|
+
events = [
|
|
85
|
+
PipelineEvent(
|
|
86
|
+
stage=e["stage"],
|
|
87
|
+
component=e["component"],
|
|
88
|
+
action=e["action"],
|
|
89
|
+
target=e.get("target"),
|
|
90
|
+
reason=e.get("reason"),
|
|
91
|
+
)
|
|
92
|
+
for e in self._events
|
|
93
|
+
]
|
|
94
|
+
return PipelineTrace(requested=True, events=events)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# E2E pipeline coherence check
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[name-defined]
|
|
102
|
+
"""Verify no contradictory states exist between analyzers.
|
|
103
|
+
|
|
104
|
+
Returns a list of human-readable violation strings (empty when clean).
|
|
105
|
+
These are emitted to stderr as [coherence] warnings — never abort a run.
|
|
106
|
+
"""
|
|
107
|
+
issues: list[str] = []
|
|
108
|
+
cs = sm.confidence_summary
|
|
109
|
+
|
|
110
|
+
if cs is not None:
|
|
111
|
+
# overall:high requires at least one manifest-detected stack
|
|
112
|
+
if cs.overall == "high":
|
|
113
|
+
manifest_stacks = [s for s in sm.stacks if s.detection_method != "heuristic"]
|
|
114
|
+
if not manifest_stacks:
|
|
115
|
+
issues.append(
|
|
116
|
+
"[coherence] overall=high but all stacks are heuristic — "
|
|
117
|
+
"downgrade not applied; check confidence_analyzer"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# overall:high requires at least one production entry point
|
|
121
|
+
if cs.overall == "high":
|
|
122
|
+
prod_eps = [
|
|
123
|
+
ep for ep in sm.entry_points
|
|
124
|
+
if is_production_entry_point(ep)
|
|
125
|
+
]
|
|
126
|
+
if not prod_eps and sm.entry_points:
|
|
127
|
+
issues.append(
|
|
128
|
+
"[coherence] overall=high but no production entry points exist — "
|
|
129
|
+
"all detected EPs are auxiliary (benchmark/example/dev)"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# entry_point_confidence must not be high when entry_points is empty
|
|
133
|
+
if cs.entry_point_confidence == "high" and not sm.entry_points:
|
|
134
|
+
issues.append(
|
|
135
|
+
"[coherence] entry_point_confidence=high but entry_points is empty"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return issues
|
|
11
139
|
|
|
12
140
|
_HELP = """\
|
|
13
141
|
Deterministic codebase context for AI coding agents.
|
|
@@ -327,6 +455,11 @@ def main(
|
|
|
327
455
|
"--agent",
|
|
328
456
|
help="Modo agente: output estructurado y sin ruido para consumo por IA. Incluye identidad, entrypoints, arquitectura, dependencias clave, señales operacionales y gaps. Sin arbol de ficheros ni secciones vacias.",
|
|
329
457
|
),
|
|
458
|
+
trace_pipeline: bool = typer.Option(
|
|
459
|
+
False,
|
|
460
|
+
"--trace-pipeline",
|
|
461
|
+
help="Modo trazabilidad: incluye pipeline_trace con candidatos, filtros, descartes y origen de cada dato. Para diagnóstico de contaminación de resultados.",
|
|
462
|
+
),
|
|
330
463
|
) -> None:
|
|
331
464
|
"""Analyze a repository and produce structured context for AI coding agents.
|
|
332
465
|
|
|
@@ -672,7 +805,18 @@ def main(
|
|
|
672
805
|
)
|
|
673
806
|
|
|
674
807
|
# 3. Construir el schema
|
|
675
|
-
|
|
808
|
+
# Compute analyzer fingerprints: short hashes of each analyzer's key rule
|
|
809
|
+
# constants so that a rule change is always visible in the output, regardless
|
|
810
|
+
# of whether the semver was bumped.
|
|
811
|
+
try:
|
|
812
|
+
_fingerprints = _compute_analyzer_fingerprints()
|
|
813
|
+
except Exception:
|
|
814
|
+
_fingerprints = {}
|
|
815
|
+
|
|
816
|
+
metadata = AnalysisMetadata(
|
|
817
|
+
analyzed_path=str(target),
|
|
818
|
+
analyzer_fingerprints=_fingerprints,
|
|
819
|
+
)
|
|
676
820
|
sm = SourceMap(
|
|
677
821
|
metadata=metadata,
|
|
678
822
|
file_tree=file_tree,
|
|
@@ -812,6 +956,52 @@ def main(
|
|
|
812
956
|
_conf_summary, _analysis_gaps = ConfidenceAnalyzer().analyze(sm)
|
|
813
957
|
sm = _replace(sm, confidence_summary=_conf_summary, analysis_gaps=_analysis_gaps)
|
|
814
958
|
|
|
959
|
+
# E2E pipeline coherence check — emits [coherence] warnings to stderr.
|
|
960
|
+
# Catches contradictory states that can survive individual-analyzer validation.
|
|
961
|
+
for _issue in _check_pipeline_coherence(sm):
|
|
962
|
+
typer.echo(_issue, err=True)
|
|
963
|
+
|
|
964
|
+
# Build pipeline trace when --trace-pipeline is set.
|
|
965
|
+
if trace_pipeline:
|
|
966
|
+
_trace = _TraceCollector(enabled=True)
|
|
967
|
+
_trace.emit("scan", "scanner", "complete",
|
|
968
|
+
reason=f"{len(sm.file_paths)} files, {len(manifests)} manifests")
|
|
969
|
+
for _s in sm.stacks:
|
|
970
|
+
_trace.emit("detect", _s.produced_by or "unknown", "emit_stack",
|
|
971
|
+
target=_s.stack,
|
|
972
|
+
reason=f"method={_s.detection_method} confidence={_s.confidence}")
|
|
973
|
+
for _ep in sm.entry_points:
|
|
974
|
+
_trace.emit("detect", _ep.produced_by or "unknown", "emit_ep",
|
|
975
|
+
target=_ep.path,
|
|
976
|
+
reason=f"type={_ep.entrypoint_type} confidence={_ep.confidence} reason={_ep.reason}")
|
|
977
|
+
# Record EPs filtered from agent_view (benchmark/example with path-auxiliary parts)
|
|
978
|
+
_aux_parts = frozenset({
|
|
979
|
+
"benchmark", "benchmarks", "bench", "demo", "demos",
|
|
980
|
+
"example", "examples", "docs", "doc", "fixtures", "fixture",
|
|
981
|
+
})
|
|
982
|
+
for _ep in sm.entry_points:
|
|
983
|
+
_normalized_ep = normalize_entry_point(_ep)
|
|
984
|
+
_ep_type = _normalized_ep.entrypoint_type
|
|
985
|
+
_path_parts = _ep.path.replace("\\", "/").lower().split("/")
|
|
986
|
+
_filtered = (
|
|
987
|
+
_normalized_ep.classification != "production"
|
|
988
|
+
or any(p in _aux_parts for p in _path_parts)
|
|
989
|
+
)
|
|
990
|
+
if _filtered:
|
|
991
|
+
_trace.emit("output", "agent_view", "filter_ep",
|
|
992
|
+
target=_ep.path,
|
|
993
|
+
reason=f"entrypoint_type={_ep_type} (auxiliary)")
|
|
994
|
+
if sm.confidence_summary is not None:
|
|
995
|
+
_cs = sm.confidence_summary
|
|
996
|
+
_trace.emit("confidence", "confidence_analyzer", "computed",
|
|
997
|
+
reason=(
|
|
998
|
+
f"overall={_cs.overall} "
|
|
999
|
+
f"stack={_cs.stack_confidence} "
|
|
1000
|
+
f"ep={_cs.entry_point_confidence} "
|
|
1001
|
+
f"anomalies={len(_cs.anomalies)}"
|
|
1002
|
+
))
|
|
1003
|
+
sm = _replace(sm, pipeline_trace=_trace.build_trace())
|
|
1004
|
+
|
|
815
1005
|
# 4. Serializar
|
|
816
1006
|
if agent:
|
|
817
1007
|
data = agent_view(sm)
|