ecoportal-api-graphql 1.3.13 → 1.3.16
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +87 -0
- data/lib/ecoportal/api/common/graphql/client.rb +0 -2
- data/lib/ecoportal/api/common/graphql/http_client.rb +6 -0
- data/lib/ecoportal/api/graphql/base/ai_summary_version.rb +17 -17
- data/lib/ecoportal/api/graphql/base/file_container.rb +14 -14
- data/lib/ecoportal/api/graphql/base/location_classification_type.rb +15 -15
- data/lib/ecoportal/api/graphql/base/location_structure.rb +27 -27
- data/lib/ecoportal/api/graphql/base/organization.rb +15 -15
- data/lib/ecoportal/api/graphql/base/page/task.rb +24 -0
- data/lib/ecoportal/api/graphql/base/page.rb +17 -16
- data/lib/ecoportal/api/graphql/base/template.rb +35 -0
- data/lib/ecoportal/api/graphql/base.rb +37 -35
- data/lib/ecoportal/api/graphql/builder/contractor_entity.rb +41 -41
- data/lib/ecoportal/api/graphql/compat/response.rb +35 -35
- data/lib/ecoportal/api/graphql/connection/action.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/action_category.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/contractor_entity.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/person_member.rb +11 -11
- data/lib/ecoportal/api/graphql/connection/preview_page.rb +11 -11
- data/lib/ecoportal/api/graphql/connection.rb +17 -17
- data/lib/ecoportal/api/graphql/file_upload/client.rb +21 -10
- data/lib/ecoportal/api/graphql/fragment/field_configuration.rb +38 -0
- data/lib/ecoportal/api/graphql/fragment/force.rb +14 -10
- data/lib/ecoportal/api/graphql/fragment/location_node.rb +26 -26
- data/lib/ecoportal/api/graphql/fragment/page_task.rb +39 -0
- data/lib/ecoportal/api/graphql/fragment/template.rb +51 -0
- data/lib/ecoportal/api/graphql/fragment.rb +3 -0
- data/lib/ecoportal/api/graphql/input/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/input/preset_view/update.rb +16 -16
- data/lib/ecoportal/api/graphql/input/workflow_command/add_stage.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_field_configuration.rb +21 -21
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page.rb +28 -28
- data/lib/ecoportal/api/graphql/input/workflow_command/edit_page_creator_permissions.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/cross_reference.rb +23 -23
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/date.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/image_gallery.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/people.rb +24 -24
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/plain_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/rich_text.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/field_config/select.rb +20 -20
- data/lib/ecoportal/api/graphql/input/workflow_command/move_field.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_forces.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command/reorder_section.rb +18 -18
- data/lib/ecoportal/api/graphql/input/workflow_command.rb +251 -251
- data/lib/ecoportal/api/graphql/input.rb +24 -24
- data/lib/ecoportal/api/graphql/logic/connection.rb +34 -34
- data/lib/ecoportal/api/graphql/model/page/task.rb +16 -0
- data/lib/ecoportal/api/graphql/model/page.rb +16 -15
- data/lib/ecoportal/api/graphql/model/person_member.rb +15 -15
- data/lib/ecoportal/api/graphql/model/template.rb +15 -0
- data/lib/ecoportal/api/graphql/model.rb +32 -30
- data/lib/ecoportal/api/graphql/mutation/ai_summary/generate.rb +45 -45
- data/lib/ecoportal/api/graphql/mutation/ai_summary/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/mutation/page/approve_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/batch_update_review_task.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/create_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/delete_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/execute_force_commands.rb +7 -7
- data/lib/ecoportal/api/graphql/mutation/page/execute_workflow_commands.rb +8 -7
- data/lib/ecoportal/api/graphql/mutation/page/publish_draft.rb +38 -40
- data/lib/ecoportal/api/graphql/mutation/page/reject_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/restart_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/page/undo_review_task.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/preset_view/destroy.rb +34 -35
- data/lib/ecoportal/api/graphql/mutation/register/destroy.rb +35 -35
- data/lib/ecoportal/api/graphql/mutation/smart_fill/generate.rb +36 -36
- data/lib/ecoportal/api/graphql/mutation/smart_fill/submit_feedback.rb +40 -40
- data/lib/ecoportal/api/graphql/mutation/smart_fill.rb +13 -13
- data/lib/ecoportal/api/graphql/mutation/template/create_related_page.rb +46 -46
- data/lib/ecoportal/api/graphql/mutation/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/mutation.rb +20 -20
- data/lib/ecoportal/api/graphql/payload/contractor_entity.rb +14 -14
- data/lib/ecoportal/api/graphql/payload/ok_payload.rb +36 -21
- data/lib/ecoportal/api/graphql/payload/page/draft.rb +26 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task.rb +13 -13
- data/lib/ecoportal/api/graphql/payload/page/review_task_batch.rb +23 -0
- data/lib/ecoportal/api/graphql/payload/page.rb +20 -19
- data/lib/ecoportal/api/graphql/payload/preset_view.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/register.rb +15 -11
- data/lib/ecoportal/api/graphql/payload/template/create_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/destroy_related_page.rb +1 -1
- data/lib/ecoportal/api/graphql/payload/template/update_information.rb +1 -1
- data/lib/ecoportal/api/graphql/payload.rb +23 -23
- data/lib/ecoportal/api/graphql/query/location_structures.rb +57 -57
- data/lib/ecoportal/api/graphql/query/page.rb +45 -45
- data/lib/ecoportal/api/graphql/query/page_with_forces.rb +9 -3
- data/lib/ecoportal/api/graphql/query/pages_workflow_commands.rb +9 -3
- data/lib/ecoportal/api/graphql/query/register_preset_views.rb +78 -78
- data/lib/ecoportal/api/graphql/query.rb +27 -27
- data/lib/ecoportal/api/graphql.rb +9 -5
- data/lib/ecoportal/api/graphql_version.rb +1 -1
- metadata +9 -180
- data/.ai-assistance/bridge/CLAUDE.md +0 -338
- data/.ai-assistance/bridge/archive/.gitkeep +0 -0
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.inbox.md +0 -29
- data/.ai-assistance/bridge/archive/oscar-a1b2c3d-gitlab-mcp-doc-update.outbox.md +0 -18
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.inbox.md +0 -42
- data/.ai-assistance/bridge/archive/oscar-c912c25-gemini-design-review.outbox.md +0 -115
- data/.ai-assistance/bridge/context/gemini-review-prompt.txt +0 -48
- data/.ai-assistance/bridge/context/gemini-review-response.md +0 -104
- data/.ai-assistance/bridge/context/project.md +0 -42
- data/.ai-assistance/bridge/inbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/.gitkeep +0 -0
- data/.ai-assistance/bridge/outbox/request-for-standards-discovery.md +0 -48
- data/.ai-assistance/bridge/queue/.gitkeep +0 -1
- data/.ai-assistance/capabilities/CLAUDE.md +0 -27
- data/.ai-assistance/capabilities/assumptions-log.md +0 -80
- data/.ai-assistance/capabilities/code.md +0 -47
- data/.ai-assistance/capabilities/connectors.md +0 -37
- data/.ai-assistance/capabilities/cowork.md +0 -55
- data/.ai-assistance/code/OVERVIEW.md +0 -155
- data/.ai-assistance/code/data_fields.md +0 -242
- data/.ai-assistance/code/dependencies.md +0 -151
- data/.ai-assistance/code/diff_as_input.md +0 -234
- data/.ai-assistance/code/diff_pairing_engine.md +0 -243
- data/.ai-assistance/code/diff_service_deep_dive.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/00_overview_and_index.md +0 -55
- data/.ai-assistance/code/ecoPortal_architecture/01_terminology_dictionary.md +0 -181
- data/.ai-assistance/code/ecoPortal_architecture/02_data_model.md +0 -192
- data/.ai-assistance/code/ecoPortal_architecture/03_api_layers.md +0 -147
- data/.ai-assistance/code/ecoPortal_architecture/04_graphql_queries_mutations.md +0 -277
- data/.ai-assistance/code/ecoPortal_architecture/05_page_workflows.md +0 -200
- data/.ai-assistance/code/ecoPortal_architecture/06_search_and_filters.md +0 -228
- data/.ai-assistance/code/ecoPortal_architecture/07_data_fields.md +0 -197
- data/.ai-assistance/code/ecoPortal_architecture/08_stages_sections.md +0 -243
- data/.ai-assistance/code/ecoPortal_architecture/09_people_contractors_locations.md +0 -196
- data/.ai-assistance/code/ecoPortal_architecture/10_forces_workflow_builder.md +0 -132
- data/.ai-assistance/code/ecoPortal_architecture/11_integration_gems.md +0 -187
- data/.ai-assistance/code/ecoPortal_architecture/12_ai_documentation_sources_gaps.md +0 -236
- data/.ai-assistance/code/ecoPortal_architecture/13_ai_infrastructure.md +0 -183
- data/.ai-assistance/code/ecoportal_schema_reference.md +0 -240
- data/.ai-assistance/code/filter_contract_matrix.md +0 -177
- data/.ai-assistance/code/graphql_domain_knowledge.md +0 -240
- data/.ai-assistance/code/refactoring/datafield-readwrite-shape-asymmetry.md +0 -71
- data/.ai-assistance/code/refactoring/opportunities.md +0 -251
- data/.ai-assistance/code/schema_analysis.md +0 -321
- data/.ai-assistance/code/search_filters.md +0 -868
- data/.ai-assistance/code/spec_coverage.md +0 -73
- data/.ai-assistance/code/template_diff_pairing_domain.md +0 -175
- data/.ai-assistance/code/workflow-command-guide.md +0 -466
- data/.ai-assistance/code/workflow-space.md +0 -353
- data/.ai-assistance/conventions/CLAUDE.md +0 -30
- data/.ai-assistance/conventions/code-working-tree-protocol.md +0 -199
- data/.ai-assistance/conventions/gitignore-rules.md +0 -42
- data/.ai-assistance/conventions/permission-guidance.md +0 -120
- data/.ai-assistance/integrations/README.md +0 -70
- data/.ai-assistance/integrations/gitkraken-mcp.md +0 -107
- data/.ai-assistance/integrations/gitlab-mcp.md +0 -123
- data/.ai-assistance/integrations/local-git.md +0 -60
- data/.ai-assistance/local_paths.example.md +0 -17
- data/.ai-assistance/projects/TODO.md +0 -118
- data/.ai-assistance/projects/api-v2-to-graphql-migration/DECISIONS.md +0 -168
- data/.ai-assistance/projects/api-v2-to-graphql-migration/INTENT.md +0 -60
- data/.ai-assistance/projects/api-v2-to-graphql-migration/TODO.md +0 -267
- data/.ai-assistance/projects/api-v2-to-graphql-migration/UPSTREAM.md +0 -53
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/csv-template-pipeline-design.md +0 -102
- data/.ai-assistance/projects/api-v2-to-graphql-migration/notes/cutover-usecase-gap-audit.md +0 -139
- data/.ai-assistance/projects/compat-layer-audit/COMPAT_AUDIT.md +0 -244
- data/.ai-assistance/projects/dynamic-model-generation/INTENT.md +0 -93
- data/.ai-assistance/projects/eco-helpers-compat/INTENT.md +0 -244
- data/.ai-assistance/projects/eco-helpers-compat/MIGRATION_GUIDE.md +0 -266
- data/.ai-assistance/projects/eco-helpers-compat/TODO.md +0 -86
- data/.ai-assistance/projects/ecoportal-api-v2-doublemodel-review/INTENT.md +0 -101
- data/.ai-assistance/projects/graphql-agent/GAP_ANALYSIS.md +0 -177
- data/.ai-assistance/projects/ooze-graphql-native-migration/DECISIONS.md +0 -161
- data/.ai-assistance/projects/ooze-graphql-native-migration/INTENT.md +0 -125
- data/.ai-assistance/projects/ooze-graphql-native-migration/INVENTORY.md +0 -136
- data/.ai-assistance/projects/ooze-graphql-native-migration/RISKS.md +0 -126
- data/.ai-assistance/projects/ooze-graphql-native-migration/TODO.md +0 -261
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-06-30-cutover-workflow-deep-review.md +0 -122
- data/.ai-assistance/projects/ooze-graphql-native-migration/analysis/2026-07-01-forces-via-workflow-commands-miss-rca.md +0 -148
- data/.ai-assistance/projects/page-model/DECISIONS.md +0 -245
- data/.ai-assistance/projects/page-model/TODO.md +0 -190
- data/.ai-assistance/projects/qa-services-delivery/DECISIONS.md +0 -93
- data/.ai-assistance/projects/qa-services-delivery/INTENT.md +0 -76
- data/.ai-assistance/projects/qa-services-delivery/PHASE3-SCOPE.md +0 -115
- data/.ai-assistance/projects/qa-services-delivery/ROADMAP.md +0 -99
- data/.ai-assistance/projects/qa-services-delivery/TODO.md +0 -81
- data/.ai-assistance/projects/search-filter-builder/INTENT.md +0 -107
- data/.ai-assistance/projects/search-filter-builder/TODO.md +0 -131
- data/.ai-assistance/projects/template-automatic-build-maintenance/INTENT.md +0 -87
- data/.ai-assistance/projects/template-automatic-build-maintenance/TODO.md +0 -108
- data/.ai-assistance/projects/template-diff-deploy/INTENT.md +0 -12
- data/.ai-assistance/projects/template-diff-deploy/TODO.md +0 -9
- data/.ai-assistance/projects/template-maintenance/DESIGN.md +0 -134
- data/.ai-assistance/projects/template-maintenance/PHASE0-FINDINGS.md +0 -93
- data/.ai-assistance/projects/template-maintenance/README.md +0 -14
- data/.ai-assistance/projects/template-model-logic/CATEGORY_CATALOG.md +0 -236
- data/.ai-assistance/projects/template-model-logic/CLASSIFICATION_SPIKE.md +0 -243
- data/.ai-assistance/projects/template-model-logic/DESIGN_NOTE.md +0 -154
- data/.ai-assistance/projects/workflow-space/TODO.md +0 -213
- data/.ai-assistance/reinstall-claude-desktop-windows.md +0 -136
- data/.ai-assistance/scripts/CLAUDE.md +0 -150
- data/.ai-assistance/scripts/bridge-inbox-check.sh +0 -75
- data/.ai-assistance/scripts/bridge-init.sh +0 -86
- data/.ai-assistance/scripts/bridge-status.sh +0 -44
- data/.ai-assistance/scripts/capabilities-check.ts +0 -104
- data/.ai-assistance/scripts/check-outbox.sh +0 -43
- data/.ai-assistance/scripts/dep_graph.rb +0 -91
- data/.ai-assistance/scripts/lock-acquire.sh +0 -103
- data/.ai-assistance/scripts/lock-multi.sh +0 -124
- data/.ai-assistance/scripts/lock-queue.sh +0 -94
- data/.ai-assistance/scripts/setup-mcps.test.ts +0 -188
- data/.ai-assistance/scripts/setup-mcps.ts +0 -234
- data/.ai-assistance/scripts/task-complete.ts +0 -74
- data/.ai-assistance/scripts/task-create.ts +0 -75
- data/.ai-assistance/scripts/task-read.ts +0 -125
- data/.ai-assistance/scripts/token-logger.js +0 -220
- data/.ai-assistance/scripts/token-report.ts +0 -158
- data/.ai-assistance/scripts/token-session-start.js +0 -66
- data/.ai-assistance/skills/ai-instructions/SKILL.md +0 -48
- data/.ai-assistance/skills/code-specs/SKILL.md +0 -69
- data/.ai-assistance/skills/corporate-policies/SKILL.md +0 -201
- data/.ai-assistance/skills/dep-graph/SKILL.md +0 -139
- data/.ai-assistance/skills/ep-ai-manager/SKILL.md +0 -417
- data/.ai-assistance/skills/gemini-assist/SKILL.md +0 -63
- data/.ai-assistance/skills/gemini-assist/gemini-mcp-server.js +0 -205
- data/.ai-assistance/skills/gemini-assist/gemini_ask.py +0 -1
- data/.ai-assistance/skills/gemini-assist/gemini_ask.rb +0 -240
- data/.ai-assistance/skills/gemini-assist/prompts/cycle_end_review.txt +0 -25
- data/.ai-assistance/skills/graphql-schema-analysis/SKILL.md +0 -261
- data/.ai-assistance/skills/procedural-memory/SKILL.md +0 -319
- data/.ai-assistance/skills/project-cycle/SKILL.md +0 -177
- data/.ai-assistance/skills/project-self-docs/SKILL.md +0 -181
- data/.ai-assistance/skills/project-self-docs/scripts/self_docs_scan.py +0 -378
- data/.ai-assistance/skills/refactor/SKILL.md +0 -62
- data/.ai-assistance/skills/rubocop/SKILL.md +0 -93
- data/.ai-assistance/skills/ruby-scripting/SKILL.md +0 -215
- data/.ai-assistance/skills/spec-generation/SKILL.md +0 -72
- data/.ai-assistance/standards-version.json +0 -22
- data/.ai-assistance/token-budget.json +0 -32
- data/.ai-assistance/version.json +0 -39
- data/.claude/settings.json +0 -150
- data/.env.example +0 -18
- data/.gitattributes +0 -15
- data/.gitignore +0 -37
- data/.gitlab-ci.yml +0 -45
- data/.markdownlint.json +0 -4
- data/.rspec +0 -3
- data/.rubocop.yml +0 -121
- data/.ruby-version +0 -1
- data/.yardopts +0 -10
- data/CLAUDE.md +0 -243
- data/Gemfile +0 -30
- data/Rakefile +0 -90
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/bin/setup.ps1 +0 -6
- data/docs/self-docs/ARCHITECTURE.md +0 -88
- data/docs/self-docs/CHANGES.jsonl +0 -12
- data/docs/self-docs/COMPLIANCE.md +0 -79
- data/docs/self-docs/CONVENTIONS.md +0 -74
- data/docs/self-docs/INTEGRATIONS.md +0 -65
- data/docs/self-docs/OPERATIONS.md +0 -76
- data/docs/self-docs/OVERVIEW.md +0 -64
- data/docs/self-docs/STATUS.md +0 -73
- data/docs/self-docs/self-docs-index.json +0 -51
- data/docs/worklog.md +0 -977
- data/ecoportal-api-graphql.gemspec +0 -40
- data/scripts/auto-worker-scheduler.sh +0 -386
- data/tests/actions_get.rb +0 -7
- data/tests/contractor_entities_get.rb +0 -20
- data/tests/contractor_entity_create.rb +0 -19
- data/tests/contractor_entity_udpate.rb +0 -20
- data/tests/dump_page_model.rb +0 -74
- data/tests/dump_template_model.rb +0 -90
- data/tests/loc_structure_get.rb +0 -10
- data/tests/loc_structure_update.rb +0 -51
- data/tests/loc_structures_get.rb +0 -15
- data/tests/local_libs.rb +0 -20
- data/tests/validate_queries.rb +0 -127
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
self_docs_scan.py -- Project self-docs OBSERVE stage (deterministic, zero-LLM core).
|
|
4
|
-
|
|
5
|
-
Skill (semantic refresh + Act): skills-library/project-self-docs/SKILL.md.
|
|
6
|
-
|
|
7
|
-
Runs FROM A TARGET REPO (the repo documenting itself), not from ep-ai-standards. It does the
|
|
8
|
-
DETERMINISTIC half of self-documentation so no interactive Claude tokens are spent on facts the
|
|
9
|
-
filesystem already knows:
|
|
10
|
-
- detect the repo type using the same signals as skills-library/discovery-manifest.yaml
|
|
11
|
-
- collect a structural fingerprint (top-level dirs, key config files, git head/branch/remote,
|
|
12
|
-
commit-activity clusters, existing .ai-assistance/code specs, CI files, doc files)
|
|
13
|
-
- compute a stable content hash per self-doc so refreshes are idempotent and the changelog
|
|
14
|
-
only records REAL changes
|
|
15
|
-
- emit a structured JSON "observation" the skill consumes to fill the self-doc set
|
|
16
|
-
|
|
17
|
-
It does NOT write project prose, summarise purpose, or judge architecture -- that semantic work is
|
|
18
|
-
the LLM skill's job (batched to Gemini per the token-frugality rule), gated by human review before
|
|
19
|
-
any egress. This scanner is the thermometer; the skill is the (human-gated) actuator.
|
|
20
|
-
|
|
21
|
-
HARD CONSTRAINTS:
|
|
22
|
-
- READ-ONLY by default. It never writes into the target repo except the self-doc index/changelog
|
|
23
|
-
under --write, and even then only to the self-docs directory (default docs/self-docs/).
|
|
24
|
-
- NO absolute paths are emitted into any committed artefact: paths are repo-relative.
|
|
25
|
-
- NO Confluence / network egress. Publish target is UNDECIDED (see SKILL.md); this script only
|
|
26
|
-
ever touches the local filesystem.
|
|
27
|
-
|
|
28
|
-
Usage:
|
|
29
|
-
python self_docs_scan.py # human summary of the observation (target = cwd)
|
|
30
|
-
python self_docs_scan.py --json # structured observation to stdout
|
|
31
|
-
python self_docs_scan.py --target <path> # observe a different LOCAL repo root (convenience
|
|
32
|
-
# only -- NEVER to generate another repo's self-docs
|
|
33
|
-
# from here; run the skill in that repo's own session)
|
|
34
|
-
python self_docs_scan.py --out docs/self-docs # self-docs dir (default docs/self-docs)
|
|
35
|
-
python self_docs_scan.py --write # (re)write the machine index + changelog only
|
|
36
|
-
"""
|
|
37
|
-
import argparse
|
|
38
|
-
import hashlib
|
|
39
|
-
import json
|
|
40
|
-
import os
|
|
41
|
-
import re
|
|
42
|
-
import subprocess
|
|
43
|
-
import sys
|
|
44
|
-
from datetime import datetime, timezone
|
|
45
|
-
|
|
46
|
-
SCHEMA_VERSION = "1.1"
|
|
47
|
-
|
|
48
|
-
# Self-doc set: stable filenames the skill fills in. Keeping this list here (not only in the
|
|
49
|
-
# template) lets the deterministic scanner report which docs already exist and which are missing,
|
|
50
|
-
# and lets the index/changelog be generated without the LLM.
|
|
51
|
-
SELF_DOC_SET = [
|
|
52
|
-
("OVERVIEW.md", "What the project is, who it serves, current priority, discovery & access"),
|
|
53
|
-
("ARCHITECTURE.md", "Top-level structure, key components, how they fit"),
|
|
54
|
-
("CONVENTIONS.md", "Coding style, branch naming, commit style, test conventions"),
|
|
55
|
-
("INTEGRATIONS.md", "External services + data + permissions, upstream/downstream repos"),
|
|
56
|
-
("STATUS.md", "Active work, migration state, known risks, open questions"),
|
|
57
|
-
("COMPLIANCE.md", "ISO-27001/audit, data classes + PII, vendors, AI content, leak controls"),
|
|
58
|
-
("OPERATIONS.md", "Infrastructure, public exposure + Cloudflare, DevOps audit, usage KPIs"),
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
# Detection signals mirror skills-library/discovery-manifest.yaml. Order matters: first match wins.
|
|
62
|
-
# (file, ...) => all must exist; file_any => any of the globs; not_file => must be absent.
|
|
63
|
-
REPO_TYPE_RULES = [
|
|
64
|
-
("aws-cdk", {"file": ["cdk.json"]}),
|
|
65
|
-
("browser-extension", {"file": ["manifest.json"], "file_any": ["package.json", "Gruntfile.js", "webpack.config.js"]}),
|
|
66
|
-
("ruby-gem", {"file_any": ["*.gemspec"]}),
|
|
67
|
-
("rails-app", {"file": ["config/application.rb"]}),
|
|
68
|
-
("ruby-scripts", {"file": ["Gemfile"], "not_file": ["*.gemspec", "config/application.rb"]}),
|
|
69
|
-
("typescript-lib", {"file": ["package.json"], "not_file": ["cdk.json", "manifest.json"]}),
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
KEY_CONFIG_FILES = [
|
|
73
|
-
"package.json", "Gemfile", "cdk.json", "manifest.json", "go.mod", "Cargo.toml",
|
|
74
|
-
"pyproject.toml", "requirements.txt", "tsconfig.json", ".eslintrc", ".eslintrc.json",
|
|
75
|
-
".eslintrc.js", ".prettierrc", ".rubocop.yml", ".editorconfig",
|
|
76
|
-
]
|
|
77
|
-
CI_FILES = [".gitlab-ci.yml", "Jenkinsfile", "Makefile"]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def _iso_now():
|
|
81
|
-
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _exists(root, rel):
|
|
85
|
-
return os.path.exists(os.path.join(root, rel))
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _glob_any(root, patterns):
|
|
89
|
-
import fnmatch
|
|
90
|
-
try:
|
|
91
|
-
top = os.listdir(root)
|
|
92
|
-
except OSError:
|
|
93
|
-
return False
|
|
94
|
-
for pat in patterns:
|
|
95
|
-
if any(fnmatch.fnmatch(name, pat) for name in top):
|
|
96
|
-
return True
|
|
97
|
-
return False
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def detect_repo_type(root):
|
|
101
|
-
for type_name, rule in REPO_TYPE_RULES:
|
|
102
|
-
ok = True
|
|
103
|
-
for f in rule.get("file", []):
|
|
104
|
-
if not _exists(root, f):
|
|
105
|
-
ok = False
|
|
106
|
-
break
|
|
107
|
-
if ok and rule.get("file_any"):
|
|
108
|
-
ok = _glob_any(root, rule["file_any"])
|
|
109
|
-
if ok and rule.get("not_file"):
|
|
110
|
-
for f in rule["not_file"]:
|
|
111
|
-
if "*" in f:
|
|
112
|
-
if _glob_any(root, [f]):
|
|
113
|
-
ok = False
|
|
114
|
-
break
|
|
115
|
-
elif _exists(root, f):
|
|
116
|
-
ok = False
|
|
117
|
-
break
|
|
118
|
-
if ok:
|
|
119
|
-
return type_name
|
|
120
|
-
return "unknown"
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _git(root, args):
|
|
124
|
-
try:
|
|
125
|
-
out = subprocess.run(
|
|
126
|
-
["git", "-C", root] + args,
|
|
127
|
-
capture_output=True, text=True, timeout=15,
|
|
128
|
-
)
|
|
129
|
-
return out.stdout.strip() if out.returncode == 0 else ""
|
|
130
|
-
except (OSError, subprocess.SubprocessError):
|
|
131
|
-
return ""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def git_facts(root):
|
|
135
|
-
branch = _git(root, ["rev-parse", "--abbrev-ref", "HEAD"])
|
|
136
|
-
head = _git(root, ["rev-parse", "--short", "HEAD"])
|
|
137
|
-
remote = _git(root, ["config", "--get", "remote.origin.url"])
|
|
138
|
-
# Real repo name from the remote, never a local folder name that may be renamed.
|
|
139
|
-
repo_name = ""
|
|
140
|
-
if remote and "/" in remote:
|
|
141
|
-
repo_name = re.sub(r"\.git$", "", remote.rstrip("/").split("/")[-1])
|
|
142
|
-
# Activity clusters: which top-level dirs churn most in the last 50 commits.
|
|
143
|
-
log = _git(root, ["log", "-50", "--name-only", "--pretty=format:"])
|
|
144
|
-
clusters = {}
|
|
145
|
-
for line in log.splitlines():
|
|
146
|
-
line = line.strip()
|
|
147
|
-
if not line:
|
|
148
|
-
continue
|
|
149
|
-
top = line.split("/")[0] if "/" in line else line
|
|
150
|
-
clusters[top] = clusters.get(top, 0) + 1
|
|
151
|
-
top_clusters = sorted(clusters.items(), key=lambda kv: kv[1], reverse=True)[:6]
|
|
152
|
-
return {
|
|
153
|
-
"branch": branch,
|
|
154
|
-
"head": head,
|
|
155
|
-
"repo_name": repo_name,
|
|
156
|
-
"has_remote": bool(remote),
|
|
157
|
-
"activity_clusters": [{"path": k, "touches": v} for k, v in top_clusters],
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def structure(root):
|
|
162
|
-
dirs, files = [], []
|
|
163
|
-
try:
|
|
164
|
-
for name in sorted(os.listdir(root)):
|
|
165
|
-
if name.startswith(".git"):
|
|
166
|
-
continue
|
|
167
|
-
full = os.path.join(root, name)
|
|
168
|
-
if os.path.isdir(full):
|
|
169
|
-
dirs.append(name)
|
|
170
|
-
else:
|
|
171
|
-
files.append(name)
|
|
172
|
-
except OSError:
|
|
173
|
-
pass
|
|
174
|
-
return {
|
|
175
|
-
"top_level_dirs": dirs,
|
|
176
|
-
"key_config_files": [f for f in KEY_CONFIG_FILES if _exists(root, f)],
|
|
177
|
-
"ci_files": [f for f in CI_FILES if _exists(root, f)],
|
|
178
|
-
"has_readme": _exists(root, "README.md") or _exists(root, "README"),
|
|
179
|
-
"has_changelog": _exists(root, "CHANGELOG.md"),
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def existing_context(root):
|
|
184
|
-
"""Reusable inputs that already exist -- the skill composes from these, not from scratch."""
|
|
185
|
-
ctx = {"code_specs": [], "repo_context": None, "conventions": None}
|
|
186
|
-
code_dir = os.path.join(root, ".ai-assistance", "code")
|
|
187
|
-
if os.path.isdir(code_dir):
|
|
188
|
-
for dirpath, _, filenames in os.walk(code_dir):
|
|
189
|
-
for fn in filenames:
|
|
190
|
-
if fn.endswith(".md"):
|
|
191
|
-
rel = os.path.relpath(os.path.join(dirpath, fn), root).replace("\\", "/")
|
|
192
|
-
ctx["code_specs"].append(rel)
|
|
193
|
-
ctx["code_specs"].sort()
|
|
194
|
-
local = os.path.join(root, ".ai-assistance", "local")
|
|
195
|
-
if os.path.isfile(os.path.join(local, "repo-context.md")):
|
|
196
|
-
ctx["repo_context"] = ".ai-assistance/local/repo-context.md"
|
|
197
|
-
if os.path.isfile(os.path.join(local, "conventions.md")):
|
|
198
|
-
ctx["conventions"] = ".ai-assistance/local/conventions.md"
|
|
199
|
-
return ctx
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def _content_hash(path):
|
|
203
|
-
"""Hash the self-doc BODY (below the frontmatter) so index/changelog record real changes,
|
|
204
|
-
not just a re-run timestamp. Missing file -> None."""
|
|
205
|
-
if not os.path.isfile(path):
|
|
206
|
-
return None
|
|
207
|
-
with open(path, encoding="utf-8", errors="replace") as fh:
|
|
208
|
-
text = fh.read()
|
|
209
|
-
body = text
|
|
210
|
-
if text.startswith("---"):
|
|
211
|
-
end = text.find("\n---", 3)
|
|
212
|
-
if end != -1:
|
|
213
|
-
body = text[end + 4:]
|
|
214
|
-
# Normalise line endings + trailing whitespace so cross-platform re-runs are stable.
|
|
215
|
-
norm = "\n".join(line.rstrip() for line in body.replace("\r\n", "\n").split("\n")).strip()
|
|
216
|
-
return hashlib.sha256(norm.encode("utf-8")).hexdigest()[:16]
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def self_doc_status(root, out_rel):
|
|
220
|
-
out_dir = os.path.join(root, out_rel)
|
|
221
|
-
docs = []
|
|
222
|
-
for fname, purpose in SELF_DOC_SET:
|
|
223
|
-
path = os.path.join(out_dir, fname)
|
|
224
|
-
docs.append({
|
|
225
|
-
"file": f"{out_rel}/{fname}".replace("\\", "/"),
|
|
226
|
-
"purpose": purpose,
|
|
227
|
-
"exists": os.path.isfile(path),
|
|
228
|
-
"content_hash": _content_hash(path),
|
|
229
|
-
})
|
|
230
|
-
return docs
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def observe(root, out_rel):
|
|
234
|
-
return {
|
|
235
|
-
"schema_version": SCHEMA_VERSION,
|
|
236
|
-
"observed_at": _iso_now(),
|
|
237
|
-
"repo_type": detect_repo_type(root),
|
|
238
|
-
"git": git_facts(root),
|
|
239
|
-
"structure": structure(root),
|
|
240
|
-
"existing_context": existing_context(root),
|
|
241
|
-
"self_doc_set": self_doc_status(root, out_rel),
|
|
242
|
-
"out_dir": out_rel.replace("\\", "/"),
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
# == index + changelog (deterministic writers) ======================================
|
|
247
|
-
|
|
248
|
-
def _read_json(path, default):
|
|
249
|
-
if not os.path.isfile(path):
|
|
250
|
-
return default
|
|
251
|
-
try:
|
|
252
|
-
with open(path, encoding="utf-8") as fh:
|
|
253
|
-
return json.load(fh)
|
|
254
|
-
except (OSError, ValueError):
|
|
255
|
-
return default
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def write_index_and_changelog(root, out_rel, obs):
|
|
259
|
-
"""Rewrite the machine-readable index and append changelog deltas -- deterministically.
|
|
260
|
-
|
|
261
|
-
The index (self-docs-index.json) is the machine surface a central hub consumes: schema
|
|
262
|
-
version, repo identity, and a hash per doc. The changelog (CHANGES.jsonl) appends one line
|
|
263
|
-
per doc whose content hash CHANGED since the previous index -- so the hub sees real deltas,
|
|
264
|
-
and a no-op re-run appends nothing (idempotent)."""
|
|
265
|
-
out_dir = os.path.join(root, out_rel)
|
|
266
|
-
os.makedirs(out_dir, exist_ok=True)
|
|
267
|
-
index_path = os.path.join(out_dir, "self-docs-index.json")
|
|
268
|
-
changes_path = os.path.join(out_dir, "CHANGES.jsonl")
|
|
269
|
-
|
|
270
|
-
prev = _read_json(index_path, {})
|
|
271
|
-
prev_hashes = {d["file"]: d.get("content_hash") for d in prev.get("docs", [])}
|
|
272
|
-
|
|
273
|
-
docs = obs["self_doc_set"]
|
|
274
|
-
now = obs["observed_at"]
|
|
275
|
-
deltas = []
|
|
276
|
-
for d in docs:
|
|
277
|
-
new_h = d["content_hash"]
|
|
278
|
-
old_h = prev_hashes.get(d["file"])
|
|
279
|
-
if new_h != old_h:
|
|
280
|
-
deltas.append({
|
|
281
|
-
"ts": now,
|
|
282
|
-
"file": d["file"],
|
|
283
|
-
"change": ("created" if old_h is None and new_h is not None
|
|
284
|
-
else "removed" if new_h is None
|
|
285
|
-
else "updated"),
|
|
286
|
-
"from_hash": old_h,
|
|
287
|
-
"to_hash": new_h,
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
index = {
|
|
291
|
-
"schema_version": SCHEMA_VERSION,
|
|
292
|
-
"generated_at": now,
|
|
293
|
-
"repo_name": obs["git"].get("repo_name") or "",
|
|
294
|
-
"repo_type": obs["repo_type"],
|
|
295
|
-
"head": obs["git"].get("head", ""),
|
|
296
|
-
"docs": [
|
|
297
|
-
{"file": d["file"], "purpose": d["purpose"],
|
|
298
|
-
"exists": d["exists"], "content_hash": d["content_hash"]}
|
|
299
|
-
for d in docs
|
|
300
|
-
],
|
|
301
|
-
}
|
|
302
|
-
with open(index_path, "w", encoding="utf-8") as fh:
|
|
303
|
-
json.dump(index, fh, indent=2)
|
|
304
|
-
fh.write("\n")
|
|
305
|
-
|
|
306
|
-
if deltas:
|
|
307
|
-
with open(changes_path, "a", encoding="utf-8") as fh:
|
|
308
|
-
for delta in deltas:
|
|
309
|
-
fh.write(json.dumps(delta) + "\n")
|
|
310
|
-
|
|
311
|
-
return {"index": f"{out_rel}/self-docs-index.json".replace("\\", "/"),
|
|
312
|
-
"changelog": f"{out_rel}/CHANGES.jsonl".replace("\\", "/"),
|
|
313
|
-
"deltas_appended": len(deltas)}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def _human(obs):
|
|
317
|
-
g = obs["git"]
|
|
318
|
-
print(f"self-docs observation (schema {obs['schema_version']})")
|
|
319
|
-
print(f" repo_type : {obs['repo_type']}")
|
|
320
|
-
print(f" repo_name : {g.get('repo_name') or '(no remote)'} @ {g.get('head') or '?'} "
|
|
321
|
-
f"on {g.get('branch') or '?'}")
|
|
322
|
-
print(f" out_dir : {obs['out_dir']}")
|
|
323
|
-
print(" top-level dirs:", ", ".join(obs["structure"]["top_level_dirs"]) or "(none)")
|
|
324
|
-
if obs["structure"]["key_config_files"]:
|
|
325
|
-
print(" config :", ", ".join(obs["structure"]["key_config_files"]))
|
|
326
|
-
if g.get("activity_clusters"):
|
|
327
|
-
hot = ", ".join(f"{c['path']}({c['touches']})" for c in g["activity_clusters"])
|
|
328
|
-
print(" hot paths :", hot)
|
|
329
|
-
ec = obs["existing_context"]
|
|
330
|
-
reuse = []
|
|
331
|
-
if ec["repo_context"]:
|
|
332
|
-
reuse.append("ai-discovery repo-context.md")
|
|
333
|
-
if ec["conventions"]:
|
|
334
|
-
reuse.append("ai-discovery conventions.md")
|
|
335
|
-
if ec["code_specs"]:
|
|
336
|
-
reuse.append(f"{len(ec['code_specs'])} code-specs")
|
|
337
|
-
print(" reuse from:", ", ".join(reuse) or "(none -- run ai-discovery/code-specs first)")
|
|
338
|
-
print(" self-doc set:")
|
|
339
|
-
for d in obs["self_doc_set"]:
|
|
340
|
-
state = "present" if d["exists"] else "MISSING"
|
|
341
|
-
print(f" [{state:7}] {d['file']} ({d['content_hash'] or '-'})")
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def main():
|
|
345
|
-
ap = argparse.ArgumentParser(description="Deterministic project self-docs observation")
|
|
346
|
-
ap.add_argument("--target", default=".",
|
|
347
|
-
help="LOCAL repo root to observe (default: cwd). Convenience only -- do NOT use to "
|
|
348
|
-
"generate ANOTHER repo's self-docs from here; run the skill in that repo's own "
|
|
349
|
-
"session (self-docs are owned + generated by the repo that documents itself).")
|
|
350
|
-
ap.add_argument("--out", default="docs/self-docs", help="self-docs dir (default: docs/self-docs)")
|
|
351
|
-
ap.add_argument("--json", action="store_true", help="emit the structured observation")
|
|
352
|
-
ap.add_argument("--write", action="store_true",
|
|
353
|
-
help="(re)write the machine index + changelog deltas only")
|
|
354
|
-
args = ap.parse_args()
|
|
355
|
-
|
|
356
|
-
root = os.path.abspath(args.target)
|
|
357
|
-
if not os.path.isdir(root):
|
|
358
|
-
print(f"[self-docs] target not a directory: {root}", file=sys.stderr)
|
|
359
|
-
return 2
|
|
360
|
-
|
|
361
|
-
obs = observe(root, args.out)
|
|
362
|
-
|
|
363
|
-
if args.write:
|
|
364
|
-
result = write_index_and_changelog(root, args.out, obs)
|
|
365
|
-
obs["_write_result"] = result
|
|
366
|
-
|
|
367
|
-
if args.json:
|
|
368
|
-
print(json.dumps(obs, indent=2))
|
|
369
|
-
else:
|
|
370
|
-
_human(obs)
|
|
371
|
-
if args.write:
|
|
372
|
-
r = obs["_write_result"]
|
|
373
|
-
print(f" wrote index -> {r['index']}; changelog deltas appended: {r['deltas_appended']}")
|
|
374
|
-
return 0
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if __name__ == "__main__":
|
|
378
|
-
sys.exit(main())
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# SKILL: Refactor
|
|
2
|
-
|
|
3
|
-
**Purpose:** Identify, log, and (with developer confirmation) apply refactoring opportunities. Never apply changes without explicit confirmation.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
- You notice a structural issue while working on something else — log it, don't fix it silently.
|
|
10
|
-
- The developer asks for a refactor audit of an area.
|
|
11
|
-
- A cycle-end review surfaces cleanup opportunities.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Criteria for a Refactor Candidate
|
|
16
|
-
|
|
17
|
-
Flag something when it fails one or more of:
|
|
18
|
-
|
|
19
|
-
- **Understandability:** Is it clear what this does and why?
|
|
20
|
-
- **Dependency tracking:** Are dependencies implicit or tangled?
|
|
21
|
-
- **Separation of concerns:** Is this class/method doing more than one thing?
|
|
22
|
-
- **Simplicity:** Could this be simpler without losing correctness?
|
|
23
|
-
- **Alignment:** Does this match the conventions of the rest of the codebase?
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Log Format
|
|
28
|
-
|
|
29
|
-
Refactoring opportunities are logged in `.ai-assistance/code/refactoring/<area>.md`.
|
|
30
|
-
|
|
31
|
-
Each entry:
|
|
32
|
-
|
|
33
|
-
```markdown
|
|
34
|
-
## [SHORT-ID] <Title>
|
|
35
|
-
|
|
36
|
-
- **File(s):** `lib/ecoportal/...`
|
|
37
|
-
- **Type:** [extract_class | extract_method | rename | simplify | reorganise | other]
|
|
38
|
-
- **Priority:** [high | medium | low]
|
|
39
|
-
- **Status:** [open | confirmed | applied | dismissed]
|
|
40
|
-
- **Identified:** YYYY-MM-DD
|
|
41
|
-
|
|
42
|
-
**Observation:**
|
|
43
|
-
What the issue is, concisely.
|
|
44
|
-
|
|
45
|
-
**Proposed change:**
|
|
46
|
-
What you'd do to fix it. High-level — not code unless it's very short.
|
|
47
|
-
|
|
48
|
-
**Risk:**
|
|
49
|
-
Any backwards-compat or downstream risk if applied.
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## Workflow
|
|
55
|
-
|
|
56
|
-
1. Identify opportunity → log it in the appropriate refactoring file.
|
|
57
|
-
2. Present a summary to the developer: "I've logged N refactoring opportunities in `.ai-assistance/code/refactoring/`. Want to review them?"
|
|
58
|
-
3. Developer confirms which ones to apply → mark as `confirmed`.
|
|
59
|
-
4. Apply one at a time, update specs if needed, mark as `applied`.
|
|
60
|
-
5. If dismissed by developer → mark as `dismissed` with a brief reason.
|
|
61
|
-
|
|
62
|
-
**Never apply a refactor without a `confirmed` status.**
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# SKILL: Rubocop & Spec Runner
|
|
2
|
-
|
|
3
|
-
**Purpose:** Run Rubocop on in-scope files and `rake spec`, then surface only actionable
|
|
4
|
-
offences. Use this continuously as new/modified files accumulate — not deferred to the end.
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## When to Use
|
|
9
|
-
|
|
10
|
-
- After writing or modifying any Ruby file.
|
|
11
|
-
- Before committing a change.
|
|
12
|
-
- Any time TODO 6.1 says "rubocop clean on all new/modified files".
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Running Rubocop on Specific Files
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
bundle exec rubocop --format json <file1> [file2 ...] | \
|
|
20
|
-
ruby -rjson -e '
|
|
21
|
-
data = JSON.parse($stdin.read)
|
|
22
|
-
offences = data["files"].flat_map do |f|
|
|
23
|
-
f["offenses"].map { |o| { file: f["path"], **o.transform_keys(&:to_sym) } }
|
|
24
|
-
end
|
|
25
|
-
offences.reject! { |o| o[:cop_name].start_with?("Lint/RedundantCopDisableDirective") }
|
|
26
|
-
if offences.empty?
|
|
27
|
-
puts "No offences."
|
|
28
|
-
else
|
|
29
|
-
offences.each do |o|
|
|
30
|
-
puts "#{o[:file]}:#{o[:location][:line]}:#{o[:location][:column]} " \
|
|
31
|
-
"[#{o[:severity]}] #{o[:cop_name]} — #{o[:message]}"
|
|
32
|
-
end
|
|
33
|
-
puts "\n#{offences.size} offence(s)."
|
|
34
|
-
end
|
|
35
|
-
'
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Key flags:**
|
|
39
|
-
- `--format json` — machine-parseable output, avoids ANSI noise.
|
|
40
|
-
- No `--autocorrect` here — apply corrections explicitly when needed (see below).
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Auto-correcting Safe Offences
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
bundle exec rubocop --autocorrect --format json <file1> [file2 ...]
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Only use `--autocorrect` (not `--autocorrect-all`) to limit to safe cops.
|
|
51
|
-
Always re-read the file after correction and verify the diff is sensible.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## Running the Full Suite
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
bundle exec rake spec
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Run after a meaningful unit of work is complete. Failures must be resolved before moving on.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Workflow
|
|
66
|
-
|
|
67
|
-
1. Write or edit a file.
|
|
68
|
-
2. Run rubocop on that file (command above).
|
|
69
|
-
3. Fix all reported offences (except those covered by existing `# rubocop:disable` comments — the
|
|
70
|
-
disable is intentional; do not remove it).
|
|
71
|
-
4. Re-run rubocop to confirm zero offences.
|
|
72
|
-
5. When a group of files is complete, run `rake spec` to confirm no regressions.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Rules
|
|
77
|
-
|
|
78
|
-
- **Do not second-guess `rubocop:disable` inline comments.** They are intentional suppressions
|
|
79
|
-
chosen by the developer or a previous agent. Leave them in place.
|
|
80
|
-
- **Do not add new `rubocop:disable` comments** unless the developer explicitly instructs it.
|
|
81
|
-
Fix the offence instead.
|
|
82
|
-
- **Surface every remaining offence** — do not filter by severity. Let the developer decide
|
|
83
|
-
what to defer.
|
|
84
|
-
- `Lint/RedundantCopDisableDirective` offences are suppressed in the output above because
|
|
85
|
-
they appear when a disable comment is no longer needed; rubocop handles their cleanup itself.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Linked TODOs
|
|
90
|
-
|
|
91
|
-
- **6.0** — This skill (done once built).
|
|
92
|
-
- **6.1** — Run continuously on all new/modified files throughout the migration.
|
|
93
|
-
- **6.2** — `rake spec` coverage for `HttpClient#execute`, `Fragment.assemble`, end-to-end query.
|