project-tiny-context-harness 0.2.84 → 0.6.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 +163 -428
- package/assets/README.md +283 -455
- package/assets/README.zh-CN.md +199 -123
- package/assets/agents/AGENTS_CORE.md +54 -67
- package/assets/context_templates/context.toml +3 -0
- package/assets/context_templates/global.md +6 -6
- package/assets/github/harness.yml +2 -2
- package/assets/skills/context_development_engineer/SKILL.md +91 -107
- package/assets/skills/context_product_plan/SKILL.md +18 -28
- package/assets/skills/context_surface_contract/SKILL.md +35 -38
- package/assets/skills/context_uiux_design/SKILL.md +31 -30
- package/assets/skills/long-task-workflow/SKILL.md +76 -0
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -0
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +41 -0
- package/assets/skills/long-task-workflow/references/contract-authoring.md +62 -0
- package/assets/skills/long-task-workflow/references/evidence-design.md +45 -0
- package/assets/skills/normal-long-task/SKILL.md +12 -489
- package/assets/skills/source-plan-authoring/SKILL.md +379 -0
- package/assets/tools/validate_context.py +293 -127
- package/dist/cli.js +3 -1
- package/dist/commands/check-modularity.js +33 -10
- package/dist/commands/composite-campaign.d.ts +1 -0
- package/dist/commands/composite-campaign.js +8 -0
- package/dist/commands/composite-long-task.d.ts +1 -6
- package/dist/commands/composite-long-task.js +7 -157
- package/dist/commands/delivery-set.d.ts +1 -0
- package/dist/commands/delivery-set.js +8 -0
- package/dist/commands/disable.d.ts +1 -0
- package/dist/commands/disable.js +15 -0
- package/dist/commands/enable.d.ts +1 -0
- package/dist/commands/enable.js +14 -0
- package/dist/commands/export-context-args.js +13 -4
- package/dist/commands/export-context.js +18 -7
- package/dist/commands/index.js +42 -30
- package/dist/commands/init.js +12 -6
- package/dist/commands/long-task-authoring.d.ts +3 -0
- package/dist/commands/long-task-authoring.js +92 -0
- package/dist/commands/long-task-explain.d.ts +1 -0
- package/dist/commands/long-task-explain.js +52 -0
- package/dist/commands/long-task.d.ts +1 -0
- package/dist/commands/long-task.js +226 -0
- package/dist/commands/package-source.js +2 -2
- package/dist/commands/upgrade.js +10 -10
- package/dist/index.d.ts +11 -1
- package/dist/index.js +8 -0
- package/dist/lib/config.js +34 -8
- package/dist/lib/context-default-footprint.d.ts +16 -0
- package/dist/lib/context-default-footprint.js +107 -0
- package/dist/lib/context-export.js +105 -41
- package/dist/lib/context-graph-snapshot.d.ts +18 -0
- package/dist/lib/context-graph-snapshot.js +171 -0
- package/dist/lib/context-manifest-schema.d.ts +28 -0
- package/dist/lib/context-manifest-schema.js +150 -0
- package/dist/lib/context-manifest.js +28 -20
- package/dist/lib/context-templates.js +5 -5
- package/dist/lib/design-md.js +1 -1
- package/dist/lib/doctor.js +54 -1
- package/dist/lib/harness-root.js +25 -8
- package/dist/lib/init.js +6 -3
- package/dist/lib/legacy-managed-scan.js +23 -7
- package/dist/lib/legacy-sdlc-migration.js +21 -11
- package/dist/lib/long-task-acceptance-reference.d.ts +22 -0
- package/dist/lib/long-task-acceptance-reference.js +46 -0
- package/dist/lib/long-task-acceptance-shape.d.ts +5 -0
- package/dist/lib/long-task-acceptance-shape.js +131 -0
- package/dist/lib/long-task-activation-validation.d.ts +21 -0
- package/dist/lib/long-task-activation-validation.js +136 -0
- package/dist/lib/long-task-artifacts.d.ts +5 -0
- package/dist/lib/long-task-artifacts.js +34 -0
- package/dist/lib/long-task-assertions-v2.d.ts +22 -0
- package/dist/lib/long-task-assertions-v2.js +212 -0
- package/dist/lib/long-task-authoring-authority-preview.d.ts +5 -0
- package/dist/lib/long-task-authoring-authority-preview.js +85 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.d.ts +8 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +179 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.d.ts +8 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.js +95 -0
- package/dist/lib/long-task-authoring-preflight-types.d.ts +46 -0
- package/dist/lib/long-task-authoring-preflight-types.js +38 -0
- package/dist/lib/long-task-authoring-preflight.d.ts +3 -0
- package/dist/lib/long-task-authoring-preflight.js +44 -0
- package/dist/lib/long-task-authority-material-diff.d.ts +17 -0
- package/dist/lib/long-task-authority-material-diff.js +152 -0
- package/dist/lib/long-task-authority-materials.d.ts +7 -0
- package/dist/lib/long-task-authority-materials.js +83 -0
- package/dist/lib/long-task-authority-policy.d.ts +168 -0
- package/dist/lib/long-task-authority-policy.js +181 -0
- package/dist/lib/long-task-authority-revision-details.d.ts +19 -0
- package/dist/lib/long-task-authority-revision-details.js +192 -0
- package/dist/lib/long-task-authority-revision-enforcement.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-enforcement.js +41 -0
- package/dist/lib/long-task-authority-revision-types.d.ts +50 -0
- package/dist/lib/long-task-authority-revision-types.js +1 -0
- package/dist/lib/long-task-authority-revision.d.ts +4 -0
- package/dist/lib/long-task-authority-revision.js +188 -0
- package/dist/lib/long-task-authority-transition-policy.d.ts +62 -0
- package/dist/lib/long-task-authority-transition-policy.js +62 -0
- package/dist/lib/long-task-authority-types.d.ts +96 -0
- package/dist/lib/long-task-authority-types.js +1 -0
- package/dist/lib/long-task-authority.d.ts +7 -0
- package/dist/lib/long-task-authority.js +142 -0
- package/dist/lib/long-task-boundary-check.d.ts +15 -0
- package/dist/lib/long-task-boundary-check.js +9 -0
- package/dist/lib/long-task-check-evidence-decoder.d.ts +3 -0
- package/dist/lib/long-task-check-evidence-decoder.js +74 -0
- package/dist/lib/long-task-check-execution-policy.d.ts +18 -0
- package/dist/lib/long-task-check-execution-policy.js +36 -0
- package/dist/lib/long-task-check-runner.d.ts +2 -0
- package/dist/lib/long-task-check-runner.js +181 -0
- package/dist/lib/long-task-check-shape.d.ts +2 -0
- package/dist/lib/long-task-check-shape.js +136 -0
- package/dist/lib/long-task-claim-definitions.d.ts +7 -0
- package/dist/lib/long-task-claim-definitions.js +78 -0
- package/dist/lib/long-task-claim-proof-policy.d.ts +4 -0
- package/dist/lib/long-task-claim-proof-policy.js +57 -0
- package/dist/lib/long-task-claims.d.ts +10 -0
- package/dist/lib/long-task-claims.js +187 -0
- package/dist/lib/long-task-context-authority-topology.d.ts +7 -0
- package/dist/lib/long-task-context-authority-topology.js +37 -0
- package/dist/lib/long-task-context-authority.d.ts +14 -0
- package/dist/lib/long-task-context-authority.js +66 -0
- package/dist/lib/long-task-contract-types.d.ts +203 -0
- package/dist/lib/long-task-contract-types.js +1 -0
- package/dist/lib/long-task-counterfactual-claim-policy.d.ts +10 -0
- package/dist/lib/long-task-counterfactual-claim-policy.js +57 -0
- package/dist/lib/long-task-counterfactual-sandbox.d.ts +6 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +87 -0
- package/dist/lib/long-task-counterfactual-types.d.ts +24 -0
- package/dist/lib/long-task-counterfactual-types.js +1 -0
- package/dist/lib/long-task-delivery-compiler.d.ts +10 -0
- package/dist/lib/long-task-delivery-compiler.js +129 -0
- package/dist/lib/long-task-delivery-parser.d.ts +13 -0
- package/dist/lib/long-task-delivery-parser.js +90 -0
- package/dist/lib/long-task-delivery-preflight.d.ts +5 -0
- package/dist/lib/long-task-delivery-preflight.js +151 -0
- package/dist/lib/long-task-delivery-shape.d.ts +5 -0
- package/dist/lib/long-task-delivery-shape.js +5 -0
- package/dist/lib/long-task-delivery-types.d.ts +8 -0
- package/dist/lib/long-task-delivery-types.js +8 -0
- package/dist/lib/long-task-delivery-validation.d.ts +3 -0
- package/dist/lib/long-task-delivery-validation.js +175 -0
- package/dist/lib/long-task-evidence-adapter-policy.d.ts +4 -0
- package/dist/lib/long-task-evidence-adapter-policy.js +15 -0
- package/dist/lib/long-task-evidence-adapter-types.d.ts +1 -0
- package/dist/lib/long-task-evidence-adapter-types.js +1 -0
- package/dist/lib/long-task-evidence-findings.d.ts +5 -0
- package/dist/lib/long-task-evidence-findings.js +113 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +4 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.js +98 -0
- package/dist/lib/long-task-evidence-v2.d.ts +5 -0
- package/dist/lib/long-task-evidence-v2.js +226 -0
- package/dist/lib/long-task-explain-acceptance-link.d.ts +46 -0
- package/dist/lib/long-task-explain-acceptance-link.js +96 -0
- package/dist/lib/long-task-explain-claim-links.d.ts +45 -0
- package/dist/lib/long-task-explain-claim-links.js +88 -0
- package/dist/lib/long-task-explain-source-links.d.ts +93 -0
- package/dist/lib/long-task-explain-source-links.js +51 -0
- package/dist/lib/long-task-final-v2.d.ts +2 -0
- package/dist/lib/long-task-final-v2.js +124 -0
- package/dist/lib/long-task-finding-context.d.ts +3 -0
- package/dist/lib/long-task-finding-context.js +63 -0
- package/dist/lib/long-task-freshness.d.ts +3 -0
- package/dist/lib/long-task-freshness.js +80 -0
- package/dist/lib/long-task-hook-install.d.ts +9 -0
- package/dist/lib/long-task-hook-install.js +184 -0
- package/dist/lib/long-task-hook-preflight.d.ts +7 -0
- package/dist/lib/long-task-hook-preflight.js +58 -0
- package/dist/lib/long-task-observation-ownership.d.ts +2 -0
- package/dist/lib/long-task-observation-ownership.js +22 -0
- package/dist/lib/long-task-outcome-parser.d.ts +4 -0
- package/dist/lib/long-task-outcome-parser.js +100 -0
- package/dist/lib/long-task-paths.d.ts +49 -0
- package/dist/lib/long-task-paths.js +418 -0
- package/dist/lib/long-task-playwright-counterfactual-policy.d.ts +7 -0
- package/dist/lib/long-task-playwright-counterfactual-policy.js +125 -0
- package/dist/lib/long-task-playwright-evidence.d.ts +6 -0
- package/dist/lib/long-task-playwright-evidence.js +244 -0
- package/dist/lib/long-task-product-shape.d.ts +6 -0
- package/dist/lib/long-task-product-shape.js +73 -0
- package/dist/lib/long-task-progress.d.ts +4 -0
- package/dist/lib/long-task-progress.js +114 -0
- package/dist/lib/long-task-protected-files.d.ts +1 -0
- package/dist/lib/long-task-protected-files.js +30 -0
- package/dist/lib/long-task-required-proof-surfaces.d.ts +2 -0
- package/dist/lib/long-task-required-proof-surfaces.js +13 -0
- package/dist/lib/long-task-requirement-shape.d.ts +2 -0
- package/dist/lib/long-task-requirement-shape.js +18 -0
- package/dist/lib/long-task-risk-surfaces.d.ts +2 -0
- package/dist/lib/long-task-risk-surfaces.js +60 -0
- package/dist/lib/long-task-risk-types.d.ts +5 -0
- package/dist/lib/long-task-risk-types.js +12 -0
- package/dist/lib/long-task-risk.d.ts +9 -0
- package/dist/lib/long-task-risk.js +132 -0
- package/dist/lib/long-task-root-shape.d.ts +4 -0
- package/dist/lib/long-task-root-shape.js +86 -0
- package/dist/lib/long-task-runner-environment.d.ts +7 -0
- package/dist/lib/long-task-runner-environment.js +57 -0
- package/dist/lib/long-task-runner-files.d.ts +3 -0
- package/dist/lib/long-task-runner-files.js +39 -0
- package/dist/lib/long-task-runner-freeze.d.ts +2 -0
- package/dist/lib/long-task-runner-freeze.js +168 -0
- package/dist/lib/long-task-runtime-types.d.ts +214 -0
- package/dist/lib/long-task-runtime-types.js +1 -0
- package/dist/lib/long-task-shape-primitives.d.ts +17 -0
- package/dist/lib/long-task-shape-primitives.js +81 -0
- package/dist/lib/long-task-source-authority-types.d.ts +13 -0
- package/dist/lib/long-task-source-authority-types.js +1 -0
- package/dist/lib/long-task-source-claim-validation.d.ts +5 -0
- package/dist/lib/long-task-source-claim-validation.js +96 -0
- package/dist/lib/long-task-source-continuity.d.ts +4 -0
- package/dist/lib/long-task-source-continuity.js +57 -0
- package/dist/lib/long-task-source-inventory.d.ts +2 -0
- package/dist/lib/long-task-source-inventory.js +21 -0
- package/dist/lib/long-task-source-item-parser.d.ts +3 -0
- package/dist/lib/long-task-source-item-parser.js +112 -0
- package/dist/lib/long-task-source-shape.d.ts +2 -0
- package/dist/lib/long-task-source-shape.js +56 -0
- package/dist/lib/long-task-source-target-continuity.d.ts +4 -0
- package/dist/lib/long-task-source-target-continuity.js +80 -0
- package/dist/lib/long-task-source-target-index.d.ts +12 -0
- package/dist/lib/long-task-source-target-index.js +76 -0
- package/dist/lib/long-task-source-validation.d.ts +3 -0
- package/dist/lib/long-task-source-validation.js +46 -0
- package/dist/lib/long-task-state.d.ts +91 -0
- package/dist/lib/long-task-state.js +611 -0
- package/dist/lib/long-task-status-projection.d.ts +21 -0
- package/dist/lib/long-task-status-projection.js +116 -0
- package/dist/lib/long-task-status-v2.d.ts +37 -0
- package/dist/lib/long-task-status-v2.js +306 -0
- package/dist/lib/long-task-technical-shape.d.ts +3 -0
- package/dist/lib/long-task-technical-shape.js +44 -0
- package/dist/lib/long-task-verifier-authority.d.ts +12 -0
- package/dist/lib/long-task-verifier-authority.js +47 -0
- package/dist/lib/long-task-verifier-identity.d.ts +2 -0
- package/dist/lib/long-task-verifier-identity.js +55 -0
- package/dist/lib/long-task-verifier-v2.d.ts +13 -0
- package/dist/lib/long-task-verifier-v2.js +266 -0
- package/dist/lib/long-task-workspace-runtime-types.d.ts +23 -0
- package/dist/lib/long-task-workspace-runtime-types.js +1 -0
- package/dist/lib/long-task-workspace.d.ts +25 -0
- package/dist/lib/long-task-workspace.js +366 -0
- package/dist/lib/managed-file.js +20 -5
- package/dist/lib/migrations.js +156 -32
- package/dist/lib/modularity.d.ts +27 -1
- package/dist/lib/modularity.js +499 -23
- package/dist/lib/package-json-config.js +14 -5
- package/dist/lib/package-source.js +15 -3
- package/dist/lib/profiles.d.ts +13 -0
- package/dist/lib/profiles.js +66 -0
- package/dist/lib/schema-guard.js +4 -2
- package/dist/lib/source-files.js +14 -7
- package/dist/lib/source-pack-classify.js +91 -19
- package/dist/lib/source-pack-config.js +25 -8
- package/dist/lib/source-pack-export.js +84 -25
- package/dist/lib/source-pack-manifest.js +18 -6
- package/dist/lib/source-pack-records.js +35 -11
- package/dist/lib/source-pack-render.js +47 -18
- package/dist/lib/stable-json.d.ts +2 -0
- package/dist/lib/stable-json.js +21 -0
- package/dist/lib/strict-codec.d.ts +4 -0
- package/dist/lib/strict-codec.js +65 -0
- package/dist/lib/sync-engine.js +76 -24
- package/dist/lib/types.d.ts +8 -1
- package/dist/lib/upgrade.js +4 -3
- package/dist/lib/validators.js +218 -191
- package/dist/long-task-hook.d.ts +2 -0
- package/dist/long-task-hook.js +55 -0
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +340 -0
- package/dist/schemas/long-task-delivery-v2/long-task-outcomes-v2.schema.json +18 -0
- package/migrations/README.md +8 -3
- package/package.json +17 -6
- package/source-mappings.yaml +0 -3
- package/assets/protected-harness-baseline.json +0 -20
- package/assets/skills/composite-long-task-workflow/SKILL.md +0 -244
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +0 -57
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +0 -17
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +0 -675
- package/dist/commands/superpowers.d.ts +0 -1
- package/dist/commands/superpowers.js +0 -8
- package/dist/lib/composite-long-task-renderer.d.ts +0 -12
- package/dist/lib/composite-long-task-renderer.js +0 -109
- package/dist/lib/plan-acceptance-artifacts.d.ts +0 -1
- package/dist/lib/plan-acceptance-artifacts.js +0 -220
- package/dist/lib/plan-acceptance-evidence.d.ts +0 -2
- package/dist/lib/plan-acceptance-evidence.js +0 -108
- package/dist/lib/plan-acceptance-json.d.ts +0 -15
- package/dist/lib/plan-acceptance-json.js +0 -133
- package/dist/lib/plan-acceptance-validator.d.ts +0 -2
- package/dist/lib/plan-acceptance-validator.js +0 -209
- package/dist/lib/plan-contract-validator.d.ts +0 -2
- package/dist/lib/plan-contract-validator.js +0 -127
- package/dist/lib/plan-validator-common.d.ts +0 -24
- package/dist/lib/plan-validator-common.js +0 -196
- package/dist/lib/superpowers-task-ac010.d.ts +0 -6
- package/dist/lib/superpowers-task-ac010.js +0 -26
- package/dist/lib/superpowers-task-assertion-normalizers.d.ts +0 -3
- package/dist/lib/superpowers-task-assertion-normalizers.js +0 -74
- package/dist/lib/superpowers-task-assertions.d.ts +0 -20
- package/dist/lib/superpowers-task-assertions.js +0 -257
- package/dist/lib/superpowers-task-attempt.d.ts +0 -4
- package/dist/lib/superpowers-task-attempt.js +0 -102
- package/dist/lib/superpowers-task-command-run-correlation.d.ts +0 -8
- package/dist/lib/superpowers-task-command-run-correlation.js +0 -103
- package/dist/lib/superpowers-task-command-specs.d.ts +0 -3
- package/dist/lib/superpowers-task-command-specs.js +0 -52
- package/dist/lib/superpowers-task-compile-diagnostics.d.ts +0 -5
- package/dist/lib/superpowers-task-compile-diagnostics.js +0 -20
- package/dist/lib/superpowers-task-compile-guards.d.ts +0 -2
- package/dist/lib/superpowers-task-compile-guards.js +0 -66
- package/dist/lib/superpowers-task-compile.d.ts +0 -6
- package/dist/lib/superpowers-task-compile.js +0 -133
- package/dist/lib/superpowers-task-completion-output.d.ts +0 -52
- package/dist/lib/superpowers-task-completion-output.js +0 -228
- package/dist/lib/superpowers-task-conformance.d.ts +0 -2
- package/dist/lib/superpowers-task-conformance.js +0 -24
- package/dist/lib/superpowers-task-contradictions.d.ts +0 -6
- package/dist/lib/superpowers-task-contradictions.js +0 -126
- package/dist/lib/superpowers-task-current-evidence.d.ts +0 -3
- package/dist/lib/superpowers-task-current-evidence.js +0 -176
- package/dist/lib/superpowers-task-delivery.d.ts +0 -4
- package/dist/lib/superpowers-task-delivery.js +0 -96
- package/dist/lib/superpowers-task-derive.d.ts +0 -14
- package/dist/lib/superpowers-task-derive.js +0 -382
- package/dist/lib/superpowers-task-events.d.ts +0 -1
- package/dist/lib/superpowers-task-events.js +0 -13
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +0 -19
- package/dist/lib/superpowers-task-evidence-kernel.js +0 -347
- package/dist/lib/superpowers-task-evidence-records.d.ts +0 -2
- package/dist/lib/superpowers-task-evidence-records.js +0 -55
- package/dist/lib/superpowers-task-evidence.d.ts +0 -10
- package/dist/lib/superpowers-task-evidence.js +0 -147
- package/dist/lib/superpowers-task-fields.d.ts +0 -22
- package/dist/lib/superpowers-task-fields.js +0 -276
- package/dist/lib/superpowers-task-final-card.d.ts +0 -3
- package/dist/lib/superpowers-task-final-card.js +0 -24
- package/dist/lib/superpowers-task-gates.d.ts +0 -12
- package/dist/lib/superpowers-task-gates.js +0 -155
- package/dist/lib/superpowers-task-harness-drift.d.ts +0 -11
- package/dist/lib/superpowers-task-harness-drift.js +0 -90
- package/dist/lib/superpowers-task-next-slices.d.ts +0 -1
- package/dist/lib/superpowers-task-next-slices.js +0 -12
- package/dist/lib/superpowers-task-protected-baseline.d.ts +0 -10
- package/dist/lib/superpowers-task-protected-baseline.js +0 -66
- package/dist/lib/superpowers-task-source-compile.d.ts +0 -5
- package/dist/lib/superpowers-task-source-compile.js +0 -226
- package/dist/lib/superpowers-task-source-parser.d.ts +0 -23
- package/dist/lib/superpowers-task-source-parser.js +0 -218
- package/dist/lib/superpowers-task-state-schema.d.ts +0 -440
- package/dist/lib/superpowers-task-state-schema.js +0 -66
- package/dist/lib/superpowers-task-state-shape.d.ts +0 -3
- package/dist/lib/superpowers-task-state-shape.js +0 -50
- package/dist/lib/superpowers-task-state.d.ts +0 -16
- package/dist/lib/superpowers-task-state.js +0 -246
- package/dist/lib/superpowers-task-status.d.ts +0 -2
- package/dist/lib/superpowers-task-status.js +0 -24
- package/dist/lib/superpowers-task-under-specified.d.ts +0 -7
- package/dist/lib/superpowers-task-under-specified.js +0 -61
- package/dist/lib/superpowers-task-unregistered-evidence.d.ts +0 -11
- package/dist/lib/superpowers-task-unregistered-evidence.js +0 -72
- package/dist/lib/superpowers-task-validator.d.ts +0 -5
- package/dist/lib/superpowers-task-validator.js +0 -290
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
|
|
2
|
+
"""Standalone Minimal Context validator installed with the portable profile."""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path, PureWindowsPath
|
|
5
|
+
import json
|
|
3
6
|
import re
|
|
7
|
+
import subprocess
|
|
4
8
|
import sys
|
|
5
9
|
|
|
6
|
-
|
|
10
|
+
try:
|
|
11
|
+
import tomllib
|
|
12
|
+
except ImportError: # Python 3.10 compatibility; Node is already required by ty-context.
|
|
13
|
+
tomllib = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
ROOT = Path.cwd().resolve()
|
|
17
|
+
CONTEXT_ROOT = ROOT / "project_context"
|
|
7
18
|
|
|
8
19
|
GLOBAL_CHECKS = [
|
|
9
20
|
("project goal", ["project goal", "项目目标", "目标"]),
|
|
@@ -38,13 +49,14 @@ VALID_ROLES = {
|
|
|
38
49
|
"implementation-index",
|
|
39
50
|
"decision-rationale",
|
|
40
51
|
}
|
|
41
|
-
|
|
42
52
|
ROLE_ALIASES = {
|
|
43
53
|
"implementation_index": "implementation-index",
|
|
44
54
|
"decision_rationale": "decision-rationale",
|
|
45
55
|
}
|
|
46
|
-
|
|
47
56
|
READ_POLICIES = {"default", "always", "optional", "on-demand", "never-default"}
|
|
57
|
+
TOP_LEVEL_FIELDS = {"areas", "context"}
|
|
58
|
+
AREA_FIELDS = {"id", "root", "context", "kind", "default", "forbidden_runtime_dependencies"}
|
|
59
|
+
CONTEXT_FIELDS = {"path", "role", "read_when", "read_policy", "triggers", "default_children"}
|
|
48
60
|
CONFIG_CANDIDATES = [
|
|
49
61
|
".agent/config.yaml",
|
|
50
62
|
".codex/config.yaml",
|
|
@@ -55,6 +67,16 @@ CONFIG_CANDIDATES = [
|
|
|
55
67
|
".roo/config.yaml",
|
|
56
68
|
".gemini/config.yaml",
|
|
57
69
|
]
|
|
70
|
+
FAKE_VERIFICATION_PATTERNS = [
|
|
71
|
+
re.compile(r"\btests?\s+(?:pass(?:ed|es)?|green)\b", re.I),
|
|
72
|
+
re.compile(r"\bverified\b", re.I),
|
|
73
|
+
re.compile(r"\bdeployed\s+successfully\b", re.I),
|
|
74
|
+
re.compile(r"\bvalidation\s+passed\b", re.I),
|
|
75
|
+
re.compile(r"测试(?:已)?通过"),
|
|
76
|
+
re.compile(r"验证(?:已)?通过"),
|
|
77
|
+
re.compile(r"部署(?:已)?成功"),
|
|
78
|
+
]
|
|
79
|
+
PLACEHOLDER = re.compile(r"^(?:todo|tbd|placeholder|coming soon|待补充|占位|暂无)[.!。…\s-]*$", re.I)
|
|
58
80
|
|
|
59
81
|
|
|
60
82
|
def has_any(text, terms):
|
|
@@ -67,6 +89,10 @@ def normalize_role(value):
|
|
|
67
89
|
return role if role in VALID_ROLES else None
|
|
68
90
|
|
|
69
91
|
|
|
92
|
+
def normalize_context_path(value):
|
|
93
|
+
return value.replace("\\", "/").removeprefix("./")
|
|
94
|
+
|
|
95
|
+
|
|
70
96
|
def parse_front_matter(text):
|
|
71
97
|
lines = text.splitlines()
|
|
72
98
|
if not lines or lines[0].strip() != "---":
|
|
@@ -81,130 +107,194 @@ def parse_front_matter(text):
|
|
|
81
107
|
return result
|
|
82
108
|
|
|
83
109
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return line[:index]
|
|
91
|
-
return line
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def parse_toml_value(raw):
|
|
95
|
-
value = raw.strip()
|
|
96
|
-
if value == "true":
|
|
97
|
-
return True
|
|
98
|
-
if value == "false":
|
|
99
|
-
return False
|
|
100
|
-
if (value.startswith("\"") and value.endswith("\"")) or (value.startswith("'") and value.endswith("'")):
|
|
101
|
-
return value[1:-1]
|
|
102
|
-
if value.startswith("[") and value.endswith("]"):
|
|
103
|
-
inner = value[1:-1].strip()
|
|
104
|
-
if not inner:
|
|
105
|
-
return []
|
|
106
|
-
items = []
|
|
107
|
-
for part in inner.split(","):
|
|
108
|
-
item = part.strip()
|
|
109
|
-
if not ((item.startswith("\"") and item.endswith("\"")) or (item.startswith("'") and item.endswith("'"))):
|
|
110
|
-
return None
|
|
111
|
-
items.append(item[1:-1])
|
|
112
|
-
return items
|
|
113
|
-
return None
|
|
110
|
+
def table_lines(content, table):
|
|
111
|
+
return [
|
|
112
|
+
index
|
|
113
|
+
for index, line in enumerate(content.splitlines(), start=1)
|
|
114
|
+
if line.strip() == f"[[{table}]]"
|
|
115
|
+
]
|
|
114
116
|
|
|
115
117
|
|
|
116
118
|
def parse_manifest(path, errors):
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
119
|
+
content = path.read_text(encoding="utf-8")
|
|
120
|
+
try:
|
|
121
|
+
parsed = parse_toml(content)
|
|
122
|
+
except (ValueError, RuntimeError) as error:
|
|
123
|
+
errors.append(f"project_context/context.toml is not valid TOML: {error}")
|
|
124
|
+
return {"areas": [], "context": []}
|
|
125
|
+
for key in parsed:
|
|
126
|
+
if key not in TOP_LEVEL_FIELDS:
|
|
127
|
+
errors.append(f"project_context/context.toml has unknown top-level field {key}")
|
|
128
|
+
manifest = {}
|
|
129
|
+
for table, allowed in (("areas", AREA_FIELDS), ("context", CONTEXT_FIELDS)):
|
|
130
|
+
entries = parsed.get(table, [])
|
|
131
|
+
if not isinstance(entries, list) or any(not isinstance(entry, dict) for entry in entries):
|
|
132
|
+
errors.append(f"project_context/context.toml field [[{table}]] must be an array of TOML tables")
|
|
133
|
+
entries = []
|
|
134
|
+
lines = table_lines(content, table)
|
|
135
|
+
manifest[table] = []
|
|
136
|
+
for index, raw in enumerate(entries):
|
|
137
|
+
entry = dict(raw)
|
|
138
|
+
entry["line"] = lines[index] if index < len(lines) else 1
|
|
139
|
+
for key in raw:
|
|
140
|
+
if key not in allowed:
|
|
141
|
+
errors.append(f"project_context/context.toml line {entry['line']} has unknown field {key}")
|
|
142
|
+
manifest[table].append(entry)
|
|
140
143
|
return manifest
|
|
141
144
|
|
|
142
145
|
|
|
143
|
-
def
|
|
144
|
-
|
|
146
|
+
def parse_toml(content):
|
|
147
|
+
if tomllib is not None:
|
|
148
|
+
try:
|
|
149
|
+
return tomllib.loads(content)
|
|
150
|
+
except tomllib.TOMLDecodeError as error:
|
|
151
|
+
raise ValueError(str(error)) from error
|
|
152
|
+
script = "import('smol-toml').then(({parse})=>{let s='';process.stdin.setEncoding('utf8');process.stdin.on('data',c=>s+=c);process.stdin.on('end',()=>process.stdout.write(JSON.stringify(parse(s))))}).catch(e=>{console.error(e.message);process.exit(1)})"
|
|
153
|
+
result = subprocess.run(
|
|
154
|
+
["node", "--input-type=module", "--eval", script],
|
|
155
|
+
input=content,
|
|
156
|
+
text=True,
|
|
157
|
+
capture_output=True,
|
|
158
|
+
cwd=ROOT,
|
|
159
|
+
check=False,
|
|
160
|
+
)
|
|
161
|
+
if result.returncode != 0:
|
|
162
|
+
raise RuntimeError(f"formal TOML parser unavailable: {result.stderr.strip()}")
|
|
163
|
+
return json.loads(result.stdout)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def required_string(entry, key, errors):
|
|
167
|
+
value = entry.get(key)
|
|
168
|
+
if not isinstance(value, str) or not value.strip():
|
|
169
|
+
errors.append(f"project_context/context.toml line {entry['line']} must include non-empty string field {key}")
|
|
170
|
+
return None
|
|
171
|
+
return value
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def optional_string(entry, key, errors):
|
|
175
|
+
value = entry.get(key)
|
|
176
|
+
if value is None:
|
|
177
|
+
return None
|
|
178
|
+
if not isinstance(value, str) or not value.strip():
|
|
179
|
+
errors.append(f"project_context/context.toml line {entry['line']} field {key} must be a non-empty string")
|
|
180
|
+
return None
|
|
181
|
+
return value
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def string_array(entry, key, errors):
|
|
185
|
+
value = entry.get(key, [])
|
|
186
|
+
if not isinstance(value, list) or any(not isinstance(item, str) or not item.strip() for item in value):
|
|
187
|
+
errors.append(f"project_context/context.toml line {entry['line']} {key} must be an array of non-empty strings")
|
|
188
|
+
return []
|
|
189
|
+
if len(value) != len(set(value)):
|
|
190
|
+
errors.append(f"project_context/context.toml line {entry['line']} {key} must not contain duplicates")
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def is_relative_to(target, root):
|
|
195
|
+
try:
|
|
196
|
+
target.relative_to(root)
|
|
197
|
+
return True
|
|
198
|
+
except ValueError:
|
|
199
|
+
return False
|
|
145
200
|
|
|
146
201
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
if
|
|
150
|
-
errors.append(f"project_context/context.toml
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
if not
|
|
154
|
-
errors.append(f"project_context/context.toml
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if
|
|
162
|
-
errors.append(f"project_context/context.toml
|
|
202
|
+
def safe_manifest_path(raw, allowed_root, label, expected, errors):
|
|
203
|
+
normalized = raw.replace("\\", "/")
|
|
204
|
+
if Path(raw).is_absolute() or PureWindowsPath(raw).is_absolute() or ".." in normalized.split("/"):
|
|
205
|
+
errors.append(f"project_context/context.toml {label} path must be relative and must not contain '..': {raw}")
|
|
206
|
+
return None
|
|
207
|
+
target = ROOT.joinpath(*normalized.split("/"))
|
|
208
|
+
if not target.exists():
|
|
209
|
+
errors.append(f"project_context/context.toml references missing {expected}: {normalized}")
|
|
210
|
+
return None
|
|
211
|
+
resolved_root = allowed_root.resolve()
|
|
212
|
+
resolved_target = target.resolve()
|
|
213
|
+
if not is_relative_to(resolved_target, resolved_root):
|
|
214
|
+
errors.append(f"project_context/context.toml {label} resolves outside its allowed root: {raw}")
|
|
215
|
+
return None
|
|
216
|
+
if expected == "context file" and not resolved_target.is_file():
|
|
217
|
+
errors.append(f"project_context/context.toml {label} must reference a regular file: {raw}")
|
|
218
|
+
return None
|
|
219
|
+
if expected == "area root" and not resolved_target.is_dir():
|
|
220
|
+
errors.append(f"project_context/context.toml {label} must reference a directory: {raw}")
|
|
221
|
+
return None
|
|
222
|
+
return resolved_target
|
|
163
223
|
|
|
164
224
|
|
|
165
225
|
def validate_manifest(errors):
|
|
166
|
-
manifest_path =
|
|
226
|
+
manifest_path = CONTEXT_ROOT / "context.toml"
|
|
167
227
|
roles = {}
|
|
228
|
+
policies = {}
|
|
168
229
|
if not manifest_path.exists():
|
|
169
230
|
if schema_requires_manifest():
|
|
170
231
|
errors.append("missing project_context/context.toml; run ty-context upgrade to create the Schema v4 Context graph manifest")
|
|
171
|
-
return roles
|
|
232
|
+
return roles, policies
|
|
172
233
|
manifest = parse_manifest(manifest_path, errors)
|
|
173
|
-
|
|
234
|
+
areas = manifest["areas"]
|
|
235
|
+
contexts = manifest["context"]
|
|
236
|
+
if not areas:
|
|
174
237
|
errors.append("project_context/context.toml must declare at least one [[areas]] entry")
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
defaults = [entry for entry in areas if entry.get("default") is True]
|
|
239
|
+
if areas and len(defaults) != 1:
|
|
240
|
+
errors.append(f"project_context/context.toml must mark exactly one [[areas]] entry with default = true; found {len(defaults)}")
|
|
241
|
+
area_ids = set()
|
|
242
|
+
registered_paths = set()
|
|
243
|
+
|
|
244
|
+
for area in areas:
|
|
245
|
+
area_id = required_string(area, "id", errors)
|
|
246
|
+
area_root = required_string(area, "root", errors)
|
|
247
|
+
context_path = required_string(area, "context", errors)
|
|
248
|
+
optional_string(area, "kind", errors)
|
|
249
|
+
if "default" in area and not isinstance(area["default"], bool):
|
|
182
250
|
errors.append(f"project_context/context.toml line {area['line']} default must be a boolean")
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
251
|
+
string_array(area, "forbidden_runtime_dependencies", errors)
|
|
252
|
+
if area_id:
|
|
253
|
+
if area_id in area_ids:
|
|
254
|
+
errors.append(f"project_context/context.toml has duplicate area id: {area_id}")
|
|
255
|
+
area_ids.add(area_id)
|
|
256
|
+
if area_root:
|
|
257
|
+
safe_manifest_path(area_root, ROOT, f"area {area_id or area['line']} root", "area root", errors)
|
|
258
|
+
if context_path:
|
|
259
|
+
register_context_path(context_path, "area", None, f"area {area_id or area['line']}", registered_paths, roles, policies, errors)
|
|
260
|
+
|
|
261
|
+
for entry in contexts:
|
|
262
|
+
context_path = required_string(entry, "path", errors)
|
|
263
|
+
raw_role = required_string(entry, "role", errors)
|
|
264
|
+
optional_string(entry, "read_when", errors)
|
|
265
|
+
read_policy = optional_string(entry, "read_policy", errors)
|
|
266
|
+
string_array(entry, "triggers", errors)
|
|
267
|
+
string_array(entry, "default_children", errors)
|
|
268
|
+
role = normalize_role(raw_role) if raw_role else None
|
|
269
|
+
if raw_role and role is None:
|
|
270
|
+
errors.append(f"project_context/context.toml line {entry['line']} has unsupported context role: {raw_role}")
|
|
271
|
+
if read_policy and read_policy not in READ_POLICIES:
|
|
272
|
+
errors.append(f"project_context/context.toml line {entry['line']} has unsupported read_policy: {read_policy}")
|
|
273
|
+
if context_path and role:
|
|
274
|
+
register_context_path(context_path, role, read_policy, f"context {context_path}", registered_paths, roles, policies, errors)
|
|
275
|
+
|
|
276
|
+
for entry in contexts:
|
|
277
|
+
for child in entry.get("default_children", []):
|
|
278
|
+
normalized = normalize_context_path(child)
|
|
279
|
+
if normalized not in registered_paths:
|
|
280
|
+
errors.append(f"project_context/context.toml line {entry['line']} default_children references unregistered Context path: {child}")
|
|
281
|
+
return roles, policies
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def register_context_path(raw, role, read_policy, label, registered, roles, policies, errors):
|
|
285
|
+
normalized = normalize_context_path(raw)
|
|
286
|
+
if normalized in registered:
|
|
287
|
+
errors.append(f"project_context/context.toml has duplicate Context path: {normalized}")
|
|
288
|
+
return
|
|
289
|
+
registered.add(normalized)
|
|
290
|
+
if not normalized.startswith("project_context/") or not normalized.endswith(".md"):
|
|
291
|
+
errors.append(f"project_context/context.toml {label} must reference a markdown file under project_context/: {raw}")
|
|
292
|
+
return
|
|
293
|
+
if safe_manifest_path(raw, CONTEXT_ROOT, label, "context file", errors) is None:
|
|
294
|
+
return
|
|
295
|
+
roles[normalized] = role
|
|
296
|
+
if read_policy:
|
|
297
|
+
policies[normalized] = read_policy
|
|
208
298
|
|
|
209
299
|
|
|
210
300
|
def schema_requires_manifest():
|
|
@@ -229,24 +319,90 @@ def validate_checks(rel, text, checks, errors):
|
|
|
229
319
|
errors.append(f"{rel} must include {label}")
|
|
230
320
|
|
|
231
321
|
|
|
322
|
+
def section_bodies(text):
|
|
323
|
+
headings = list(re.finditer(r"^(#{2,6})\s+(.+?)\s*$", text, re.M))
|
|
324
|
+
for index, match in enumerate(headings):
|
|
325
|
+
end = headings[index + 1].start() if index + 1 < len(headings) else len(text)
|
|
326
|
+
yield match.group(2), text[match.end():end].strip()
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def validate_no_fake_verification(rel, text, errors):
|
|
330
|
+
for heading, body in section_bodies(text):
|
|
331
|
+
if not re.search(r"verification|tests?|deployment|验证|测试|部署", heading, re.I):
|
|
332
|
+
continue
|
|
333
|
+
if any(pattern.search(body) for pattern in FAKE_VERIFICATION_PATTERNS):
|
|
334
|
+
errors.append(f"{rel} must list verification entry points, not claim tests were already executed")
|
|
335
|
+
return
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def validate_minimum_recoverability(rel, text, errors):
|
|
339
|
+
if not re.search(r"^#\s+\S.+$", text, re.M):
|
|
340
|
+
errors.append(f"{rel} must contain a non-empty level-one title")
|
|
341
|
+
body = re.sub(r"^---\s*\n[\s\S]*?\n---\s*\n?", "", text, count=1)
|
|
342
|
+
facts = []
|
|
343
|
+
for line in body.splitlines():
|
|
344
|
+
value = line.strip()
|
|
345
|
+
if not value or value.startswith(("#", "```", "<!--")):
|
|
346
|
+
continue
|
|
347
|
+
value = re.sub(r"^[-*+>]\s*", "", value).strip()
|
|
348
|
+
if len(value) >= 8 and not PLACEHOLDER.fullmatch(value):
|
|
349
|
+
facts.append(value)
|
|
350
|
+
if not facts:
|
|
351
|
+
errors.append(f"{rel} must contain at least one concrete fact paragraph and cannot be only TODO or placeholder text")
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def validate_role_recoverability(rel, text, role, errors):
|
|
355
|
+
if role == "verification" and not re.search(r"`[^`\r\n]+`|(?:npm|pnpm|yarn|make|node|python|pytest|cargo|go)\s+\S+|(?:^|\s)(?:\./|[\w.-]+/)[\w./-]+", text, re.I | re.M):
|
|
356
|
+
errors.append(f"{rel} verification Context must contain a command, repository path or repeatable entry point")
|
|
357
|
+
if role == "implementation-index":
|
|
358
|
+
candidates = [match.group(1) for match in re.finditer(r"`([^`]+)`", text) if ("/" in match.group(1) or "\\" in match.group(1)) and " " not in match.group(1)]
|
|
359
|
+
if not any(repository_path_exists(candidate) for candidate in candidates):
|
|
360
|
+
errors.append(f"{rel} implementation-index Context must contain at least one existing repository path")
|
|
361
|
+
if role == "contract" and not re.search(r"producer|consumer|input|output|schema|compatibility|constraint|must|shall|禁止|必须|输入|输出|兼容", text, re.I):
|
|
362
|
+
errors.append(f"{rel} contract Context must state at least one constraint or input/output semantic")
|
|
363
|
+
if role == "decision-rationale":
|
|
364
|
+
has_decision = re.search(r"^##\s+(?:(?:Decision|Current Design Choice|Design Choice)(?:\b|\s|$)|决定(?:\s|$)|当前设计选择(?:\s|$))", text, re.I | re.M)
|
|
365
|
+
has_reason = re.search(r"^##\s+(?:(?:Reason|Rationale|Why)(?:\b|\s|$)|原因(?:\s|$)|理由(?:\s|$)|为什么(?:\s|$))", text, re.I | re.M)
|
|
366
|
+
if not has_decision or not has_reason:
|
|
367
|
+
errors.append(f"{rel} decision-rationale Context must contain both a Decision and a Reason")
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def repository_path_exists(candidate):
|
|
371
|
+
clean = candidate.rstrip(".:,;").replace("\\", "/")
|
|
372
|
+
if Path(clean).is_absolute() or PureWindowsPath(clean).is_absolute() or ".." in clean.split("/"):
|
|
373
|
+
return False
|
|
374
|
+
target = ROOT.joinpath(*clean.split("/"))
|
|
375
|
+
return target.exists() and is_relative_to(target.resolve(), ROOT)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def validate_context_file(rel, text, role, errors):
|
|
379
|
+
validate_no_fake_verification(rel, text, errors)
|
|
380
|
+
validate_minimum_recoverability(rel, text, errors)
|
|
381
|
+
if role:
|
|
382
|
+
validate_role_recoverability(rel, text, role, errors)
|
|
383
|
+
|
|
384
|
+
|
|
232
385
|
def main():
|
|
233
386
|
errors = []
|
|
234
|
-
|
|
387
|
+
warnings = []
|
|
388
|
+
global_path = CONTEXT_ROOT / "global.md"
|
|
389
|
+
architecture_path = CONTEXT_ROOT / "architecture.md"
|
|
235
390
|
if not global_path.exists():
|
|
236
391
|
errors.append("missing project_context/global.md")
|
|
237
392
|
else:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
393
|
+
text = global_path.read_text(encoding="utf-8")
|
|
394
|
+
validate_checks("project_context/global.md", text, GLOBAL_CHECKS, errors)
|
|
395
|
+
validate_context_file("project_context/global.md", text, "global", errors)
|
|
241
396
|
if not architecture_path.exists():
|
|
242
397
|
errors.append("missing project_context/architecture.md")
|
|
243
398
|
else:
|
|
244
|
-
|
|
399
|
+
text = architecture_path.read_text(encoding="utf-8")
|
|
400
|
+
validate_checks("project_context/architecture.md", text, ARCHITECTURE_CHECKS, errors)
|
|
401
|
+
validate_context_file("project_context/architecture.md", text, "architecture", errors)
|
|
245
402
|
|
|
246
|
-
manifest_roles = validate_manifest(errors)
|
|
247
|
-
context_root = ROOT / "project_context"
|
|
248
|
-
context_files = sorted(context_root.rglob("*.md")) if context_root.exists() else []
|
|
403
|
+
manifest_roles, manifest_policies = validate_manifest(errors)
|
|
249
404
|
checked = 0
|
|
405
|
+
context_files = sorted(CONTEXT_ROOT.rglob("*.md")) if CONTEXT_ROOT.exists() else []
|
|
250
406
|
for path in context_files:
|
|
251
407
|
rel = path.relative_to(ROOT).as_posix()
|
|
252
408
|
if rel in {"project_context/global.md", "project_context/architecture.md"}:
|
|
@@ -254,16 +410,26 @@ def main():
|
|
|
254
410
|
text = path.read_text(encoding="utf-8")
|
|
255
411
|
front_matter = parse_front_matter(text)
|
|
256
412
|
declared_role = front_matter.get("context_role")
|
|
257
|
-
|
|
258
|
-
if declared_role and
|
|
413
|
+
front_role = normalize_role(declared_role) if declared_role else None
|
|
414
|
+
if declared_role and front_role is None:
|
|
259
415
|
errors.append(f"{rel} has unsupported context_role: {declared_role}")
|
|
260
|
-
|
|
261
|
-
if
|
|
262
|
-
errors.append(f"{rel} has unsupported read_policy: {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
416
|
+
front_policy = front_matter.get("read_policy")
|
|
417
|
+
if front_policy and front_policy not in READ_POLICIES:
|
|
418
|
+
errors.append(f"{rel} has unsupported read_policy: {front_policy}")
|
|
419
|
+
manifest_role = manifest_roles.get(rel)
|
|
420
|
+
manifest_policy = manifest_policies.get(rel)
|
|
421
|
+
if manifest_role and front_role and manifest_role != front_role:
|
|
422
|
+
errors.append(f"{rel} front matter context_role {front_role} does not match manifest role {manifest_role}")
|
|
423
|
+
if manifest_policy and front_policy and manifest_policy != front_policy:
|
|
424
|
+
errors.append(f"{rel} front matter read_policy {front_policy} does not match manifest read_policy {manifest_policy}")
|
|
425
|
+
if not manifest_role:
|
|
426
|
+
warnings.append(f"{rel} is an unregistered Context Markdown file; add it to project_context/context.toml or move it out of project_context/**")
|
|
427
|
+
role = manifest_role or front_role
|
|
428
|
+
validate_context_file(rel, text, role, errors)
|
|
429
|
+
checked += 1
|
|
430
|
+
|
|
431
|
+
for warning in warnings:
|
|
432
|
+
print(f"warning: {warning}", file=sys.stderr)
|
|
267
433
|
if errors:
|
|
268
434
|
for error in errors:
|
|
269
435
|
print(f"error: {error}", file=sys.stderr)
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { commands } from "./commands/index.js";
|
|
3
3
|
const [command = "help", ...args] = process.argv.slice(2);
|
|
4
4
|
async function main() {
|
|
5
|
-
const handler = commands[command]
|
|
5
|
+
const handler = commands[command];
|
|
6
|
+
if (!handler)
|
|
7
|
+
throw new Error(`Unknown command: ${command}`);
|
|
6
8
|
await handler(args);
|
|
7
9
|
}
|
|
8
10
|
main().catch((error) => {
|
|
@@ -9,7 +9,11 @@ export async function checkModularity(args) {
|
|
|
9
9
|
process.exitCode = 1;
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
|
-
if (parsed.help ||
|
|
12
|
+
if (parsed.help ||
|
|
13
|
+
(!parsed.configOnly &&
|
|
14
|
+
!parsed.touched &&
|
|
15
|
+
!parsed.base &&
|
|
16
|
+
parsed.files.length === 0)) {
|
|
13
17
|
console.log(helpText());
|
|
14
18
|
if (!parsed.help) {
|
|
15
19
|
process.exitCode = 1;
|
|
@@ -21,15 +25,21 @@ export async function checkModularity(args) {
|
|
|
21
25
|
touched: parsed.touched,
|
|
22
26
|
base: parsed.base,
|
|
23
27
|
files: parsed.files,
|
|
24
|
-
limit: parsed.limit
|
|
28
|
+
limit: parsed.limit,
|
|
25
29
|
});
|
|
26
30
|
console.log(`check-modularity audited=${report.files.length} warning=${report.warnings.length} limit=${report.limit} waived=${report.waivedWarnings.length}`);
|
|
27
31
|
if (report.files.length === 0) {
|
|
28
32
|
console.log("No handwritten source files matched the selected scope.");
|
|
29
33
|
}
|
|
30
34
|
for (const file of report.files) {
|
|
31
|
-
const prefix = file.
|
|
32
|
-
|
|
35
|
+
const prefix = file.regressed && file.waived
|
|
36
|
+
? "waived"
|
|
37
|
+
: file.regressed
|
|
38
|
+
? "over-limit"
|
|
39
|
+
: file.overLimit
|
|
40
|
+
? "observed-risk"
|
|
41
|
+
: "ok";
|
|
42
|
+
console.log(`${prefix}: ${file.relativePath} ${file.lines} lines statements=${file.metrics.maxFunctionStatements} branches=${file.metrics.maxBranchComplexity} exports=${file.metrics.exports} transitions=${file.metrics.stateTransitions} responsibilities=${file.metrics.responsibilities.join(",") || "none"} statement_at=${formatLocation(file.metrics.maxFunctionStatementsLocation)} branch_at=${formatLocation(file.metrics.maxBranchComplexityLocation)}`);
|
|
33
43
|
}
|
|
34
44
|
for (const error of report.errors) {
|
|
35
45
|
console.error(`error: ${error}`);
|
|
@@ -41,9 +51,10 @@ export async function checkModularity(args) {
|
|
|
41
51
|
console.warn(`waived: ${waiver}`);
|
|
42
52
|
}
|
|
43
53
|
if (report.warnings.length > 0) {
|
|
44
|
-
console.warn("warning:
|
|
54
|
+
console.warn("warning: modularity risks need a split or, when modularity.policy is scoped_waivers, a valid <harnessRoot>/config.yaml waiver with path, category, owner, introduced_at, reason, tracking_issue and expiry_condition.");
|
|
45
55
|
}
|
|
46
|
-
if (report.errors.length > 0 ||
|
|
56
|
+
if (report.errors.length > 0 ||
|
|
57
|
+
(report.warnings.length > 0 && parsed.failOnWarning)) {
|
|
47
58
|
process.exitCode = 1;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
@@ -52,13 +63,17 @@ export async function checkModularity(args) {
|
|
|
52
63
|
process.exitCode = 1;
|
|
53
64
|
}
|
|
54
65
|
}
|
|
66
|
+
function formatLocation(location) {
|
|
67
|
+
return location ? `${location.symbol}:${location.line}` : "none";
|
|
68
|
+
}
|
|
55
69
|
function parseArgs(args) {
|
|
56
70
|
const parsed = {
|
|
57
71
|
touched: false,
|
|
58
72
|
files: [],
|
|
59
73
|
limit: 300,
|
|
60
74
|
failOnWarning: false,
|
|
61
|
-
|
|
75
|
+
configOnly: false,
|
|
76
|
+
help: false,
|
|
62
77
|
};
|
|
63
78
|
for (let index = 0; index < args.length; index += 1) {
|
|
64
79
|
const arg = args[index];
|
|
@@ -70,6 +85,10 @@ function parseArgs(args) {
|
|
|
70
85
|
parsed.failOnWarning = true;
|
|
71
86
|
continue;
|
|
72
87
|
}
|
|
88
|
+
if (arg === "--config-only") {
|
|
89
|
+
parsed.configOnly = true;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
73
92
|
if (arg === "--help" || arg === "-h") {
|
|
74
93
|
parsed.help = true;
|
|
75
94
|
continue;
|
|
@@ -127,7 +146,9 @@ function parseArgs(args) {
|
|
|
127
146
|
}
|
|
128
147
|
function parseLimit(value) {
|
|
129
148
|
const limit = Number.parseInt(value, 10);
|
|
130
|
-
if (!Number.isInteger(limit) ||
|
|
149
|
+
if (!Number.isInteger(limit) ||
|
|
150
|
+
limit <= 0 ||
|
|
151
|
+
String(limit) !== value.trim()) {
|
|
131
152
|
throw new Error("check-modularity --limit requires a positive integer");
|
|
132
153
|
}
|
|
133
154
|
return limit;
|
|
@@ -137,9 +158,11 @@ function helpText() {
|
|
|
137
158
|
check-modularity --touched [--limit 300] [--fail-on-warning]
|
|
138
159
|
check-modularity --file <path> [--file <path> ...] [--limit 300] [--fail-on-warning]
|
|
139
160
|
check-modularity --base <ref> [--limit 300] [--fail-on-warning]
|
|
161
|
+
check-modularity --config-only
|
|
140
162
|
|
|
141
|
-
Audits
|
|
163
|
+
Audits physical lines, per-function statements and branch complexity, exports, state transitions and module responsibilities.
|
|
164
|
+
For --touched and --base, existing findings are reported but only new or worsened non-line complexity is a warning; physical lines remain a risk signal and new files are audited in full.
|
|
142
165
|
The default is warning-only; --fail-on-warning lets projects opt into CI enforcement.
|
|
143
166
|
Generated configs default to modularity.policy: strict_except_generated; omitted policy is treated as scoped_waivers for compatibility.
|
|
144
|
-
|
|
167
|
+
Risks can be waived only through lifecycle-complete <harnessRoot>/config.yaml modularity.waivers when policy is scoped_waivers.`;
|
|
145
168
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function compositeCampaign(_args: string[]): void;
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
export declare function compositeLongTask(
|
|
2
|
-
export declare function runCompositeLongTaskCommand(args: string[], options: {
|
|
3
|
-
commandName: string;
|
|
4
|
-
label: string;
|
|
5
|
-
showHelp: boolean;
|
|
6
|
-
}): Promise<void>;
|
|
1
|
+
export declare function compositeLongTask(_args: string[]): void;
|