okstra 0.130.4 → 0.131.0
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.
- package/README.md +0 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +1 -1
- package/docs/for-ai/README.md +0 -2
- package/docs/for-ai/skills/okstra-run.md +13 -2
- package/docs/for-ai/skills/okstra-setup.md +4 -1
- package/docs/project-structure-overview.md +5 -4
- package/docs/task-process/implementation-planning.md +13 -0
- package/docs/task-process/implementation.md +17 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +7 -0
- package/runtime/bin/lib/okstra/usage.sh +4 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/plan-body-verification.md +26 -5
- package/runtime/prompts/lead/report-writer.md +6 -2
- package/runtime/prompts/profiles/_common-contract.md +2 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +6 -1
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +33 -10
- package/runtime/prompts/profiles/final-verification.md +21 -6
- package/runtime/prompts/profiles/implementation-planning.md +16 -4
- package/runtime/python/okstra_ctl/codex_dispatch.py +31 -0
- package/runtime/python/okstra_ctl/conformance.py +49 -11
- package/runtime/python/okstra_ctl/report_finalize.py +52 -1
- package/runtime/python/okstra_ctl/run.py +15 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
- package/runtime/python/okstra_vendor/__init__.py +0 -44
- package/runtime/schemas/final-report-v1.0.schema.json +99 -5
- package/runtime/skills/okstra-run/SKILL.md +11 -4
- package/runtime/skills/okstra-setup/SKILL.md +3 -2
- package/runtime/skills/okstra-setup/references/project-config.md +16 -7
- package/runtime/templates/reports/final-report.template.md +15 -9
- package/runtime/templates/reports/i18n/en.json +1 -0
- package/runtime/templates/reports/i18n/ko.json +1 -0
- package/runtime/validators/lib/fixtures.sh +6 -0
- package/runtime/validators/validate-run.py +1722 -98
- package/src/cli-registry.mjs +0 -7
- package/src/commands/lifecycle/doctor.mjs +0 -9
- package/src/lib/skill-catalog.mjs +0 -1
- package/docs/for-ai/skills/okstra-graphify.md +0 -64
- package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
- package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
- package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
- package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
- package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
- package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
- package/runtime/python/okstra_vendor/graphify/build.py +0 -107
- package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
- package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
- package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
- package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
- package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
- package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
- package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
- package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
- package/runtime/python/okstra_vendor/graphify/report.py +0 -175
- package/runtime/python/okstra_vendor/graphify/security.py +0 -203
- package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
- package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
- package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
- package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
- package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
- package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
- package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
- package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
- package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
- package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
- package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
- package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
- package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
- package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
- package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
- package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
- package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
- package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
- package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
- package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
- package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
- package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
- package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
- package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
- package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
- package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
- package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
- package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
- package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
- package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
- package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
- package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
- package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
- package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
- package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
- package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
- package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
- package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
- package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
- package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
- package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
- package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
- package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
- package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
- package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
- package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
- package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
- package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
- package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
- package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
- package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
- package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
- package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
- package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
- package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
- package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
- package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
- package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
- package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
- package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
- package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
- package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
- package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
- package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
- package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
- package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
- package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
- package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
- package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
- package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
- package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
- package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
- package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
- package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
- package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
- package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
- package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
- package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
- package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
- package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
- package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
- package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
- package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
- package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
- package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
- package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
- package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
- package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
- package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
- package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
- package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
- package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
- package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
- package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
- package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
- package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
- package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
- package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
- package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
- package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
- package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
- package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
- package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
- package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
- package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
- package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
- package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
- package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
- package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
- package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
- package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
- package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
- package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
- package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
- package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
- package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
- package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
- package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
- package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
- package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
- package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
- package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
- package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
- package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
- package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
- package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
- package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
- package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
- package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
- package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
- package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
- package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
- package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
- package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
- package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
- package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
- package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
- package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
- package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
- package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
- package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
- package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
- package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
- package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
- package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
- package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
- package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
- package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
- package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
- package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
- package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
- package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
- package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
- package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
- package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
- package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
- package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
- package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
- package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
- package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
- package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
- package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
- package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
- package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
- package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
- package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
- package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
- package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
- package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
- package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
- package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
- package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
- package/runtime/skills/okstra-graphify/SKILL.md +0 -169
- package/src/commands/graphify.mjs +0 -32
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
# file discovery, type classification, and corpus health checks
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
import fnmatch
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
import re
|
|
7
|
-
from enum import Enum
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class FileType(str, Enum):
|
|
12
|
-
CODE = "code"
|
|
13
|
-
DOCUMENT = "document"
|
|
14
|
-
PAPER = "paper"
|
|
15
|
-
IMAGE = "image"
|
|
16
|
-
VIDEO = "video"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
_MANIFEST_PATH = "graphify-out/manifest.json"
|
|
20
|
-
|
|
21
|
-
CODE_EXTENSIONS = {'.py', '.ts', '.js', '.jsx', '.tsx', '.mjs', '.ejs', '.go', '.rs', '.java', '.cpp', '.cc', '.cxx', '.c', '.h', '.hpp', '.rb', '.swift', '.kt', '.kts', '.cs', '.scala', '.php', '.lua', '.toc', '.zig', '.ps1', '.ex', '.exs', '.m', '.mm', '.jl', '.vue', '.svelte', '.dart', '.v', '.sv'}
|
|
22
|
-
DOC_EXTENSIONS = {'.md', '.mdx', '.txt', '.rst', '.html'}
|
|
23
|
-
PAPER_EXTENSIONS = {'.pdf'}
|
|
24
|
-
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'}
|
|
25
|
-
OFFICE_EXTENSIONS = {'.docx', '.xlsx'}
|
|
26
|
-
VIDEO_EXTENSIONS = {'.mp4', '.mov', '.webm', '.mkv', '.avi', '.m4v', '.mp3', '.wav', '.m4a', '.ogg'}
|
|
27
|
-
|
|
28
|
-
CORPUS_WARN_THRESHOLD = 50_000 # words - below this, warn "you may not need a graph"
|
|
29
|
-
CORPUS_UPPER_THRESHOLD = 500_000 # words - above this, warn about token cost
|
|
30
|
-
FILE_COUNT_UPPER = 200 # files - above this, warn about token cost
|
|
31
|
-
|
|
32
|
-
# Files that may contain secrets - skip silently
|
|
33
|
-
_SENSITIVE_PATTERNS = [
|
|
34
|
-
re.compile(r'(^|[\\/])\.(env|envrc)(\.|$)', re.IGNORECASE),
|
|
35
|
-
re.compile(r'\.(pem|key|p12|pfx|cert|crt|der|p8)$', re.IGNORECASE),
|
|
36
|
-
re.compile(r'(credential|secret|passwd|password|token|private_key)', re.IGNORECASE),
|
|
37
|
-
re.compile(r'(id_rsa|id_dsa|id_ecdsa|id_ed25519)(\.pub)?$'),
|
|
38
|
-
re.compile(r'(\.netrc|\.pgpass|\.htpasswd)$', re.IGNORECASE),
|
|
39
|
-
re.compile(r'(aws_credentials|gcloud_credentials|service.account)', re.IGNORECASE),
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
# Signals that a .md/.txt file is actually a converted academic paper
|
|
43
|
-
_PAPER_SIGNALS = [
|
|
44
|
-
re.compile(r'\barxiv\b', re.IGNORECASE),
|
|
45
|
-
re.compile(r'\bdoi\s*:', re.IGNORECASE),
|
|
46
|
-
re.compile(r'\babstract\b', re.IGNORECASE),
|
|
47
|
-
re.compile(r'\bproceedings\b', re.IGNORECASE),
|
|
48
|
-
re.compile(r'\bjournal\b', re.IGNORECASE),
|
|
49
|
-
re.compile(r'\bpreprint\b', re.IGNORECASE),
|
|
50
|
-
re.compile(r'\\cite\{'), # LaTeX citation
|
|
51
|
-
re.compile(r'\[\d+\]'), # Numbered citation [1], [23] (inline)
|
|
52
|
-
re.compile(r'\[\n\d+\n\]'), # Numbered citation spread across lines (markdown conversion)
|
|
53
|
-
re.compile(r'eq\.\s*\d+|equation\s+\d+', re.IGNORECASE),
|
|
54
|
-
re.compile(r'\d{4}\.\d{4,5}'), # arXiv ID like 1706.03762
|
|
55
|
-
re.compile(r'\bwe propose\b', re.IGNORECASE), # common academic phrasing
|
|
56
|
-
re.compile(r'\bliterature\b', re.IGNORECASE), # "from the literature"
|
|
57
|
-
]
|
|
58
|
-
_PAPER_SIGNAL_THRESHOLD = 3 # need at least this many signals to call it a paper
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _is_sensitive(path: Path) -> bool:
|
|
62
|
-
"""Return True if this file likely contains secrets and should be skipped."""
|
|
63
|
-
name = path.name
|
|
64
|
-
full = str(path)
|
|
65
|
-
return any(p.search(name) or p.search(full) for p in _SENSITIVE_PATTERNS)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _looks_like_paper(path: Path) -> bool:
|
|
69
|
-
"""Heuristic: does this text file read like an academic paper?"""
|
|
70
|
-
try:
|
|
71
|
-
# Only scan first 3000 chars for speed
|
|
72
|
-
text = path.read_text(encoding="utf-8", errors="ignore")[:3000]
|
|
73
|
-
hits = sum(1 for pattern in _PAPER_SIGNALS if pattern.search(text))
|
|
74
|
-
return hits >= _PAPER_SIGNAL_THRESHOLD
|
|
75
|
-
except Exception:
|
|
76
|
-
return False
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
_ASSET_DIR_MARKERS = {".imageset", ".xcassets", ".appiconset", ".colorset", ".launchimage"}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def classify_file(path: Path) -> FileType | None:
|
|
83
|
-
# Compound extensions must be checked before simple suffix lookup
|
|
84
|
-
if path.name.lower().endswith(".blade.php"):
|
|
85
|
-
return FileType.CODE
|
|
86
|
-
ext = path.suffix.lower()
|
|
87
|
-
if ext in CODE_EXTENSIONS:
|
|
88
|
-
return FileType.CODE
|
|
89
|
-
if ext in PAPER_EXTENSIONS:
|
|
90
|
-
# PDFs inside Xcode asset catalogs are vector icons, not papers
|
|
91
|
-
if any(part.endswith(tuple(_ASSET_DIR_MARKERS)) for part in path.parts):
|
|
92
|
-
return None
|
|
93
|
-
return FileType.PAPER
|
|
94
|
-
if ext in IMAGE_EXTENSIONS:
|
|
95
|
-
return FileType.IMAGE
|
|
96
|
-
if ext in DOC_EXTENSIONS:
|
|
97
|
-
# Check if it's a converted paper
|
|
98
|
-
if _looks_like_paper(path):
|
|
99
|
-
return FileType.PAPER
|
|
100
|
-
return FileType.DOCUMENT
|
|
101
|
-
if ext in OFFICE_EXTENSIONS:
|
|
102
|
-
return FileType.DOCUMENT
|
|
103
|
-
if ext in VIDEO_EXTENSIONS:
|
|
104
|
-
return FileType.VIDEO
|
|
105
|
-
return None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def extract_pdf_text(path: Path) -> str:
|
|
109
|
-
"""Extract plain text from a PDF file using pypdf."""
|
|
110
|
-
try:
|
|
111
|
-
from pypdf import PdfReader
|
|
112
|
-
reader = PdfReader(str(path))
|
|
113
|
-
pages = []
|
|
114
|
-
for page in reader.pages:
|
|
115
|
-
text = page.extract_text()
|
|
116
|
-
if text:
|
|
117
|
-
pages.append(text)
|
|
118
|
-
return "\n".join(pages)
|
|
119
|
-
except Exception:
|
|
120
|
-
return ""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def docx_to_markdown(path: Path) -> str:
|
|
124
|
-
"""Convert a .docx file to markdown text using python-docx."""
|
|
125
|
-
try:
|
|
126
|
-
from docx import Document
|
|
127
|
-
from docx.oxml.ns import qn
|
|
128
|
-
doc = Document(str(path))
|
|
129
|
-
lines = []
|
|
130
|
-
for para in doc.paragraphs:
|
|
131
|
-
style = para.style.name if para.style else ""
|
|
132
|
-
text = para.text.strip()
|
|
133
|
-
if not text:
|
|
134
|
-
lines.append("")
|
|
135
|
-
continue
|
|
136
|
-
if style.startswith("Heading 1"):
|
|
137
|
-
lines.append(f"# {text}")
|
|
138
|
-
elif style.startswith("Heading 2"):
|
|
139
|
-
lines.append(f"## {text}")
|
|
140
|
-
elif style.startswith("Heading 3"):
|
|
141
|
-
lines.append(f"### {text}")
|
|
142
|
-
elif style.startswith("List"):
|
|
143
|
-
lines.append(f"- {text}")
|
|
144
|
-
else:
|
|
145
|
-
lines.append(text)
|
|
146
|
-
# Tables
|
|
147
|
-
for table in doc.tables:
|
|
148
|
-
rows = [[cell.text.strip() for cell in row.cells] for row in table.rows]
|
|
149
|
-
if not rows:
|
|
150
|
-
continue
|
|
151
|
-
header = "| " + " | ".join(rows[0]) + " |"
|
|
152
|
-
sep = "| " + " | ".join("---" for _ in rows[0]) + " |"
|
|
153
|
-
lines.extend([header, sep])
|
|
154
|
-
for row in rows[1:]:
|
|
155
|
-
lines.append("| " + " | ".join(row) + " |")
|
|
156
|
-
return "\n".join(lines)
|
|
157
|
-
except ImportError:
|
|
158
|
-
return ""
|
|
159
|
-
except Exception:
|
|
160
|
-
return ""
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def xlsx_to_markdown(path: Path) -> str:
|
|
164
|
-
"""Convert an .xlsx file to markdown text using openpyxl."""
|
|
165
|
-
try:
|
|
166
|
-
import openpyxl
|
|
167
|
-
wb = openpyxl.load_workbook(str(path), read_only=True, data_only=True)
|
|
168
|
-
sections = []
|
|
169
|
-
for sheet_name in wb.sheetnames:
|
|
170
|
-
ws = wb[sheet_name]
|
|
171
|
-
rows = []
|
|
172
|
-
for row in ws.iter_rows(values_only=True):
|
|
173
|
-
# Skip entirely empty rows
|
|
174
|
-
if all(cell is None for cell in row):
|
|
175
|
-
continue
|
|
176
|
-
rows.append([str(cell) if cell is not None else "" for cell in row])
|
|
177
|
-
if not rows:
|
|
178
|
-
continue
|
|
179
|
-
sections.append(f"## Sheet: {sheet_name}")
|
|
180
|
-
if len(rows) >= 1:
|
|
181
|
-
header = "| " + " | ".join(rows[0]) + " |"
|
|
182
|
-
sep = "| " + " | ".join("---" for _ in rows[0]) + " |"
|
|
183
|
-
sections.extend([header, sep])
|
|
184
|
-
for row in rows[1:]:
|
|
185
|
-
sections.append("| " + " | ".join(row) + " |")
|
|
186
|
-
wb.close()
|
|
187
|
-
return "\n".join(sections)
|
|
188
|
-
except ImportError:
|
|
189
|
-
return ""
|
|
190
|
-
except Exception:
|
|
191
|
-
return ""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def convert_office_file(path: Path, out_dir: Path) -> Path | None:
|
|
195
|
-
"""Convert a .docx or .xlsx to a markdown sidecar in out_dir.
|
|
196
|
-
|
|
197
|
-
Returns the path of the converted .md file, or None if conversion failed
|
|
198
|
-
or the required library is not installed.
|
|
199
|
-
"""
|
|
200
|
-
ext = path.suffix.lower()
|
|
201
|
-
if ext == ".docx":
|
|
202
|
-
text = docx_to_markdown(path)
|
|
203
|
-
elif ext == ".xlsx":
|
|
204
|
-
text = xlsx_to_markdown(path)
|
|
205
|
-
else:
|
|
206
|
-
return None
|
|
207
|
-
|
|
208
|
-
if not text.strip():
|
|
209
|
-
return None
|
|
210
|
-
|
|
211
|
-
out_dir.mkdir(parents=True, exist_ok=True)
|
|
212
|
-
# Use a stable name derived from the original path to avoid collisions
|
|
213
|
-
import hashlib
|
|
214
|
-
name_hash = hashlib.sha256(str(path.resolve()).encode()).hexdigest()[:8]
|
|
215
|
-
out_path = out_dir / f"{path.stem}_{name_hash}.md"
|
|
216
|
-
out_path.write_text(
|
|
217
|
-
f"<!-- converted from {path.name} -->\n\n{text}",
|
|
218
|
-
encoding="utf-8",
|
|
219
|
-
)
|
|
220
|
-
return out_path
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
def count_words(path: Path) -> int:
|
|
224
|
-
try:
|
|
225
|
-
ext = path.suffix.lower()
|
|
226
|
-
if ext == ".pdf":
|
|
227
|
-
return len(extract_pdf_text(path).split())
|
|
228
|
-
if ext == ".docx":
|
|
229
|
-
return len(docx_to_markdown(path).split())
|
|
230
|
-
if ext == ".xlsx":
|
|
231
|
-
return len(xlsx_to_markdown(path).split())
|
|
232
|
-
return len(path.read_text(encoding="utf-8", errors="ignore").split())
|
|
233
|
-
except Exception:
|
|
234
|
-
return 0
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
# Directory names to always skip - venvs, caches, build artifacts, deps
|
|
238
|
-
_SKIP_DIRS = {
|
|
239
|
-
"venv", ".venv", "env", ".env",
|
|
240
|
-
"node_modules", "__pycache__", ".git",
|
|
241
|
-
"dist", "build", "target", "out",
|
|
242
|
-
"site-packages", "lib64",
|
|
243
|
-
".pytest_cache", ".mypy_cache", ".ruff_cache",
|
|
244
|
-
".tox", ".eggs", "*.egg-info",
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
# Large generated files that are never useful to extract
|
|
248
|
-
_SKIP_FILES = {
|
|
249
|
-
"package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
|
250
|
-
"Cargo.lock", "poetry.lock", "Gemfile.lock",
|
|
251
|
-
"composer.lock", "go.sum", "go.work.sum",
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
def _is_noise_dir(part: str) -> bool:
|
|
255
|
-
"""Return True if this directory name looks like a venv, cache, or dep dir."""
|
|
256
|
-
if part in _SKIP_DIRS:
|
|
257
|
-
return True
|
|
258
|
-
# Catch *_venv, *_repo/site-packages patterns
|
|
259
|
-
if part.endswith("_venv") or part.endswith("_env"):
|
|
260
|
-
return True
|
|
261
|
-
if part.endswith(".egg-info"):
|
|
262
|
-
return True
|
|
263
|
-
return False
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _load_graphifyignore(root: Path) -> list[tuple[Path, str]]:
|
|
267
|
-
"""Read .graphifyignore from root **and ancestor directories**.
|
|
268
|
-
|
|
269
|
-
Returns a list of (anchor_dir, pattern) pairs. Each pattern is matched
|
|
270
|
-
against paths relative to both the scan root and the anchor_dir where
|
|
271
|
-
the .graphifyignore file was found — so patterns written relative to a
|
|
272
|
-
parent directory still work when graphify is run on a subfolder.
|
|
273
|
-
|
|
274
|
-
Walks upward from *root* towards the filesystem root, stopping at a
|
|
275
|
-
``.git`` boundary. Lines starting with # are comments; blank lines ignored.
|
|
276
|
-
"""
|
|
277
|
-
patterns: list[tuple[Path, str]] = []
|
|
278
|
-
current = root.resolve()
|
|
279
|
-
while True:
|
|
280
|
-
ignore_file = current / ".graphifyignore"
|
|
281
|
-
if ignore_file.exists():
|
|
282
|
-
for line in ignore_file.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
283
|
-
line = line.strip()
|
|
284
|
-
if line and not line.startswith("#"):
|
|
285
|
-
patterns.append((current, line))
|
|
286
|
-
# Stop climbing once we've processed the git repo root
|
|
287
|
-
if (current / ".git").exists():
|
|
288
|
-
break
|
|
289
|
-
parent = current.parent
|
|
290
|
-
if parent == current:
|
|
291
|
-
break # filesystem root
|
|
292
|
-
current = parent
|
|
293
|
-
return patterns
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def _is_ignored(path: Path, root: Path, patterns: list[tuple[Path, str]]) -> bool:
|
|
297
|
-
"""Return True if path matches any .graphifyignore pattern."""
|
|
298
|
-
if not patterns:
|
|
299
|
-
return False
|
|
300
|
-
|
|
301
|
-
def _matches(rel: str, p: str) -> bool:
|
|
302
|
-
parts = rel.split("/")
|
|
303
|
-
if fnmatch.fnmatch(rel, p):
|
|
304
|
-
return True
|
|
305
|
-
if fnmatch.fnmatch(path.name, p):
|
|
306
|
-
return True
|
|
307
|
-
for i, part in enumerate(parts):
|
|
308
|
-
if fnmatch.fnmatch(part, p):
|
|
309
|
-
return True
|
|
310
|
-
if fnmatch.fnmatch("/".join(parts[:i + 1]), p):
|
|
311
|
-
return True
|
|
312
|
-
return False
|
|
313
|
-
|
|
314
|
-
for anchor, pattern in patterns:
|
|
315
|
-
p = pattern.strip("/")
|
|
316
|
-
if not p:
|
|
317
|
-
continue
|
|
318
|
-
# Try path relative to the scan root
|
|
319
|
-
try:
|
|
320
|
-
rel = str(path.relative_to(root)).replace(os.sep, "/")
|
|
321
|
-
if _matches(rel, p):
|
|
322
|
-
return True
|
|
323
|
-
except ValueError:
|
|
324
|
-
pass
|
|
325
|
-
# Also try relative to the anchor dir (the .graphifyignore's location),
|
|
326
|
-
# so patterns written at a parent level still fire when running on a subfolder
|
|
327
|
-
if anchor != root:
|
|
328
|
-
try:
|
|
329
|
-
rel_anchor = str(path.relative_to(anchor)).replace(os.sep, "/")
|
|
330
|
-
if _matches(rel_anchor, p):
|
|
331
|
-
return True
|
|
332
|
-
except ValueError:
|
|
333
|
-
pass
|
|
334
|
-
return False
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def detect(root: Path, *, follow_symlinks: bool = False) -> dict:
|
|
338
|
-
files: dict[FileType, list[str]] = {
|
|
339
|
-
FileType.CODE: [],
|
|
340
|
-
FileType.DOCUMENT: [],
|
|
341
|
-
FileType.PAPER: [],
|
|
342
|
-
FileType.IMAGE: [],
|
|
343
|
-
FileType.VIDEO: [],
|
|
344
|
-
}
|
|
345
|
-
total_words = 0
|
|
346
|
-
|
|
347
|
-
skipped_sensitive: list[str] = []
|
|
348
|
-
ignore_patterns = _load_graphifyignore(root)
|
|
349
|
-
|
|
350
|
-
# Always include graphify-out/memory/ - query results filed back into the graph
|
|
351
|
-
memory_dir = root / "graphify-out" / "memory"
|
|
352
|
-
scan_paths = [root]
|
|
353
|
-
if memory_dir.exists():
|
|
354
|
-
scan_paths.append(memory_dir)
|
|
355
|
-
|
|
356
|
-
seen: set[Path] = set()
|
|
357
|
-
all_files: list[Path] = []
|
|
358
|
-
|
|
359
|
-
for scan_root in scan_paths:
|
|
360
|
-
in_memory_tree = memory_dir.exists() and str(scan_root).startswith(str(memory_dir))
|
|
361
|
-
for dirpath, dirnames, filenames in os.walk(scan_root, followlinks=follow_symlinks):
|
|
362
|
-
dp = Path(dirpath)
|
|
363
|
-
if follow_symlinks and os.path.islink(dirpath):
|
|
364
|
-
real = os.path.realpath(dirpath)
|
|
365
|
-
parent_real = os.path.realpath(os.path.dirname(dirpath))
|
|
366
|
-
if parent_real == real or parent_real.startswith(real + os.sep):
|
|
367
|
-
dirnames.clear()
|
|
368
|
-
continue
|
|
369
|
-
if not in_memory_tree:
|
|
370
|
-
# Prune noise dirs in-place so os.walk never descends into them
|
|
371
|
-
dirnames[:] = [
|
|
372
|
-
d for d in dirnames
|
|
373
|
-
if not d.startswith(".")
|
|
374
|
-
and not _is_noise_dir(d)
|
|
375
|
-
and not _is_ignored(dp / d, root, ignore_patterns)
|
|
376
|
-
]
|
|
377
|
-
for fname in filenames:
|
|
378
|
-
if fname in _SKIP_FILES:
|
|
379
|
-
continue
|
|
380
|
-
p = dp / fname
|
|
381
|
-
if p not in seen:
|
|
382
|
-
seen.add(p)
|
|
383
|
-
all_files.append(p)
|
|
384
|
-
|
|
385
|
-
converted_dir = root / "graphify-out" / "converted"
|
|
386
|
-
|
|
387
|
-
for p in all_files:
|
|
388
|
-
# For memory dir files, skip hidden/noise filtering
|
|
389
|
-
in_memory = memory_dir.exists() and str(p).startswith(str(memory_dir))
|
|
390
|
-
if not in_memory:
|
|
391
|
-
# Hidden files are already excluded via dir pruning above,
|
|
392
|
-
# but catch hidden files at the root level
|
|
393
|
-
if p.name.startswith("."):
|
|
394
|
-
continue
|
|
395
|
-
# Skip files inside our own converted/ dir (avoid re-processing sidecars)
|
|
396
|
-
if str(p).startswith(str(converted_dir)):
|
|
397
|
-
continue
|
|
398
|
-
if _is_ignored(p, root, ignore_patterns):
|
|
399
|
-
continue
|
|
400
|
-
if _is_sensitive(p):
|
|
401
|
-
skipped_sensitive.append(str(p))
|
|
402
|
-
continue
|
|
403
|
-
ftype = classify_file(p)
|
|
404
|
-
if ftype:
|
|
405
|
-
# Office files: convert to markdown sidecar so subagents can read them
|
|
406
|
-
if p.suffix.lower() in OFFICE_EXTENSIONS:
|
|
407
|
-
md_path = convert_office_file(p, converted_dir)
|
|
408
|
-
if md_path:
|
|
409
|
-
files[ftype].append(str(md_path))
|
|
410
|
-
total_words += count_words(md_path)
|
|
411
|
-
else:
|
|
412
|
-
# Conversion failed (library not installed) - skip with note
|
|
413
|
-
skipped_sensitive.append(str(p) + " [office conversion failed - pip install graphifyy[office]]")
|
|
414
|
-
continue
|
|
415
|
-
files[ftype].append(str(p))
|
|
416
|
-
if ftype != FileType.VIDEO:
|
|
417
|
-
total_words += count_words(p)
|
|
418
|
-
|
|
419
|
-
total_files = sum(len(v) for v in files.values())
|
|
420
|
-
needs_graph = total_words >= CORPUS_WARN_THRESHOLD
|
|
421
|
-
|
|
422
|
-
# Determine warning - lower bound, upper bound, or sensitive files skipped
|
|
423
|
-
warning: str | None = None
|
|
424
|
-
if not needs_graph:
|
|
425
|
-
warning = (
|
|
426
|
-
f"Corpus is ~{total_words:,} words - fits in a single context window. "
|
|
427
|
-
f"You may not need a graph."
|
|
428
|
-
)
|
|
429
|
-
elif total_words >= CORPUS_UPPER_THRESHOLD or total_files >= FILE_COUNT_UPPER:
|
|
430
|
-
warning = (
|
|
431
|
-
f"Large corpus: {total_files} files · ~{total_words:,} words. "
|
|
432
|
-
f"Semantic extraction will be expensive (many Claude tokens). "
|
|
433
|
-
f"Consider running on a subfolder, or use --no-semantic to run AST-only."
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
"files": {k.value: v for k, v in files.items()},
|
|
438
|
-
"total_files": total_files,
|
|
439
|
-
"total_words": total_words,
|
|
440
|
-
"needs_graph": needs_graph,
|
|
441
|
-
"warning": warning,
|
|
442
|
-
"skipped_sensitive": skipped_sensitive,
|
|
443
|
-
"graphifyignore_patterns": len(ignore_patterns),
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def load_manifest(manifest_path: str = _MANIFEST_PATH) -> dict[str, float]:
|
|
448
|
-
"""Load the file modification time manifest from a previous run."""
|
|
449
|
-
try:
|
|
450
|
-
return json.loads(Path(manifest_path).read_text(encoding="utf-8"))
|
|
451
|
-
except Exception:
|
|
452
|
-
return {}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
def save_manifest(files: dict[str, list[str]], manifest_path: str = _MANIFEST_PATH) -> None:
|
|
456
|
-
"""Save current file mtimes so the next --update run can diff against them."""
|
|
457
|
-
manifest: dict[str, float] = {}
|
|
458
|
-
for file_list in files.values():
|
|
459
|
-
for f in file_list:
|
|
460
|
-
try:
|
|
461
|
-
manifest[f] = Path(f).stat().st_mtime
|
|
462
|
-
except OSError:
|
|
463
|
-
pass # file deleted between detect() and manifest write - skip it
|
|
464
|
-
Path(manifest_path).parent.mkdir(parents=True, exist_ok=True)
|
|
465
|
-
Path(manifest_path).write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def detect_incremental(root: Path, manifest_path: str = _MANIFEST_PATH) -> dict:
|
|
469
|
-
"""Like detect(), but returns only new or modified files since the last run.
|
|
470
|
-
|
|
471
|
-
Compares current file mtimes against the stored manifest.
|
|
472
|
-
Use for --update mode: re-extract only what changed, merge into existing graph.
|
|
473
|
-
"""
|
|
474
|
-
full = detect(root)
|
|
475
|
-
manifest = load_manifest(manifest_path)
|
|
476
|
-
|
|
477
|
-
if not manifest:
|
|
478
|
-
# No previous run - treat everything as new
|
|
479
|
-
full["incremental"] = True
|
|
480
|
-
full["new_files"] = full["files"]
|
|
481
|
-
full["unchanged_files"] = {k: [] for k in full["files"]}
|
|
482
|
-
full["new_total"] = full["total_files"]
|
|
483
|
-
return full
|
|
484
|
-
|
|
485
|
-
new_files: dict[str, list[str]] = {k: [] for k in full["files"]}
|
|
486
|
-
unchanged_files: dict[str, list[str]] = {k: [] for k in full["files"]}
|
|
487
|
-
|
|
488
|
-
for ftype, file_list in full["files"].items():
|
|
489
|
-
for f in file_list:
|
|
490
|
-
stored_mtime = manifest.get(f)
|
|
491
|
-
try:
|
|
492
|
-
current_mtime = Path(f).stat().st_mtime
|
|
493
|
-
except Exception:
|
|
494
|
-
current_mtime = 0
|
|
495
|
-
if stored_mtime is None or current_mtime > stored_mtime:
|
|
496
|
-
new_files[ftype].append(f)
|
|
497
|
-
else:
|
|
498
|
-
unchanged_files[ftype].append(f)
|
|
499
|
-
|
|
500
|
-
# Files in manifest that no longer exist - their cached nodes are now ghost nodes
|
|
501
|
-
current_files = {f for flist in full["files"].values() for f in flist}
|
|
502
|
-
deleted_files = [f for f in manifest if f not in current_files]
|
|
503
|
-
|
|
504
|
-
new_total = sum(len(v) for v in new_files.values())
|
|
505
|
-
full["incremental"] = True
|
|
506
|
-
full["new_files"] = new_files
|
|
507
|
-
full["unchanged_files"] = unchanged_files
|
|
508
|
-
full["new_total"] = new_total
|
|
509
|
-
full["deleted_files"] = deleted_files
|
|
510
|
-
return full
|