@xulthekl/team-flow 0.22.4
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/.agents/plugins/marketplace.json +20 -0
- package/.agents/skills +1 -0
- package/.claude/always/phase-guard.md +3 -0
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.codexignore +6 -0
- package/.cursor-plugin/marketplace.json +17 -0
- package/.cursor-plugin/plugin.json +31 -0
- package/.github/copilot-instructions.md +15 -0
- package/.github/dependabot.yml +10 -0
- package/.github/plugin/marketplace.json +42 -0
- package/.github/workflows/ci.yml +98 -0
- package/.github/workflows/hol-plugin-scanner.yml +17 -0
- package/.opencode/INSTALL.md +61 -0
- package/.opencode/plugins/team-flow.js +70 -0
- package/AGENTS.md +280 -0
- package/CHANGELOG.md +635 -0
- package/CONTRIBUTING.md +115 -0
- package/GEMINI.md +14 -0
- package/HANDOFF.md +219 -0
- package/INSTALL.md +810 -0
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/SECURITY.md +37 -0
- package/agents/bug-investigator.md +180 -0
- package/agents/change-split-auditor.md +257 -0
- package/agents/code-reviewer.md +171 -0
- package/agents/cross-change-consistency-checker.md +215 -0
- package/agents/prd-completeness-reviewer.md +197 -0
- package/agents/prototype-builder.md +230 -0
- package/agents/prototype-env-scout.md +156 -0
- package/agents/prototype-reviewer.md +226 -0
- package/assets/icon.svg +6 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +5 -0
- package/dist/parsing/change-parser.d.ts +12 -0
- package/dist/parsing/change-parser.js +54 -0
- package/dist/parsing/requirement-blocks.d.ts +31 -0
- package/dist/parsing/requirement-blocks.js +192 -0
- package/dist/schema/base.d.ts +7 -0
- package/dist/schema/base.js +1 -0
- package/dist/schema/change.d.ts +25 -0
- package/dist/schema/change.js +1 -0
- package/dist/schema/index.d.ts +3 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/spec.d.ts +11 -0
- package/dist/schema/spec.js +1 -0
- package/dist/validation/constants.d.ts +42 -0
- package/dist/validation/constants.js +42 -0
- package/dist/validation/tokenizer.d.ts +17 -0
- package/dist/validation/tokenizer.js +173 -0
- package/dist/validation/types.d.ts +40 -0
- package/dist/validation/types.js +1 -0
- package/dist/validation/validator.d.ts +21 -0
- package/dist/validation/validator.js +489 -0
- package/docs/README_en.md +358 -0
- package/docs/artifact-contract.md +96 -0
- package/docs/decision-points.md +88 -0
- package/docs/e2e-integration-design.md +207 -0
- package/docs/examples/README.md +25 -0
- package/docs/examples/add-dark-mode/README.md +13 -0
- package/docs/examples/add-dark-mode/design.md +49 -0
- package/docs/examples/add-dark-mode/execution-contract.md +56 -0
- package/docs/examples/add-dark-mode/proposal.md +46 -0
- package/docs/examples/add-dark-mode/specs/ui-theme/spec.md +35 -0
- package/docs/examples/add-dark-mode/tasks.md +26 -0
- package/docs/examples/refactor-auth-boundary/README.md +15 -0
- package/docs/examples/refactor-auth-boundary/design.md +41 -0
- package/docs/examples/refactor-auth-boundary/execution-contract.md +61 -0
- package/docs/examples/refactor-auth-boundary/proposal.md +47 -0
- package/docs/examples/refactor-auth-boundary/specs/auth-boundary/spec.md +45 -0
- package/docs/examples/refactor-auth-boundary/tasks.md +25 -0
- package/docs/plans/2026-07-07-fix-batch-design.md +68 -0
- package/docs/plans/2026-07-20-001-refactor-skill-collaboration-protocol-plan.md +128 -0
- package/docs/platform-matrix.md +45 -0
- package/docs/prototype-design-research.md +206 -0
- package/docs/release-checklist.md +96 -0
- package/docs/showcase.html +699 -0
- package/docs/skill-rename-v0.8.0.md +49 -0
- package/docs/state-machine.md +162 -0
- package/gemini-extension.json +6 -0
- package/hooks/hooks-cursor.json +8 -0
- package/hooks/hooks.json +26 -0
- package/hooks/pre-tool-use-guard +116 -0
- package/hooks/session-start +14 -0
- package/llms.txt +51 -0
- package/package.json +48 -0
- package/plugin.json +27 -0
- package/prd/v1/plan.md +443 -0
- package/prd/v1/prd.md +580 -0
- package/scripts/check-update.mjs +91 -0
- package/scripts/check-version-consistency.mjs +345 -0
- package/scripts/ensure-branch.mjs +71 -0
- package/scripts/get-config +31 -0
- package/scripts/guard/checks/artifacts-exist.mjs +36 -0
- package/scripts/guard/checks/contract-current.mjs +33 -0
- package/scripts/guard/checks/contract-fresh.mjs +17 -0
- package/scripts/guard/checks/dp-gate-passed.mjs +35 -0
- package/scripts/guard/checks/dp3-approved.mjs +22 -0
- package/scripts/guard/checks/execution-plan-ready.mjs +40 -0
- package/scripts/guard/checks/execution-reviews-passed.mjs +36 -0
- package/scripts/guard/checks/schema-valid.mjs +63 -0
- package/scripts/guard/checks/specs-merged.mjs +51 -0
- package/scripts/guard/checks/tasks-complete.mjs +31 -0
- package/scripts/guard/checks/tests-passing.mjs +31 -0
- package/scripts/guard/design-token-guard.mjs +237 -0
- package/scripts/guard/guard.mjs +205 -0
- package/scripts/infer-workflow.mjs +136 -0
- package/scripts/install-amazon-q.mjs +9 -0
- package/scripts/install-cline.mjs +9 -0
- package/scripts/install-continue.mjs +9 -0
- package/scripts/install-cursor.mjs +265 -0
- package/scripts/install-git-hooks.mjs +82 -0
- package/scripts/install-kiro.mjs +9 -0
- package/scripts/install-pi.mjs +9 -0
- package/scripts/install-qoder.mjs +9 -0
- package/scripts/install-qwen.mjs +9 -0
- package/scripts/install-roocode.mjs +9 -0
- package/scripts/install-windsurf.mjs +9 -0
- package/scripts/install-zcode.mjs +265 -0
- package/scripts/lib/cmd-audit.mjs +127 -0
- package/scripts/lib/cmd-checkpoint.mjs +118 -0
- package/scripts/lib/cmd-config.mjs +88 -0
- package/scripts/lib/cmd-doctor.mjs +267 -0
- package/scripts/lib/cmd-execution.mjs +206 -0
- package/scripts/lib/cmd-handoff.mjs +94 -0
- package/scripts/lib/cmd-inject.mjs +310 -0
- package/scripts/lib/cmd-install-amazon-q.mjs +11 -0
- package/scripts/lib/cmd-install-cline.mjs +11 -0
- package/scripts/lib/cmd-install-continue.mjs +11 -0
- package/scripts/lib/cmd-install-cursor.mjs +14 -0
- package/scripts/lib/cmd-install-kiro.mjs +11 -0
- package/scripts/lib/cmd-install-pi.mjs +11 -0
- package/scripts/lib/cmd-install-qoder.mjs +11 -0
- package/scripts/lib/cmd-install-qwen.mjs +11 -0
- package/scripts/lib/cmd-install-roocode.mjs +11 -0
- package/scripts/lib/cmd-install-windsurf.mjs +11 -0
- package/scripts/lib/cmd-install-workbuddy.mjs +359 -0
- package/scripts/lib/cmd-install-zcode.mjs +14 -0
- package/scripts/lib/cmd-isolate.mjs +37 -0
- package/scripts/lib/cmd-list.mjs +71 -0
- package/scripts/lib/cmd-runtime.mjs +85 -0
- package/scripts/lib/cmd-solutions.mjs +71 -0
- package/scripts/lib/cmd-state.mjs +231 -0
- package/scripts/lib/cmd-sync.mjs +102 -0
- package/scripts/lib/cmd-validate.mjs +105 -0
- package/scripts/lib/cmd-version.mjs +148 -0
- package/scripts/lib/config-loader.mjs +107 -0
- package/scripts/lib/execution-plan.mjs +492 -0
- package/scripts/lib/execution-recommendation.mjs +221 -0
- package/scripts/lib/hash.mjs +74 -0
- package/scripts/lib/install.mjs +267 -0
- package/scripts/lib/platform-runtime-inventory.mjs +27 -0
- package/scripts/lib/platforms.mjs +165 -0
- package/scripts/lib/prototype-sync.mjs +192 -0
- package/scripts/lib/sdd-overlay.mjs +262 -0
- package/scripts/lib/shell-quote.mjs +4 -0
- package/scripts/lib/solutions-capture.mjs +102 -0
- package/scripts/lib/solutions-index-gen.mjs +112 -0
- package/scripts/lib/solutions-inject.mjs +77 -0
- package/scripts/lib/solutions-promote.mjs +145 -0
- package/scripts/lib/spec-paths.mjs +85 -0
- package/scripts/lib/state-loader.mjs +154 -0
- package/scripts/lint/lint-skills.mjs +149 -0
- package/scripts/lint/rules/behavior-consistency.mjs +56 -0
- package/scripts/lint/rules/dp-trigger-points.mjs +60 -0
- package/scripts/lint/rules/exception-handling.mjs +59 -0
- package/scripts/lint/rules/no-contradictory-instructions.mjs +67 -0
- package/scripts/lint/rules/no-redundant-checks.mjs +50 -0
- package/scripts/lint/rules/token-rules.mjs +122 -0
- package/scripts/review-package +44 -0
- package/scripts/task-brief +40 -0
- package/scripts/team-flow.mjs +164 -0
- package/scripts/token-baseline.mjs +158 -0
- package/scripts/validate-artifacts +96 -0
- package/scripts/verify-marketplace-release.mjs +59 -0
- package/skills/architecture-design/SKILL.md +140 -0
- package/skills/architecture-design/chapters/ch01-4a-domains.md +27 -0
- package/skills/architecture-design/chapters/ch02-change-cascade.md +27 -0
- package/skills/architecture-design/chapters/ch03-architecture-outputs.md +25 -0
- package/skills/architecture-design/chapters/ch04-entity-to-aggregate.md +27 -0
- package/skills/architecture-design/chapters/ch05-cqrs.md +32 -0
- package/skills/architecture-design/chapters/ch06-integration.md +54 -0
- package/skills/architecture-design/cheatsheet.md +51 -0
- package/skills/architecture-design/glossary.md +23 -0
- package/skills/architecture-design/patterns.md +32 -0
- package/skills/bug-investigator/SKILL.md +77 -0
- package/skills/build-executor/SKILL.md +163 -0
- package/skills/build-executor/implementer-prompt.md +162 -0
- package/skills/build-executor/references/execution-modes.md +86 -0
- package/skills/build-executor/task-reviewer-prompt.md +195 -0
- package/skills/ce-brainstorm/SKILL.md +415 -0
- package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
- package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
- package/skills/ce-brainstorm/references/brainstorm-sections.md +285 -0
- package/skills/ce-brainstorm/references/handoff.md +168 -0
- package/skills/ce-brainstorm/references/html-rendering.md +634 -0
- package/skills/ce-brainstorm/references/markdown-rendering.md +241 -0
- package/skills/ce-brainstorm/references/model-tiers.md +9 -0
- package/skills/ce-brainstorm/references/prd-mapping.md +71 -0
- package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
- package/skills/ce-brainstorm/references/reasoning-elevation.md +63 -0
- package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
- package/skills/ce-brainstorm/references/synthesis-summary.md +282 -0
- package/skills/ce-brainstorm/references/universal-brainstorming.md +73 -0
- package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
- package/skills/ce-brainstorm/references/visual-probes.md +158 -0
- package/skills/ce-brainstorm/scripts/visual-probe-server.js +418 -0
- package/skills/ce-compound/SKILL.md +114 -0
- package/skills/ce-compound/assets/resolution-template.md +94 -0
- package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
- package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
- package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
- package/skills/ce-compound/references/agents/session-historian.md +83 -0
- package/skills/ce-compound/references/concepts-vocabulary.md +78 -0
- package/skills/ce-compound/references/full-mode-workflow.md +413 -0
- package/skills/ce-compound/references/grounding-validation.md +84 -0
- package/skills/ce-compound/references/lightweight-mode.md +53 -0
- package/skills/ce-compound/references/output-standards.md +182 -0
- package/skills/ce-compound/references/promotion-rules.md +38 -0
- package/skills/ce-compound/references/schema.yaml +231 -0
- package/skills/ce-compound/references/three-tier-index.md +55 -0
- package/skills/ce-compound/references/v0.5-upgrade.md +32 -0
- package/skills/ce-compound/references/write-flow.md +36 -0
- package/skills/ce-compound/references/yaml-schema.md +118 -0
- package/skills/ce-compound/scripts/session-history/discover-sessions.sh +130 -0
- package/skills/ce-compound/scripts/session-history/extract-errors.py +254 -0
- package/skills/ce-compound/scripts/session-history/extract-metadata.py +456 -0
- package/skills/ce-compound/scripts/session-history/extract-skeleton.py +575 -0
- package/skills/ce-compound/scripts/validate-doc-claims.py +346 -0
- package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-ideate/SKILL.md +402 -0
- package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +200 -0
- package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
- package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
- package/skills/ce-ideate/references/divergent-ideation.md +89 -0
- package/skills/ce-ideate/references/html-rendering.md +634 -0
- package/skills/ce-ideate/references/ideation-sections.md +191 -0
- package/skills/ce-ideate/references/markdown-rendering.md +238 -0
- package/skills/ce-ideate/references/post-ideation-workflow.md +167 -0
- package/skills/ce-ideate/references/universal-ideation.md +107 -0
- package/skills/ce-ideate/references/web-research-cache.md +56 -0
- package/skills/ce-plan/SKILL.md +137 -0
- package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +62 -0
- package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
- package/skills/ce-plan/references/agents/best-practices-researcher.md +114 -0
- package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-plan/references/agents/data-migration-reviewer.md +103 -0
- package/skills/ce-plan/references/agents/deployment-verification-agent.md +157 -0
- package/skills/ce-plan/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
- package/skills/ce-plan/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
- package/skills/ce-plan/references/agents/repo-research-analyst.md +258 -0
- package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
- package/skills/ce-plan/references/agents/slack-researcher.md +127 -0
- package/skills/ce-plan/references/agents/spec-flow-analyzer.md +80 -0
- package/skills/ce-plan/references/agents/web-researcher.md +121 -0
- package/skills/ce-plan/references/approach-altitude.md +55 -0
- package/skills/ce-plan/references/change-splitting.md +65 -0
- package/skills/ce-plan/references/completion-contract.md +33 -0
- package/skills/ce-plan/references/core-principles.md +40 -0
- package/skills/ce-plan/references/deepening-workflow.md +263 -0
- package/skills/ce-plan/references/dependency-graph.md +36 -0
- package/skills/ce-plan/references/html-rendering.md +634 -0
- package/skills/ce-plan/references/intake-routing.md +189 -0
- package/skills/ce-plan/references/markdown-rendering.md +238 -0
- package/skills/ce-plan/references/plan-handoff.md +150 -0
- package/skills/ce-plan/references/plan-sections.md +427 -0
- package/skills/ce-plan/references/plan-structure.md +120 -0
- package/skills/ce-plan/references/planning-modes.md +69 -0
- package/skills/ce-plan/references/reasoning-elevation.md +63 -0
- package/skills/ce-plan/references/research-workflow.md +165 -0
- package/skills/ce-plan/references/review-and-write.md +127 -0
- package/skills/ce-plan/references/settled-decisions.md +45 -0
- package/skills/ce-plan/references/synthesis-summary.md +418 -0
- package/skills/ce-plan/references/universal-planning.md +168 -0
- package/skills/ce-proof/SKILL.md +346 -0
- package/skills/ce-strategy/SKILL.md +97 -0
- package/skills/ce-strategy/references/interview.md +143 -0
- package/skills/ce-strategy/references/strategy-template.md +89 -0
- package/skills/code-reviewer/SKILL.md +84 -0
- package/skills/code-reviewer/code-reviewer-prompt.md +195 -0
- package/skills/contract-builder/SKILL.md +72 -0
- package/skills/design-system/SKILL.md +75 -0
- package/skills/design-system/references/agents/design-system-architect.md +138 -0
- package/skills/design-system/references/creation-flow.md +170 -0
- package/skills/design-system/references/preview-template.html +319 -0
- package/skills/design-system/references/token-derivation.md +115 -0
- package/skills/design-system/references/variant-schema.md +60 -0
- package/skills/e2e/SKILL.md +56 -0
- package/skills/e2e/references/test-verifier.md +40 -0
- package/skills/need-explorer/SKILL.md +79 -0
- package/skills/prototype/SKILL.md +98 -0
- package/skills/prototype/references/agents/design-system-architect.md +122 -0
- package/skills/prototype/references/checklist.md +82 -0
- package/skills/prototype/references/craft/accessibility-baseline.md +71 -0
- package/skills/prototype/references/craft/anti-ai-slop.md +87 -0
- package/skills/prototype/references/craft/laws-of-ux.md +60 -0
- package/skills/prototype/references/craft/state-coverage.md +81 -0
- package/skills/prototype/references/craft/typography-hierarchy.md +50 -0
- package/skills/prototype/references/layouts.md +223 -0
- package/skills/prototype/references/orchestration-flow.md +101 -0
- package/skills/prototype/references/prototype-scaffold/assets/design-tokens.css +19 -0
- package/skills/prototype/references/prototype-scaffold/components/button.html +3 -0
- package/skills/prototype/references/prototype-scaffold/design-system.md +7 -0
- package/skills/prototype/references/prototype-scaffold/flow.md +11 -0
- package/skills/prototype/references/prototype-scaffold/index.html +20 -0
- package/skills/prototype/references/prototype-scaffold/pages/index.html +11 -0
- package/skills/prototype/references/template.html +396 -0
- package/skills/release-archivist/SKILL.md +166 -0
- package/skills/release-archivist/references/closing-procedures.md +81 -0
- package/skills/session-handoff/SKILL.md +110 -0
- package/skills/session-handoff/references/context-extraction.md +70 -0
- package/skills/session-handoff/references/handoff-template.md +90 -0
- package/skills/session-handoff/references/skill-recommendation.md +43 -0
- package/skills/spec-merger/SKILL.md +66 -0
- package/skills/spec-writer/SKILL.md +149 -0
- package/skills/workflow-bootstrap/SKILL.md +148 -0
- package/skills/workflow-bootstrap/references/agents/codebase-recon-analyst.md +121 -0
- package/skills/workflow-bootstrap/references/b1-reconnaissance.md +80 -0
- package/skills/workflow-bootstrap/scripts/recon-probe.sh +328 -0
- package/skills/workflow-feedback/SKILL.md +120 -0
- package/skills/workflow-feedback/references/category-guide.md +121 -0
- package/skills/workflow-feedback/references/feedback-template.md +73 -0
- package/skills/workflow-feedback/references/review-mode.md +92 -0
- package/skills/workflow-orchestrator/SKILL.md +94 -0
- package/skills/workflow-orchestrator/references/feedback-loops.md +56 -0
- package/skills/workflow-orchestrator/references/s1-path-router.md +81 -0
- package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +96 -0
- package/skills/workflow-orchestrator/references/s3-plan-pipeline.md +50 -0
- package/skills/workflow-orchestrator/references/s4-split-validate.md +108 -0
- package/skills/workflow-orchestrator/references/s5-monitoring.md +61 -0
- package/skills/workflow-orchestrator/references/state-model.md +185 -0
- package/skills/workflow-start/SKILL.md +147 -0
- package/skills/workflow-start/references/routing-rules.md +155 -0
- package/src/index.ts +16 -0
- package/src/parsing/change-parser.ts +79 -0
- package/src/parsing/requirement-blocks.ts +244 -0
- package/src/schema/base.ts +8 -0
- package/src/schema/change.ts +29 -0
- package/src/schema/index.ts +3 -0
- package/src/schema/spec.ts +12 -0
- package/src/validation/constants.ts +53 -0
- package/src/validation/tokenizer.ts +180 -0
- package/src/validation/types.ts +47 -0
- package/src/validation/validator.ts +552 -0
- package/templates/abandonment-summary.md +29 -0
- package/templates/design.md +37 -0
- package/templates/execution-contract.md +99 -0
- package/templates/plan.md +86 -0
- package/templates/prd.md +468 -0
- package/templates/proposal.md +37 -0
- package/templates/spec.md +31 -0
- package/templates/tasks.md +47 -0
- package/tests/e2e.test.mjs +266 -0
- package/tests/lib/cmd-audit.test.mjs +202 -0
- package/tests/lib/cmd-checkpoint.test.mjs +98 -0
- package/tests/lib/cmd-config.test.mjs +101 -0
- package/tests/lib/cmd-doctor.test.mjs +450 -0
- package/tests/lib/cmd-execution.test.mjs +647 -0
- package/tests/lib/cmd-handoff.test.mjs +137 -0
- package/tests/lib/cmd-inject.test.mjs +218 -0
- package/tests/lib/cmd-install-workbuddy.test.mjs +113 -0
- package/tests/lib/cmd-install-zcode.test.mjs +42 -0
- package/tests/lib/cmd-isolate.test.mjs +68 -0
- package/tests/lib/cmd-list.test.mjs +127 -0
- package/tests/lib/cmd-runtime.test.mjs +83 -0
- package/tests/lib/cmd-state-missing.test.mjs +65 -0
- package/tests/lib/cmd-state.test.mjs +365 -0
- package/tests/lib/cmd-sync-paths.test.mjs +84 -0
- package/tests/lib/cmd-validate-paths.test.mjs +90 -0
- package/tests/lib/config-loader.test.mjs +175 -0
- package/tests/lib/ensure-branch.test.mjs +59 -0
- package/tests/lib/execution-control-plane.test.mjs +168 -0
- package/tests/lib/execution-plan.test.mjs +474 -0
- package/tests/lib/guard-specs-merged.test.mjs +108 -0
- package/tests/lib/guard-tests-passing.test.mjs +112 -0
- package/tests/lib/guard-transitions.test.mjs +262 -0
- package/tests/lib/guard.test.mjs +611 -0
- package/tests/lib/hash.test.mjs +138 -0
- package/tests/lib/infer-workflow.test.mjs +161 -0
- package/tests/lib/install-git-hooks.test.mjs +20 -0
- package/tests/lib/marketplace-release-docs.test.mjs +31 -0
- package/tests/lib/minimality-discipline.test.mjs +37 -0
- package/tests/lib/model-profiles-docs.test.mjs +33 -0
- package/tests/lib/node20-compatibility.test.mjs +73 -0
- package/tests/lib/node20-test-entry.test.mjs +17 -0
- package/tests/lib/platform-runtime-distribution.test.mjs +150 -0
- package/tests/lib/raw-mode-smoke.test.mjs +42 -0
- package/tests/lib/sdd-overlay.test.mjs +118 -0
- package/tests/lib/spec-paths.test.mjs +80 -0
- package/tests/lib/state-loader.test.mjs +251 -0
- package/tests/lib/token-baseline.test.mjs +72 -0
- package/tests/lib/token-rules.test.mjs +94 -0
- package/tests/lib/verify-marketplace-release.test.mjs +128 -0
- package/tests/tsconfig.json +12 -0
- package/token-baseline.json +95 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate cited claims in a solution doc against the git tree.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 validate-doc-claims.py <doc-path>
|
|
6
|
+
|
|
7
|
+
Exit codes:
|
|
8
|
+
0 — nothing flagged
|
|
9
|
+
1 — one or more flags need adjudication (report on stdout)
|
|
10
|
+
2 — usage error (bad arguments, missing file)
|
|
11
|
+
|
|
12
|
+
Scope: mechanical grounding checks on a written doc's *body*. Complements
|
|
13
|
+
validate-frontmatter.py (parser-safety) — this script checks the body's
|
|
14
|
+
citations against the repository:
|
|
15
|
+
|
|
16
|
+
1. Cited repo-relative paths (backticked, containing at least one '/')
|
|
17
|
+
exist in the working tree; tokens containing '../' resolve from the
|
|
18
|
+
doc's directory (those escaping the repo are skipped). Misses tracked
|
|
19
|
+
at HEAD or the upstream default branch still count as real paths and
|
|
20
|
+
are classified (deleted/uncommitted vs stale checkout). Tokens
|
|
21
|
+
missing everywhere are flagged only when path-shaped; slash-delimited
|
|
22
|
+
identifiers (branch names, git refs, provider/model IDs) are skipped.
|
|
23
|
+
2. Cited commit SHAs (7-40 hex chars with at least one digit and one
|
|
24
|
+
a-f letter) resolve to commits, classified by reachability from
|
|
25
|
+
HEAD and the upstream default branch.
|
|
26
|
+
3. Relative markdown link targets resolve from the doc's location.
|
|
27
|
+
4. Dangling drafting scaffold: "Learning(s) N" numbering and
|
|
28
|
+
unresolved {{...}} placeholder tokens.
|
|
29
|
+
|
|
30
|
+
Flags are adjudication input, NOT hard failures — a doc may legitimately
|
|
31
|
+
cite a path deleted by the very fix it documents. The calling agent
|
|
32
|
+
decides per flag: fix, annotate as historical, or confirm intentional.
|
|
33
|
+
Only the summary exit code distinguishes "clean" from "needs a look".
|
|
34
|
+
|
|
35
|
+
The script never touches the network (no fetch); classification uses
|
|
36
|
+
whatever refs exist locally. Run a best-effort `git fetch --quiet` first
|
|
37
|
+
when freshness matters. Pure stdlib (no third-party deps).
|
|
38
|
+
"""
|
|
39
|
+
import os
|
|
40
|
+
import re
|
|
41
|
+
import subprocess
|
|
42
|
+
import sys
|
|
43
|
+
|
|
44
|
+
# Tokens containing these are placeholders/examples, not real citations.
|
|
45
|
+
PLACEHOLDER_CHARS = set("<>{}*$")
|
|
46
|
+
PLACEHOLDER_SUBSTRINGS = ("path/to", "...", "…")
|
|
47
|
+
|
|
48
|
+
SHA_RE = re.compile(r"\b[0-9a-f]{7,40}\b")
|
|
49
|
+
BACKTICK_RE = re.compile(r"`([^`\n]+)`")
|
|
50
|
+
MD_LINK_RE = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)")
|
|
51
|
+
SCAFFOLD_RES = (
|
|
52
|
+
re.compile(r"\bLearnings?\s+#?\d"),
|
|
53
|
+
re.compile(r"\{\{[^}\n]*\}\}"),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def usage_fail(msg: str) -> "NoReturn":
|
|
58
|
+
sys.stderr.write(f"validate-doc-claims: {msg}\n")
|
|
59
|
+
sys.exit(2)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def git(args: list[str], cwd: str) -> tuple[int, str]:
|
|
63
|
+
try:
|
|
64
|
+
result = subprocess.run(
|
|
65
|
+
["git", *args],
|
|
66
|
+
cwd=cwd,
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
timeout=30,
|
|
70
|
+
)
|
|
71
|
+
return result.returncode, result.stdout.strip()
|
|
72
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
73
|
+
return 1, ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def split_body(text: str) -> tuple[str, int]:
|
|
77
|
+
"""Return (body, 1-indexed line number the body starts on).
|
|
78
|
+
|
|
79
|
+
Skips YAML frontmatter when present so frontmatter fields are not
|
|
80
|
+
scanned as body citations.
|
|
81
|
+
"""
|
|
82
|
+
lines = text.split("\n")
|
|
83
|
+
if lines and lines[0].rstrip() == "---":
|
|
84
|
+
for i in range(1, len(lines)):
|
|
85
|
+
if lines[i].rstrip() == "---":
|
|
86
|
+
return "\n".join(lines[i + 1 :]), i + 2
|
|
87
|
+
return text, 1
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def is_path_candidate(token: str) -> bool:
|
|
91
|
+
if any(ch.isspace() for ch in token):
|
|
92
|
+
return False
|
|
93
|
+
if "/" not in token:
|
|
94
|
+
return False
|
|
95
|
+
if "://" in token or token.startswith(("http", "#", "/", "~")):
|
|
96
|
+
return False
|
|
97
|
+
if token.startswith(("origin/", "upstream/", "refs/")):
|
|
98
|
+
return False # git refs, not repo paths
|
|
99
|
+
if PLACEHOLDER_CHARS & set(token):
|
|
100
|
+
return False
|
|
101
|
+
if any(sub in token for sub in PLACEHOLDER_SUBSTRINGS):
|
|
102
|
+
return False
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_path_shaped(token: str, base: str) -> bool:
|
|
107
|
+
"""Distinguish a path citation from a slash-delimited identifier
|
|
108
|
+
(branch name, provider/model ID) among tokens found nowhere in git."""
|
|
109
|
+
segments = token.split("/")
|
|
110
|
+
if re.search(r"\.[A-Za-z0-9]{1,8}$", segments[-1]):
|
|
111
|
+
return True
|
|
112
|
+
if token.endswith("/"):
|
|
113
|
+
return True
|
|
114
|
+
return os.path.isdir(os.path.join(base, segments[0]))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def normalize_path(token: str) -> str:
|
|
118
|
+
token = token.strip().rstrip(".,;")
|
|
119
|
+
token = re.sub(r":\d+(-\d+)?$", "", token) # strip `:line` / `:a-b` refs
|
|
120
|
+
if token.startswith("./"):
|
|
121
|
+
token = token[2:]
|
|
122
|
+
return token
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main(argv: list[str]) -> int:
|
|
126
|
+
if len(argv) != 2:
|
|
127
|
+
usage_fail(f"usage: {os.path.basename(argv[0])} <doc-path>")
|
|
128
|
+
|
|
129
|
+
doc_path = argv[1]
|
|
130
|
+
if not os.path.isfile(doc_path):
|
|
131
|
+
usage_fail(f"file not found: {doc_path}")
|
|
132
|
+
|
|
133
|
+
with open(doc_path) as f:
|
|
134
|
+
text = f.read()
|
|
135
|
+
|
|
136
|
+
doc_dir = os.path.dirname(os.path.abspath(doc_path))
|
|
137
|
+
body, body_start = split_body(text)
|
|
138
|
+
body_lines = body.split("\n")
|
|
139
|
+
|
|
140
|
+
def loc_suffix(needle: str) -> str:
|
|
141
|
+
for i, line in enumerate(body_lines):
|
|
142
|
+
if needle in line:
|
|
143
|
+
return f" (line {body_start + i})"
|
|
144
|
+
return ""
|
|
145
|
+
|
|
146
|
+
infos: list[str] = []
|
|
147
|
+
flags: list[str] = []
|
|
148
|
+
|
|
149
|
+
# --- Repo context -----------------------------------------------------
|
|
150
|
+
code, repo_root = git(["rev-parse", "--show-toplevel"], doc_dir)
|
|
151
|
+
in_git = code == 0 and bool(repo_root)
|
|
152
|
+
upstream: str | None = None
|
|
153
|
+
if in_git:
|
|
154
|
+
code, ref = git(["rev-parse", "--abbrev-ref", "origin/HEAD"], repo_root)
|
|
155
|
+
if code == 0 and ref:
|
|
156
|
+
upstream = ref
|
|
157
|
+
else:
|
|
158
|
+
for candidate in ("origin/main", "origin/master"):
|
|
159
|
+
code, _ = git(
|
|
160
|
+
["rev-parse", "--verify", "--quiet", candidate], repo_root
|
|
161
|
+
)
|
|
162
|
+
if code == 0:
|
|
163
|
+
upstream = candidate
|
|
164
|
+
break
|
|
165
|
+
if upstream:
|
|
166
|
+
code, behind = git(
|
|
167
|
+
["rev-list", "--count", f"HEAD..{upstream}"], repo_root
|
|
168
|
+
)
|
|
169
|
+
if code == 0 and behind.isdigit() and int(behind) > 0:
|
|
170
|
+
infos.append(
|
|
171
|
+
f"INFO: worktree is {behind} commits behind {upstream} — "
|
|
172
|
+
"verify merge-state claims against remote truth (gh pr view), "
|
|
173
|
+
"not this checkout"
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
infos.append(
|
|
177
|
+
"INFO: no upstream default branch found — "
|
|
178
|
+
"path/SHA classification limited to HEAD"
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
infos.append(
|
|
182
|
+
"INFO: not a git repository — path and SHA classification skipped "
|
|
183
|
+
"(scaffold and link checks still apply)"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def upstream_has_path(path: str) -> bool:
|
|
187
|
+
if not (in_git and upstream):
|
|
188
|
+
return False
|
|
189
|
+
code, _ = git(["cat-file", "-e", f"{upstream}:{path}"], repo_root)
|
|
190
|
+
return code == 0
|
|
191
|
+
|
|
192
|
+
def head_has_path(path: str) -> bool:
|
|
193
|
+
if not in_git:
|
|
194
|
+
return False
|
|
195
|
+
code, _ = git(["cat-file", "-e", f"HEAD:{path}"], repo_root)
|
|
196
|
+
return code == 0
|
|
197
|
+
|
|
198
|
+
# --- 1. Cited repo paths ----------------------------------------------
|
|
199
|
+
checked_paths = 0
|
|
200
|
+
seen_paths: set[str] = set()
|
|
201
|
+
base = repo_root if in_git else os.getcwd()
|
|
202
|
+
for raw in BACKTICK_RE.findall(body):
|
|
203
|
+
token = normalize_path(raw)
|
|
204
|
+
if not is_path_candidate(token):
|
|
205
|
+
continue
|
|
206
|
+
check = token
|
|
207
|
+
if token.startswith("../") or "/../" in token:
|
|
208
|
+
# A `../` citation is doc-relative (matching how markdown links
|
|
209
|
+
# resolve), so map it to a repo-root path before checking.
|
|
210
|
+
if not in_git:
|
|
211
|
+
continue
|
|
212
|
+
resolved = os.path.realpath(os.path.join(doc_dir, token))
|
|
213
|
+
check = os.path.relpath(resolved, os.path.realpath(base))
|
|
214
|
+
if check.startswith(".."):
|
|
215
|
+
continue # escapes the repo — not checkable as a repo path
|
|
216
|
+
if check in seen_paths:
|
|
217
|
+
continue
|
|
218
|
+
seen_paths.add(check)
|
|
219
|
+
if os.path.exists(os.path.join(base, check)):
|
|
220
|
+
checked_paths += 1
|
|
221
|
+
continue
|
|
222
|
+
tracked_head = head_has_path(check)
|
|
223
|
+
tracked_upstream = upstream_has_path(check)
|
|
224
|
+
if not (tracked_head or tracked_upstream) and not is_path_shaped(
|
|
225
|
+
check, base
|
|
226
|
+
):
|
|
227
|
+
continue # branch name / provider ID, not a path citation
|
|
228
|
+
checked_paths += 1
|
|
229
|
+
loc = loc_suffix(raw)
|
|
230
|
+
if tracked_head:
|
|
231
|
+
flags.append(
|
|
232
|
+
f"FLAG path `{token}`{loc} — tracked at HEAD but missing from "
|
|
233
|
+
"the working tree: deleted or uncommitted removal? Annotate as "
|
|
234
|
+
"historical (e.g. removed by this fix) or restore it."
|
|
235
|
+
)
|
|
236
|
+
elif tracked_upstream:
|
|
237
|
+
flags.append(
|
|
238
|
+
f"FLAG path `{token}`{loc} — not in working tree but exists at "
|
|
239
|
+
f"{upstream}: stale checkout? Annotate or verify against upstream."
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
where = (
|
|
243
|
+
f"working tree or {upstream}" if upstream else "working tree"
|
|
244
|
+
)
|
|
245
|
+
flags.append(
|
|
246
|
+
f"FLAG path `{token}`{loc} — not found in {where}. Fix the "
|
|
247
|
+
"citation, or annotate it as historical (e.g. removed by this fix)."
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# --- 2. Cited commit SHAs ----------------------------------------------
|
|
251
|
+
checked_shas = 0
|
|
252
|
+
seen_shas: set[str] = set()
|
|
253
|
+
if in_git:
|
|
254
|
+
for m in SHA_RE.finditer(body):
|
|
255
|
+
sha = m.group(0)
|
|
256
|
+
if sha in seen_shas:
|
|
257
|
+
continue
|
|
258
|
+
if not (any(c.isdigit() for c in sha) and any(c in "abcdef" for c in sha)):
|
|
259
|
+
continue # dates and decimal ids are not SHAs
|
|
260
|
+
seen_shas.add(sha)
|
|
261
|
+
checked_shas += 1
|
|
262
|
+
loc = loc_suffix(sha)
|
|
263
|
+
code, _ = git(["cat-file", "-e", f"{sha}^{{commit}}"], repo_root)
|
|
264
|
+
if code != 0:
|
|
265
|
+
flags.append(
|
|
266
|
+
f"FLAG sha {sha}{loc} — does not resolve to a commit in this "
|
|
267
|
+
"repository. Replace with the PR number, or drop it."
|
|
268
|
+
)
|
|
269
|
+
continue
|
|
270
|
+
in_head = (
|
|
271
|
+
git(["merge-base", "--is-ancestor", sha, "HEAD"], repo_root)[0] == 0
|
|
272
|
+
)
|
|
273
|
+
in_up = (
|
|
274
|
+
upstream is not None
|
|
275
|
+
and git(["merge-base", "--is-ancestor", sha, upstream], repo_root)[0]
|
|
276
|
+
== 0
|
|
277
|
+
)
|
|
278
|
+
if in_head and (in_up or upstream is None):
|
|
279
|
+
continue
|
|
280
|
+
if in_head and not in_up:
|
|
281
|
+
flags.append(
|
|
282
|
+
f"FLAG sha {sha}{loc} — reachable from HEAD but not {upstream}: "
|
|
283
|
+
"local-only commit whose SHA may be rewritten on merge "
|
|
284
|
+
"(rebase/squash). Prefer citing the PR number."
|
|
285
|
+
)
|
|
286
|
+
elif in_up:
|
|
287
|
+
flags.append(
|
|
288
|
+
f"FLAG sha {sha}{loc} — not reachable from HEAD but reachable "
|
|
289
|
+
f"from {upstream}: this checkout predates the merge. Add a "
|
|
290
|
+
"temporal qualifier or verify the claim via gh."
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
flags.append(
|
|
294
|
+
f"FLAG sha {sha}{loc} — exists but unreachable from HEAD"
|
|
295
|
+
+ (f" or {upstream}" if upstream else "")
|
|
296
|
+
+ ": likely a rebased-away commit. Prefer citing the PR number."
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# --- 3. Relative markdown links -----------------------------------------
|
|
300
|
+
checked_links = 0
|
|
301
|
+
seen_links: set[str] = set()
|
|
302
|
+
for target in MD_LINK_RE.findall(body):
|
|
303
|
+
if re.match(r"^[a-z][a-z0-9+.-]*:", target, re.IGNORECASE):
|
|
304
|
+
continue # URL scheme
|
|
305
|
+
if target.startswith("#"):
|
|
306
|
+
continue # intra-doc anchor
|
|
307
|
+
bare = target.split("#", 1)[0]
|
|
308
|
+
if not bare or bare in seen_links:
|
|
309
|
+
continue
|
|
310
|
+
seen_links.add(bare)
|
|
311
|
+
checked_links += 1
|
|
312
|
+
if not os.path.exists(os.path.normpath(os.path.join(doc_dir, bare))):
|
|
313
|
+
loc = loc_suffix(target)
|
|
314
|
+
flags.append(
|
|
315
|
+
f"FLAG link ({target}){loc} — relative target does not resolve "
|
|
316
|
+
"from the doc's location. Fix the path."
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# --- 4. Dangling drafting scaffold ---------------------------------------
|
|
320
|
+
for i, line_text in enumerate(body_lines):
|
|
321
|
+
for pattern in SCAFFOLD_RES:
|
|
322
|
+
m = pattern.search(line_text)
|
|
323
|
+
if m:
|
|
324
|
+
flags.append(
|
|
325
|
+
f'FLAG scaffold "{m.group(0)}" (line {body_start + i}) — '
|
|
326
|
+
"drafting-context reference leaked into the doc. Rewrite it "
|
|
327
|
+
"as a real path or link."
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# --- Report ---------------------------------------------------------------
|
|
331
|
+
for info in infos:
|
|
332
|
+
print(info)
|
|
333
|
+
for flag in flags:
|
|
334
|
+
print(flag)
|
|
335
|
+
print(
|
|
336
|
+
f"checked {checked_paths} paths, {checked_shas} SHAs, "
|
|
337
|
+
f"{checked_links} links; {len(flags)} flags"
|
|
338
|
+
)
|
|
339
|
+
if flags:
|
|
340
|
+
return 1
|
|
341
|
+
print(f"OK: {doc_path}")
|
|
342
|
+
return 0
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
if __name__ == "__main__":
|
|
346
|
+
sys.exit(main(sys.argv))
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate ce-compound docs/solutions/ frontmatter for parser-safety issues.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 validate-frontmatter.py <doc-path>
|
|
6
|
+
|
|
7
|
+
Exit codes:
|
|
8
|
+
0 — frontmatter passes all checks
|
|
9
|
+
1 — validation failure (diagnostics on stderr)
|
|
10
|
+
2 — usage error (bad arguments, missing file)
|
|
11
|
+
|
|
12
|
+
Scope: this script catches *parser-safety* issues — frontmatter that strict
|
|
13
|
+
YAML parsers will silently misread. It does NOT validate against the
|
|
14
|
+
schema's required-field or enum-value rules; that's a separate concern. The
|
|
15
|
+
intent is to prevent the silent-data-loss bug class where YAML's quoting
|
|
16
|
+
rules truncate or reframe scalar values without raising.
|
|
17
|
+
|
|
18
|
+
Checks (regex-based, no YAML parser dependency):
|
|
19
|
+
1. File starts and ends frontmatter with `---` lines (matched as full
|
|
20
|
+
lines, not substrings — `----` and `---extra` are rejected)
|
|
21
|
+
2. No top-level scalar value contains ` #` unquoted (silent comment
|
|
22
|
+
truncation — what Codex caught on PR #695)
|
|
23
|
+
3. No top-level scalar value contains `: ` unquoted (mapping confusion —
|
|
24
|
+
what surfaced in a 2026-04-16 plan doc's `title:` field)
|
|
25
|
+
|
|
26
|
+
The script does NOT flag values starting with YAML reserved indicators
|
|
27
|
+
(`` ` ``, `*`, `&`, `!`, etc.) because those produce loud parser errors
|
|
28
|
+
downstream rather than silent corruption — they're already caught by
|
|
29
|
+
whatever consumes the doc. This validator's purpose is silent-corruption
|
|
30
|
+
prevention, not lint.
|
|
31
|
+
|
|
32
|
+
Pure-stdlib (no PyYAML or other third-party deps). Runs in <50ms typical.
|
|
33
|
+
Designed to produce concrete, actionable error messages so the calling
|
|
34
|
+
agent can fix and retry without ambiguity.
|
|
35
|
+
"""
|
|
36
|
+
import os
|
|
37
|
+
import re
|
|
38
|
+
import sys
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def usage_fail(msg: str) -> "NoReturn":
|
|
42
|
+
sys.stderr.write(f"validate-frontmatter: {msg}\n")
|
|
43
|
+
sys.exit(2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main(argv: list[str]) -> int:
|
|
47
|
+
if len(argv) != 2:
|
|
48
|
+
usage_fail(f"usage: {os.path.basename(argv[0])} <doc-path>")
|
|
49
|
+
|
|
50
|
+
doc_path = argv[1]
|
|
51
|
+
if not os.path.isfile(doc_path):
|
|
52
|
+
usage_fail(f"file not found: {doc_path}")
|
|
53
|
+
|
|
54
|
+
with open(doc_path) as f:
|
|
55
|
+
text = f.read()
|
|
56
|
+
|
|
57
|
+
issues: list[str] = []
|
|
58
|
+
|
|
59
|
+
# Check 1: frontmatter delimiters. Match the delimiter as a complete
|
|
60
|
+
# line whose stripped content is exactly `---` — substring matching
|
|
61
|
+
# (e.g. `text.find("\n---", 4)`) would falsely accept `----` or
|
|
62
|
+
# `---extra` as a terminator and let malformed docs slip through to
|
|
63
|
+
# downstream parsers that require a strict `---` line.
|
|
64
|
+
lines = text.split("\n")
|
|
65
|
+
if not lines or lines[0].rstrip() != "---":
|
|
66
|
+
sys.stderr.write(
|
|
67
|
+
f"FAIL: {doc_path}\n"
|
|
68
|
+
f" file does not start with '---' frontmatter delimiter line\n"
|
|
69
|
+
)
|
|
70
|
+
return 1
|
|
71
|
+
|
|
72
|
+
end_idx: int | None = None
|
|
73
|
+
for i in range(1, len(lines)):
|
|
74
|
+
if lines[i].rstrip() == "---":
|
|
75
|
+
end_idx = i
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
if end_idx is None:
|
|
79
|
+
sys.stderr.write(
|
|
80
|
+
f"FAIL: {doc_path}\n"
|
|
81
|
+
f" frontmatter not closed (no '---' line after the opening delimiter)\n"
|
|
82
|
+
)
|
|
83
|
+
return 1
|
|
84
|
+
|
|
85
|
+
fm_text = "\n".join(lines[1:end_idx])
|
|
86
|
+
|
|
87
|
+
# Checks 2 & 3: silent-corruption quoting risks on top-level scalar
|
|
88
|
+
# fields. We scan line-by-line and only flag top-level mapping entries
|
|
89
|
+
# (no leading whitespace) whose value isn't already quoted/structured.
|
|
90
|
+
for lineno, line in enumerate(fm_text.split("\n"), start=2):
|
|
91
|
+
stripped = line.lstrip()
|
|
92
|
+
if not stripped or stripped.startswith("#"):
|
|
93
|
+
continue
|
|
94
|
+
if ":" not in line:
|
|
95
|
+
continue
|
|
96
|
+
# Top-level mapping keys only — skip nested values, array items
|
|
97
|
+
if line.startswith((" ", "\t")):
|
|
98
|
+
continue
|
|
99
|
+
# Skip pure list-marker lines like "- item" (these can't be top-level
|
|
100
|
+
# in our frontmatter convention, but be defensive)
|
|
101
|
+
if stripped.startswith("- "):
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
key, _, val = line.partition(":")
|
|
105
|
+
val_stripped = val.strip()
|
|
106
|
+
if not val_stripped:
|
|
107
|
+
# Key with no value on this line — likely a parent of a nested
|
|
108
|
+
# block (`tags:` followed by `- foo`). Nothing to validate here.
|
|
109
|
+
continue
|
|
110
|
+
# Already quoted or structured (block scalar, flow collection)
|
|
111
|
+
if val_stripped[0] in '"\'[{|>':
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if re.search(r"\s#", val_stripped):
|
|
115
|
+
issues.append(
|
|
116
|
+
f"line {lineno}: '{key.strip()}' value contains ' #' — quote it. "
|
|
117
|
+
"YAML treats space-then-# as a comment delimiter and silently "
|
|
118
|
+
"drops the rest of the value."
|
|
119
|
+
)
|
|
120
|
+
if re.search(r":\s", val_stripped):
|
|
121
|
+
issues.append(
|
|
122
|
+
f"line {lineno}: '{key.strip()}' value contains ': ' — quote it. "
|
|
123
|
+
"Strict YAML parsers may treat this as a nested mapping."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if issues:
|
|
127
|
+
sys.stderr.write(f"FAIL: {doc_path}\n")
|
|
128
|
+
for issue in issues:
|
|
129
|
+
sys.stderr.write(f" {issue}\n")
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
print(f"OK: {doc_path}")
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
sys.exit(main(sys.argv))
|