@opengsd/gsd-core 1.12.0 → 1.14.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +458 -0
- package/agents/gsd-code-fixer.md +5 -5
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +345 -0
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +444 -134
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +1 -0
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +2 -1
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/surface.md +18 -8
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +532 -174
- package/gsd-core/bin/lib/adr-parser.cjs +1 -1
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +39 -22
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +528 -116
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +134 -5
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +198 -38
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +981 -79
- package/gsd-core/bin/lib/config-loader.cjs +4 -0
- package/gsd-core/bin/lib/config.cjs +153 -38
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +343 -28
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +87 -23
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +539 -60
- package/gsd-core/bin/lib/install-engine.cjs +199 -14
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +36 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +33 -4
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +64 -39
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +41 -10
- package/gsd-core/bin/lib/model-resolver.cjs +101 -10
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -7
- package/gsd-core/bin/lib/phase-id.cjs +412 -31
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase.cjs +941 -98
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +34 -18
- package/gsd-core/bin/lib/planning-snapshot.cjs +206 -30
- package/gsd-core/bin/lib/planning-workspace.cjs +153 -29
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +96 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +555 -41
- package/gsd-core/bin/lib/roadmap.cjs +292 -69
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +260 -43
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +28 -20
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +294 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -47
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +159 -32
- package/gsd-core/bin/lib/state-md-schema.cjs +44 -27
- package/gsd-core/bin/lib/state-transition.cjs +465 -62
- package/gsd-core/bin/lib/state.cjs +906 -151
- package/gsd-core/bin/lib/surface.cjs +83 -10
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1420 -516
- package/gsd-core/bin/lib/update-context.cjs +36 -26
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +316 -23
- package/gsd-core/bin/lib/verify-command-grounding.cjs +1 -1
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +531 -36
- package/gsd-core/bin/lib/workstream-inventory.cjs +21 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +21 -7
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +507 -81
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +12 -3
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +5 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +17 -9
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/worktree-path-safety.md +112 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +4 -3
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +15 -10
- package/gsd-core/workflows/check-todos.md +5 -3
- package/gsd-core/workflows/cleanup.md +4 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +22 -13
- package/gsd-core/workflows/code-review-fix.md +5 -3
- package/gsd-core/workflows/code-review.md +211 -43
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +40 -254
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +15 -156
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +20 -3
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +24 -3
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +78 -159
- package/gsd-core/workflows/execute-plan.md +28 -15
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +3 -1
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +52 -3
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +55 -13
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project.md +37 -205
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +144 -185
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +30 -10
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +21 -4
- package/gsd-core/workflows/reapply-patches.md +79 -3
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +19 -6
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +48 -43
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work.md +68 -182
- package/hooks/dist/gsd-agent-isolation-guard.js +42 -16
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +371 -27
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1105 -0
- package/hooks/dist/gsd-statusline.js +18 -10
- package/hooks/dist/gsd-validate-commit.sh +474 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +42 -16
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +371 -27
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1105 -0
- package/hooks/gsd-statusline.js +18 -10
- package/hooks/gsd-validate-commit.sh +474 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -9
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +11 -4
- package/scripts/check-contract-drift.cjs +4 -1
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +133 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +17 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +210 -0
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +276 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +24 -2
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +465 -15
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +530 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-test-file-count.allowlist.json +4 -1
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +1112 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +22 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +1 -0
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +2 -1
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* shellcheck-fetch.cjs
|
|
5
|
+
*
|
|
6
|
+
* Dependency-free replacement for the `shellcheck` npm package (removed in
|
|
7
|
+
* #4120 — its extraction step pulled in `decompress@4.2.1`, which carries an
|
|
8
|
+
* unpatched CRITICAL zip-slip vulnerability, GHSA-mp2f-45pm-3cg9, CVSS 9.1,
|
|
9
|
+
* plus two moderate findings, with no patched version available upstream).
|
|
10
|
+
*
|
|
11
|
+
* This module fetches a PINNED koalaman/shellcheck release directly from
|
|
12
|
+
* GitHub releases (never "latest" — see SHELLCHECK_VERSION below), extracts
|
|
13
|
+
* the single `shellcheck` binary from the release's `.tar.gz` asset using
|
|
14
|
+
* only Node's built-in `https`/`zlib` modules plus a small hand-written tar
|
|
15
|
+
* reader (no third-party archive library), and caches the extracted binary
|
|
16
|
+
* for reuse across runs.
|
|
17
|
+
*
|
|
18
|
+
* Zip-slip defense: unlike `decompress`, which wrote extracted files using
|
|
19
|
+
* PATHS TAKEN FROM THE ARCHIVE (the exact defect class in GHSA-mp2f-45pm-
|
|
20
|
+
* 3cg9 — a malicious archive entry named e.g. `../../etc/passwd` gets
|
|
21
|
+
* written there verbatim), this reader NEVER uses an archive-supplied name
|
|
22
|
+
* as a filesystem path. `extractFileFromTar` only ever returns the matched
|
|
23
|
+
* entry's raw byte content; the caller (`resolveShellcheckBin`) writes those
|
|
24
|
+
* bytes to a path it constructs itself (`<cacheDir>/shellcheck`), and the
|
|
25
|
+
* archive's own name field is used only for a string-equality/suffix CHECK
|
|
26
|
+
* (`name === targetName || name.endsWith('/' + targetName)`), never
|
|
27
|
+
* interpolated into a path passed to `fs.writeFileSync`/`fs.mkdirSync`/etc.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require('node:fs');
|
|
31
|
+
const path = require('node:path');
|
|
32
|
+
const zlib = require('node:zlib');
|
|
33
|
+
const https = require('node:https');
|
|
34
|
+
const { ExitError } = require('./cli-exit.cjs');
|
|
35
|
+
|
|
36
|
+
// Pinned explicitly — verified against `koalaman/shellcheck`'s GitHub
|
|
37
|
+
// releases API as the current latest tag at the time this was written
|
|
38
|
+
// (2026-08-31). Never resolved dynamically ("latest") — a moving target
|
|
39
|
+
// would make this lint's exact ShellCheck version, and therefore its exact
|
|
40
|
+
// finding set against scripts/lint-workflow-shellcheck-baseline.json,
|
|
41
|
+
// non-reproducible across runs/machines/CI.
|
|
42
|
+
const SHELLCHECK_VERSION = 'v0.11.0';
|
|
43
|
+
|
|
44
|
+
const ROOT = path.join(__dirname, '..', '..');
|
|
45
|
+
const CACHE_DIR = path.join(ROOT, 'node_modules', '.cache', 'shellcheck', SHELLCHECK_VERSION);
|
|
46
|
+
const CACHED_BIN_PATH = path.join(CACHE_DIR, 'shellcheck');
|
|
47
|
+
|
|
48
|
+
// process.arch -> the arch token ShellCheck's release asset names use.
|
|
49
|
+
// Only the two architectures that actually matter for this repo (per
|
|
50
|
+
// .github/workflows/test.yml: the lint-tests job that runs this script only
|
|
51
|
+
// runs on ubuntu-latest, which is x86_64; and Apple Silicon dev machines are
|
|
52
|
+
// aarch64) are supported — anything else fails with a clear error rather
|
|
53
|
+
// than guessing.
|
|
54
|
+
const ARCH_MAP = { x64: 'x86_64', arm64: 'aarch64' };
|
|
55
|
+
|
|
56
|
+
const MAX_REDIRECTS = 5;
|
|
57
|
+
|
|
58
|
+
// Bounds each individual HTTP hop (the initial request AND every redirect
|
|
59
|
+
// hop get their own fresh 30s budget, rather than one shared budget across
|
|
60
|
+
// the whole redirect chain) — a stalled connection on any single hop is
|
|
61
|
+
// caught in a bounded time, mirroring lint-workflow-shellcheck.cjs's own
|
|
62
|
+
// SHELLCHECK_TIMEOUT_MS bound on the ShellCheck subprocess. A bare `timeout`
|
|
63
|
+
// option on the request does NOT abort it by itself — Node only emits a
|
|
64
|
+
// 'timeout' event, which must be handled by destroying the request (see the
|
|
65
|
+
// `req.on('timeout', ...)` below).
|
|
66
|
+
const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Issue one real HTTPS request. Exists as its own function purely so tests
|
|
70
|
+
* can inject a fake in its place (see `httpsGetFollowingRedirects`'s
|
|
71
|
+
* `requestFn` parameter) — production callers never pass an override, so
|
|
72
|
+
* the real download path always uses this exact implementation.
|
|
73
|
+
*/
|
|
74
|
+
function defaultRequestFn(url, options, callback) {
|
|
75
|
+
return https.get(url, options, callback);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* GET `url` following HTTP redirects manually — `https.get` does NOT follow
|
|
80
|
+
* redirects automatically, and GitHub release asset URLs redirect through
|
|
81
|
+
* `objects.githubusercontent.com`. Resolves with the full response body as a
|
|
82
|
+
* Buffer once a 200 response is received.
|
|
83
|
+
*
|
|
84
|
+
* `requestFn` defaults to a real `https.get`-based transport
|
|
85
|
+
* (`defaultRequestFn`) and is only ever overridden in tests, so calling this
|
|
86
|
+
* with zero/one arg from `resolveShellcheckBin` is unchanged behavior.
|
|
87
|
+
*/
|
|
88
|
+
function httpsGetFollowingRedirects(url, redirectsLeft = MAX_REDIRECTS, requestFn = defaultRequestFn) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const req = requestFn(
|
|
91
|
+
url,
|
|
92
|
+
{ headers: { 'User-Agent': 'gsd-core-shellcheck-fetch' }, timeout: DOWNLOAD_TIMEOUT_MS },
|
|
93
|
+
(res) => {
|
|
94
|
+
const status = res.statusCode || 0;
|
|
95
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
96
|
+
res.resume(); // drain so the socket can be reused/closed
|
|
97
|
+
if (redirectsLeft <= 0) {
|
|
98
|
+
reject(new Error(`too many redirects fetching ${url}`));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const next = new URL(res.headers.location, url).toString();
|
|
102
|
+
httpsGetFollowingRedirects(next, redirectsLeft - 1, requestFn).then(resolve, reject);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (status !== 200) {
|
|
106
|
+
res.resume();
|
|
107
|
+
reject(new Error(`unexpected HTTP ${status} fetching ${url}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
112
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
113
|
+
res.on('error', reject);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
req.on('error', reject);
|
|
117
|
+
// `timeout` in the options above only ARMS a timer — Node emits a
|
|
118
|
+
// 'timeout' event on the request but does not abort it. Without this
|
|
119
|
+
// handler the request (and this Promise) would hang indefinitely past
|
|
120
|
+
// the configured bound on a stalled connection.
|
|
121
|
+
req.on('timeout', () => {
|
|
122
|
+
req.destroy(new Error(`timed out after ${DOWNLOAD_TIMEOUT_MS}ms fetching ${url}`));
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Find the entry named (or path-ending-in) `targetName` inside a raw
|
|
129
|
+
* (already gunzipped) POSIX tar byte stream and return its content as a
|
|
130
|
+
* Buffer, or `null` if not found.
|
|
131
|
+
*
|
|
132
|
+
* Tar format: a sequence of 512-byte headers — name at offset 0/length 100,
|
|
133
|
+
* size at offset 124/length 12 (octal ASCII), typeflag at offset 156 — each
|
|
134
|
+
* followed by that many content bytes, padded up to the next 512-byte
|
|
135
|
+
* boundary, terminated by an all-zero 512-byte block. This deliberately
|
|
136
|
+
* implements only enough to locate ONE known entry name (no general
|
|
137
|
+
* multi-file extraction, no symlink handling, no GNU long-name `@LongLink`
|
|
138
|
+
* entries — ShellCheck's own release tarballs never need them) — see this
|
|
139
|
+
* module's header comment for why the entry's NAME is never used as a
|
|
140
|
+
* filesystem path.
|
|
141
|
+
*/
|
|
142
|
+
function extractFileFromTar(buffer, targetName) {
|
|
143
|
+
let offset = 0;
|
|
144
|
+
while (offset + 512 <= buffer.length) {
|
|
145
|
+
const header = buffer.subarray(offset, offset + 512);
|
|
146
|
+
if (header.every((b) => b === 0)) break; // end-of-archive marker
|
|
147
|
+
const name = header.subarray(0, 100).toString('utf8').replace(/\0.*$/, '');
|
|
148
|
+
const sizeRaw = header.subarray(124, 136).toString('utf8').replace(/\0.*$/, '').trim();
|
|
149
|
+
const size = sizeRaw === '' ? 0 : parseInt(sizeRaw, 8);
|
|
150
|
+
const typeflag = String.fromCharCode(header[156]);
|
|
151
|
+
const dataStart = offset + 512;
|
|
152
|
+
const isRegularFile = typeflag === '0' || typeflag === '\0';
|
|
153
|
+
if (isRegularFile && (name === targetName || name.endsWith(`/${targetName}`))) {
|
|
154
|
+
return buffer.subarray(dataStart, dataStart + size);
|
|
155
|
+
}
|
|
156
|
+
const contentBlocks = Math.ceil(size / 512);
|
|
157
|
+
offset = dataStart + contentBlocks * 512;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the local path to a working, executable `shellcheck` binary,
|
|
164
|
+
* downloading and caching the pinned release on first use. Subsequent calls
|
|
165
|
+
* (same version) reuse the cached binary with no network activity — mirrors
|
|
166
|
+
* the `fs.accessSync(bin, F_OK | X_OK)` cache-check pattern already used by
|
|
167
|
+
* this script's own `runShellcheck`.
|
|
168
|
+
*/
|
|
169
|
+
async function resolveShellcheckBin() {
|
|
170
|
+
try {
|
|
171
|
+
fs.accessSync(CACHED_BIN_PATH, fs.constants.F_OK | fs.constants.X_OK);
|
|
172
|
+
return CACHED_BIN_PATH;
|
|
173
|
+
} catch {
|
|
174
|
+
// not cached yet — fall through to download
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (process.platform === 'win32') {
|
|
178
|
+
throw new ExitError(
|
|
179
|
+
1,
|
|
180
|
+
'lint-workflow-shellcheck: automatic ShellCheck download is not supported on Windows yet ' +
|
|
181
|
+
'(this lint only ever runs in the ubuntu-latest lint-tests CI job — see .github/workflows/test.yml — ' +
|
|
182
|
+
'so this is an honest platform gap, not expected to be hit in CI).',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const platform = process.platform === 'darwin' || process.platform === 'linux' ? process.platform : null;
|
|
186
|
+
if (!platform) {
|
|
187
|
+
throw new ExitError(
|
|
188
|
+
1,
|
|
189
|
+
`lint-workflow-shellcheck: unsupported platform '${process.platform}' for ShellCheck auto-download ` +
|
|
190
|
+
`(supported: linux, darwin).`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const arch = ARCH_MAP[process.arch];
|
|
194
|
+
if (!arch) {
|
|
195
|
+
throw new ExitError(
|
|
196
|
+
1,
|
|
197
|
+
`lint-workflow-shellcheck: unsupported architecture '${process.arch}' for ShellCheck auto-download ` +
|
|
198
|
+
`(supported: x86_64 [node arch 'x64'], aarch64 [node arch 'arm64']).`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const assetName = `shellcheck-${SHELLCHECK_VERSION}.${platform}.${arch}.tar.gz`;
|
|
203
|
+
const url = `https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/${assetName}`;
|
|
204
|
+
|
|
205
|
+
let gz;
|
|
206
|
+
try {
|
|
207
|
+
gz = await httpsGetFollowingRedirects(url);
|
|
208
|
+
} catch (e) {
|
|
209
|
+
throw new ExitError(1, `lint-workflow-shellcheck: failed to download ShellCheck (${url}): ${e.message}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let tarBuf;
|
|
213
|
+
try {
|
|
214
|
+
tarBuf = zlib.gunzipSync(gz);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
throw new ExitError(1, `lint-workflow-shellcheck: failed to gunzip downloaded ShellCheck archive: ${e.message}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const entry = extractFileFromTar(tarBuf, 'shellcheck');
|
|
220
|
+
if (!entry) {
|
|
221
|
+
throw new ExitError(
|
|
222
|
+
1,
|
|
223
|
+
`lint-workflow-shellcheck: could not find a 'shellcheck' entry inside downloaded archive ${assetName}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
228
|
+
// Write to a per-process temp path and rename into place — avoids any
|
|
229
|
+
// other concurrent invocation observing (and trying to execute) a
|
|
230
|
+
// partially-written binary at the real cache path.
|
|
231
|
+
const tmpPath = path.join(CACHE_DIR, `.shellcheck.tmp-${process.pid}`);
|
|
232
|
+
fs.writeFileSync(tmpPath, entry);
|
|
233
|
+
fs.chmodSync(tmpPath, 0o755);
|
|
234
|
+
fs.renameSync(tmpPath, CACHED_BIN_PATH);
|
|
235
|
+
|
|
236
|
+
return CACHED_BIN_PATH;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
SHELLCHECK_VERSION,
|
|
241
|
+
CACHED_BIN_PATH,
|
|
242
|
+
MAX_REDIRECTS,
|
|
243
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
244
|
+
extractFileFromTar,
|
|
245
|
+
httpsGetFollowingRedirects,
|
|
246
|
+
resolveShellcheckBin,
|
|
247
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Suite classification by filename suffix (#4591 packaging fix — extracted
|
|
5
|
+
* from scripts/run-tests.cjs so a SHIPPED script, e.g.
|
|
6
|
+
* scripts/gen-platform-conformance-tier.cjs, can depend on it: run-tests.cjs
|
|
7
|
+
* itself does not ship (it is a repo-development-only tool), so a shipped
|
|
8
|
+
* file requiring it directly is MODULE_NOT_FOUND in a published install
|
|
9
|
+
* (tests/packaging-shipped-scripts-require-only-shipped.test.cjs, #2858).
|
|
10
|
+
* run-tests.cjs re-exports suiteOf from here unchanged for full backward
|
|
11
|
+
* compatibility — this is a pure relocation, not a behavior change.
|
|
12
|
+
*
|
|
13
|
+
* A file with no suite suffix (plain `foo.test.cjs`) belongs to the `unit`
|
|
14
|
+
* suite and returns `null` here; a suite-tagged file (`foo.install.test.cjs`)
|
|
15
|
+
* returns its marker string.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { basename } = require('node:path');
|
|
19
|
+
|
|
20
|
+
const MARKED_SUITES = ['integration', 'install', 'security', 'slow', 'qa'];
|
|
21
|
+
|
|
22
|
+
function suiteOf(filename) {
|
|
23
|
+
const name = basename(filename);
|
|
24
|
+
if (!name.endsWith('.test.cjs')) return null;
|
|
25
|
+
const base = name.slice(0, -'.test.cjs'.length);
|
|
26
|
+
const lastDot = base.lastIndexOf('.');
|
|
27
|
+
if (lastDot === -1) return null;
|
|
28
|
+
const marker = base.slice(lastDot + 1);
|
|
29
|
+
return MARKED_SUITES.includes(marker) ? marker : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { MARKED_SUITES, suiteOf };
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"tests/code-review-pipeline-regression.test.cjs :: source-text-is-the-product",
|
|
25
25
|
"tests/code-review.test.cjs :: source-text-is-the-product",
|
|
26
26
|
"tests/codebuddy-install.test.cjs :: source-text-is-the-product",
|
|
27
|
-
"tests/codex-config.test.cjs :: source-text-is-the-product",
|
|
28
27
|
"tests/command-contract.test.cjs :: source-text-is-the-product",
|
|
29
28
|
"tests/commands.test.cjs :: source-text-is-the-product",
|
|
30
29
|
"tests/config-field-docs.test.cjs :: docs-parity",
|
|
@@ -52,7 +51,6 @@
|
|
|
52
51
|
"tests/frontmatter-cli.test.cjs :: source-text-is-the-product",
|
|
53
52
|
"tests/gates-taxonomy.test.cjs :: source-text-is-the-product",
|
|
54
53
|
"tests/git-base-branch.test.cjs :: source-text-is-the-product",
|
|
55
|
-
"tests/gsd-check-update-worker-platform-gate.test.cjs :: structural-regression-guard",
|
|
56
54
|
"tests/gsd-researcher-app-aware.test.cjs :: source-text-is-the-product",
|
|
57
55
|
"tests/gsd-researcher-flow-diagram.test.cjs :: source-text-is-the-product",
|
|
58
56
|
"tests/gsd-settings-advanced.test.cjs :: source-text-is-the-product",
|
|
@@ -74,13 +72,11 @@
|
|
|
74
72
|
"tests/next-safety-gates.test.cjs :: source-text-is-the-product",
|
|
75
73
|
"tests/next-up-clear-order.test.cjs :: source-text-is-the-product",
|
|
76
74
|
"tests/no-hardcoded-home-gsd-tools.test.cjs :: source-text-is-the-product",
|
|
77
|
-
"tests/orphaned-hooks.test.cjs :: structural-regression-guard",
|
|
78
75
|
"tests/package-legitimacy-gate.test.cjs :: source-text-is-the-product",
|
|
79
76
|
"tests/parallel-dependent-plans.test.cjs :: source-text-is-the-product",
|
|
80
77
|
"tests/path-replacement.test.cjs :: source-text-is-the-product",
|
|
81
78
|
"tests/phase.test.cjs :: source-text-is-the-product",
|
|
82
79
|
"tests/phase6-capability-docs.test.cjs :: source-text-is-the-product",
|
|
83
|
-
"tests/phase6-capstone-conformance.test.cjs :: source-text-is-the-product",
|
|
84
80
|
"tests/phase6-planning-capabilities.test.cjs :: source-text-is-the-product",
|
|
85
81
|
"tests/plan-bounce.test.cjs :: source-text-is-the-product",
|
|
86
82
|
"tests/plan-phase-drift-guard.test.cjs :: source-text-is-the-product",
|
|
@@ -105,7 +101,6 @@
|
|
|
105
101
|
"tests/research-agent-profiles.test.cjs :: source-text-is-the-product",
|
|
106
102
|
"tests/roadmap.test.cjs :: source-text-is-the-product",
|
|
107
103
|
"tests/runtime-launcher-parity.test.cjs :: structural-regression-guard",
|
|
108
|
-
"tests/runtime-name-policy.test.cjs :: source-text-is-the-product",
|
|
109
104
|
"tests/scan-command.test.cjs :: source-text-is-the-product",
|
|
110
105
|
"tests/secret-scan-lint.security.test.cjs :: source-text-is-the-product",
|
|
111
106
|
"tests/secure-phase.test.cjs :: source-text-is-the-product",
|
|
@@ -113,7 +108,6 @@
|
|
|
113
108
|
"tests/security-scan.security.test.cjs :: source-text-is-the-product",
|
|
114
109
|
"tests/seed-scan-new-milestone.test.cjs :: source-text-is-the-product",
|
|
115
110
|
"tests/settings-integrations.test.cjs :: source-text-is-the-product",
|
|
116
|
-
"tests/settings-jsonc.test.cjs :: structural-regression-guard",
|
|
117
111
|
"tests/skill-frontmatter-contract.test.cjs :: source-text-is-the-product",
|
|
118
112
|
"tests/spawn-liveness-banner.test.cjs :: source-text-is-the-product",
|
|
119
113
|
"tests/state.test.cjs :: source-text-is-the-product",
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-allowed-tools-parity.cjs — catch `allowed-tools` frontmatter that
|
|
6
|
+
* declares `Bash` but omits `Grep` (#4394, follow-up to #3085).
|
|
7
|
+
*
|
|
8
|
+
* ## Why
|
|
9
|
+
*
|
|
10
|
+
* `gen-plugin-skills.cjs --check` already guarantees `skills/*` /SKILL.md`
|
|
11
|
+
* matches byte-for-byte what `commands/gsd/*.md` generates, so the two trees
|
|
12
|
+
* cannot silently diverge FROM EACH OTHER. Nothing guarded the shape #3085
|
|
13
|
+
* actually found: a command shipping `Bash` without `Grep` purely by
|
|
14
|
+
* omission, identical in both trees and therefore invisible to a parity
|
|
15
|
+
* check that only compares them to each other.
|
|
16
|
+
*
|
|
17
|
+
* That drift ran long enough for 29 of 71 skills to lack a tool most of
|
|
18
|
+
* their siblings already declared, and it surfaced through a manual audit
|
|
19
|
+
* rather than any gate. A command that can shell out but cannot Grep does
|
|
20
|
+
* not fail loudly — it quietly reaches for `Bash` + `grep` instead, which is
|
|
21
|
+
* slower, less structured, and (per `lint-portable-grep.cjs`) a portability
|
|
22
|
+
* hazard of its own on hosts without GNU grep.
|
|
23
|
+
*
|
|
24
|
+
* ## The rule
|
|
25
|
+
*
|
|
26
|
+
* A command whose `allowed-tools` includes `Bash` must also include `Grep`,
|
|
27
|
+
* unless it is on the exemption list below.
|
|
28
|
+
*
|
|
29
|
+
* Detection only. This lint never edits a command's `allowed-tools`.
|
|
30
|
+
*
|
|
31
|
+
* ## Why an exemption list rather than a heuristic
|
|
32
|
+
*
|
|
33
|
+
* The alternative — inferring from a command's body whether it "really"
|
|
34
|
+
* needs Grep — would make the rule's verdict depend on prose that changes
|
|
35
|
+
* constantly, and produce a lint whose failures nobody can predict. A short
|
|
36
|
+
* literal list keeps every exemption a reviewable one-line diff, and the
|
|
37
|
+
* staleness check below stops it becoming a dumping ground: an entry that no
|
|
38
|
+
* longer needs to be there fails just as loudly as a missing tool.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const fs = require('fs');
|
|
42
|
+
const path = require('path');
|
|
43
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
44
|
+
|
|
45
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
46
|
+
const DEFAULT_ROOT = 'commands/gsd';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Commands allowed to declare `Bash` without `Grep`.
|
|
50
|
+
*
|
|
51
|
+
* Keyed by command stem (the filename without `.md`), with the reason inline
|
|
52
|
+
* so changing the set is a one-line, reviewable diff.
|
|
53
|
+
*
|
|
54
|
+
* #4394 named eight candidates from the #3085 review: the six `gsd-ns-*`
|
|
55
|
+
* namespace dispatchers, `gsd-help`, and `gsd-surface`. Seven of those turn
|
|
56
|
+
* out not to need an entry at all — they do not declare `Bash` in the first
|
|
57
|
+
* place, so the rule never reaches them:
|
|
58
|
+
*
|
|
59
|
+
* ns-context/ns-ideate/ns-manage/ns-project/ns-review/ns-workflow Read + Skill
|
|
60
|
+
* help Read
|
|
61
|
+
*
|
|
62
|
+
* Listing them anyway would be seven pre-forgiven commands: the day one of
|
|
63
|
+
* them gained `Bash`, the omission it was granted an exemption for would
|
|
64
|
+
* pass silently. The staleness check in `scan()` below is what surfaced
|
|
65
|
+
* this, and it is why the list stays minimal — an exemption is a real
|
|
66
|
+
* suppression, not documentation.
|
|
67
|
+
*/
|
|
68
|
+
const EXEMPT = new Map([
|
|
69
|
+
// Toggles which skills are surfaced. Mutates the install tree through the
|
|
70
|
+
// installer's own seams rather than by searching project files, so `Bash`
|
|
71
|
+
// here is not standing in for a search it cannot perform.
|
|
72
|
+
['surface', 'mutates the install surface via installer seams, not by searching the project'],
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse an `allowed-tools` frontmatter value.
|
|
77
|
+
*
|
|
78
|
+
* Handles both shapes the corpus uses: a YAML block sequence
|
|
79
|
+
*
|
|
80
|
+
* allowed-tools:
|
|
81
|
+
* - Read
|
|
82
|
+
* - Bash
|
|
83
|
+
*
|
|
84
|
+
* and an inline scalar or flow sequence (`allowed-tools: Read, Bash` /
|
|
85
|
+
* `allowed-tools: [Read, Bash]`). Returns `null` when the key is absent,
|
|
86
|
+
* which is NOT a violation — a command with no `allowed-tools` at all
|
|
87
|
+
* declares no `Bash` either, so this rule has nothing to say about it.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately not a YAML parser: the frontmatter here is a fixed, shallow
|
|
90
|
+
* shape, and pulling in a parser to read one list would be a dependency
|
|
91
|
+
* bought for a single key.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} text full file contents
|
|
94
|
+
* @returns {string[] | null} declared tool names, or null when the key is absent
|
|
95
|
+
*/
|
|
96
|
+
function parseAllowedTools(text) {
|
|
97
|
+
const lines = String(text).split(/\r?\n/);
|
|
98
|
+
const keyIndex = lines.findIndex((l) => /^allowed-tools:/.test(l));
|
|
99
|
+
if (keyIndex === -1) return null;
|
|
100
|
+
|
|
101
|
+
const inline = lines[keyIndex].replace(/^allowed-tools:/, '').trim();
|
|
102
|
+
if (inline) {
|
|
103
|
+
return inline
|
|
104
|
+
.replace(/^\[/, '')
|
|
105
|
+
.replace(/\]$/, '')
|
|
106
|
+
.split(',')
|
|
107
|
+
.map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
|
|
108
|
+
.filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const tools = [];
|
|
112
|
+
for (let i = keyIndex + 1; i < lines.length; i += 1) {
|
|
113
|
+
const line = lines[i];
|
|
114
|
+
const item = line.match(/^\s*-\s+(.+?)\s*$/);
|
|
115
|
+
if (item) {
|
|
116
|
+
tools.push(item[1].replace(/^['"]|['"]$/g, ''));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// The first line that is neither a list item nor blank ends the block —
|
|
120
|
+
// the next frontmatter key, or the closing `---`.
|
|
121
|
+
if (line.trim() === '') continue;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
return tools;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List command stems under `dir`, sorted.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} dir absolute path to the commands directory
|
|
131
|
+
* @returns {{ stem: string, file: string }[]}
|
|
132
|
+
*/
|
|
133
|
+
function listCommands(dir) {
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
137
|
+
} catch {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return entries
|
|
141
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
142
|
+
.map((e) => ({ stem: e.name.replace(/\.md$/, ''), file: path.join(dir, e.name) }))
|
|
143
|
+
.sort((a, b) => a.stem.localeCompare(b.stem));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Evaluate the corpus.
|
|
148
|
+
*
|
|
149
|
+
* Reports two independent failure classes, because an exemption list that
|
|
150
|
+
* can only ever grow rots into a list of things nobody re-examined:
|
|
151
|
+
*
|
|
152
|
+
* - `violations`: declares Bash, omits Grep, not exempt.
|
|
153
|
+
* - `staleExemptions`: on the list but no longer needs to be — either the
|
|
154
|
+
* command is gone, or it no longer declares Bash without Grep. Removing
|
|
155
|
+
* the entry is then a one-line diff, and the next omission in that
|
|
156
|
+
* command is caught rather than silently pre-forgiven.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} [root] repo-relative commands directory
|
|
159
|
+
* @returns {{ scanned: number, violations: {stem: string, tools: string[]}[], staleExemptions: {stem: string, reason: string}[] }}
|
|
160
|
+
*/
|
|
161
|
+
function scan(root = DEFAULT_ROOT) {
|
|
162
|
+
const abs = path.isAbsolute(root) ? root : path.join(ROOT, root);
|
|
163
|
+
const commands = listCommands(abs);
|
|
164
|
+
const violations = [];
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
|
|
167
|
+
for (const { stem, file } of commands) {
|
|
168
|
+
const tools = parseAllowedTools(fs.readFileSync(file, 'utf8'));
|
|
169
|
+
if (!tools) continue;
|
|
170
|
+
if (!tools.includes('Bash') || tools.includes('Grep')) continue;
|
|
171
|
+
seen.add(stem);
|
|
172
|
+
if (EXEMPT.has(stem)) continue;
|
|
173
|
+
violations.push({ stem, tools });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const present = new Set(commands.map((c) => c.stem));
|
|
177
|
+
const staleExemptions = [];
|
|
178
|
+
for (const [stem, reason] of EXEMPT) {
|
|
179
|
+
if (!present.has(stem)) {
|
|
180
|
+
staleExemptions.push({ stem, reason: `no such command (${reason})` });
|
|
181
|
+
} else if (!seen.has(stem)) {
|
|
182
|
+
staleExemptions.push({ stem, reason: `no longer declares Bash without Grep (${reason})` });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { scanned: commands.length, violations, staleExemptions };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function main() {
|
|
190
|
+
const root = process.env.GSD_LINT_ALLOWED_TOOLS_ROOT || DEFAULT_ROOT;
|
|
191
|
+
const { scanned, violations, staleExemptions } = scan(root);
|
|
192
|
+
|
|
193
|
+
if (violations.length > 0 || staleExemptions.length > 0) {
|
|
194
|
+
const parts = [];
|
|
195
|
+
if (violations.length > 0) {
|
|
196
|
+
parts.push(
|
|
197
|
+
'lint-allowed-tools-parity: these commands declare `Bash` but not `Grep` (#4394).\n' +
|
|
198
|
+
'A command that can shell out but cannot Grep reaches for `Bash` + `grep` instead —\n' +
|
|
199
|
+
'slower, unstructured, and a portability hazard on hosts without GNU grep. Add `Grep`\n' +
|
|
200
|
+
'to the frontmatter, or add the command to EXEMPT in this script with a reason:\n' +
|
|
201
|
+
violations.map((v) => ` ${root}/${v.stem}.md [${v.tools.join(', ')}]`).join('\n'),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (staleExemptions.length > 0) {
|
|
205
|
+
parts.push(
|
|
206
|
+
'lint-allowed-tools-parity: these EXEMPT entries are stale — delete them so the next\n' +
|
|
207
|
+
'omission in those commands is caught rather than silently pre-forgiven:\n' +
|
|
208
|
+
staleExemptions.map((s) => ` ${s.stem}: ${s.reason}`).join('\n'),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
throw new ExitError(1, parts.join('\n\n'));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(
|
|
215
|
+
`ok lint-allowed-tools-parity: ${scanned} command(s) checked, ${EXEMPT.size} exemption(s) all still needed`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = { parseAllowedTools, scan, EXEMPT, DEFAULT_ROOT };
|
|
220
|
+
|
|
221
|
+
if (require.main === module) runMain(main);
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* `docs-guard-exempt:` marker for its specific reason.
|
|
32
32
|
*/
|
|
33
33
|
const DOCS_GUARD_EXEMPT_BASELINE = [
|
|
34
|
+
'adr-612-bracket-phase-counting.test.cjs',
|
|
34
35
|
'adr-parser.test.cjs',
|
|
35
36
|
'adr-parser.unit.test.cjs',
|
|
36
37
|
'agent-marker-documentation-guard.test.cjs',
|
|
@@ -45,6 +46,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
|
|
|
45
46
|
'codebuddy-upgrades.test.cjs',
|
|
46
47
|
'commands.test.cjs',
|
|
47
48
|
'commit-docs-bypass.test.cjs',
|
|
49
|
+
'compact-content-partition-guard.test.cjs',
|
|
48
50
|
'complexity-trigger.test.cjs',
|
|
49
51
|
'concurrency-safety.test.cjs',
|
|
50
52
|
'cursor-imperative-reference.test.cjs',
|
|
@@ -79,6 +81,7 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
|
|
|
79
81
|
'repo-invariants.test.cjs',
|
|
80
82
|
'require-issue-link-policy.test.cjs',
|
|
81
83
|
'reviewer-manifest-body.test.cjs',
|
|
84
|
+
'reviewer-step-dispatch.test.cjs',
|
|
82
85
|
'run-tests-harness.test.cjs',
|
|
83
86
|
'runtime-name-policy.test.cjs',
|
|
84
87
|
'security-prompt-injection.security.test.cjs',
|
|
@@ -103,6 +106,10 @@ const DOCS_GUARD_EXEMPT_BASELINE = [
|
|
|
103
106
|
* exact same scan.
|
|
104
107
|
*/
|
|
105
108
|
const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
|
|
109
|
+
// #2761: cites docs/adr/612-bracket-phase-id-convention.md in an explanatory
|
|
110
|
+
// comment describing ADR-612's Decision 1; the file never reads that (or any)
|
|
111
|
+
// docs/ file — every read call it makes targets a tmpdir .planning fixture.
|
|
112
|
+
'adr-612-bracket-phase-counting.test.cjs': ['docs/adr/612-bracket-phase-id-convention.md'],
|
|
106
113
|
'adr-parser.test.cjs': ['docs/adr/0001.md', 'docs/adr/0002.md', 'docs/adr/0010.md', 'docs/adr/NNNN.md'],
|
|
107
114
|
'adr-parser.unit.test.cjs': ['docs/adr/0001.md', 'docs/adr/0099.md', 'docs/adr/NNNN.md'],
|
|
108
115
|
'agent-marker-documentation-guard.test.cjs': ['docs/reference', 'docs/reference/workflow-fragments.md'],
|
|
@@ -118,8 +125,12 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
|
|
|
118
125
|
'docs/how-to/some-unrelated-guide.md', 'docs/how-to/x.md', 'docs/some-unrelated-file.md',
|
|
119
126
|
'docs/totally-unrelated.md',
|
|
120
127
|
],
|
|
128
|
+
// #4641: cites docs/adr/4641-windows-selector-consolidation.md in an
|
|
129
|
+
// explanatory comment describing why the retired `windows` scope must not
|
|
130
|
+
// be restored; the file never reads that (or any) docs/ file.
|
|
121
131
|
'ci-test-scope.test.cjs': [
|
|
122
|
-
'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md',
|
|
132
|
+
'docs/a.md', 'docs/adr', 'docs/adr/22-plan-drift-guard.md',
|
|
133
|
+
'docs/adr/4641-windows-selector-consolidation.md', 'docs/how-to/configure-model-profiles.md',
|
|
123
134
|
'docs/installer-migrations.md', 'docs/ja-JP', 'docs/ja-JP/USAGE.md', 'docs/usage.md', 'docs/x.md',
|
|
124
135
|
],
|
|
125
136
|
'cline-install.test.cjs': ['docs/guide.md'],
|
|
@@ -130,6 +141,10 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
|
|
|
130
141
|
'commit-docs-bypass.test.cjs': [
|
|
131
142
|
'docs/40-design.md', 'docs/CONFIGURATION.md', 'docs/readme.md', 'docs/tracked-var-mentioning',
|
|
132
143
|
],
|
|
144
|
+
// #4403: cites docs/PARTITION-RULES.md as a "see" pointer in the module docstring
|
|
145
|
+
// describing the operational spec this guard implements; the file never reads that
|
|
146
|
+
// (or any) docs/ file.
|
|
147
|
+
'compact-content-partition-guard.test.cjs': ['docs/PARTITION-RULES.md'],
|
|
133
148
|
'complexity-trigger.test.cjs': ['docs/readme.md'],
|
|
134
149
|
// #3884: cites docs/CLI-TOOLS.md:736 in an explanatory comment describing
|
|
135
150
|
// the real `frontmatter get <file> [--field key]` CLI shape; the file
|
|
@@ -182,11 +197,18 @@ const DOCS_GUARD_EXEMPT_DOCS_PATHS = {
|
|
|
182
197
|
'repo-invariants.test.cjs': ['docs/FEATURES.md', 'docs/workflows/README'],
|
|
183
198
|
'require-issue-link-policy.test.cjs': ['docs/-prefixed', 'docs/CONFIGURATION.md', 'docs/a.md', 'docs/b.md', 'docs/guide.md'],
|
|
184
199
|
'reviewer-manifest-body.test.cjs': ['docs/how-to/ship-a-reviewer-lane.md'],
|
|
200
|
+
// #4209: 'docs/spec.md' is a synthetic, never-read fake path proving dispatchReviewerLanes
|
|
201
|
+
// has no code-review-specific special-casing; 'docs/adr/456-test-rigor-architecture.md' is a
|
|
202
|
+
// prose citation in a code comment justifying the fast-check property tests below (#4209
|
|
203
|
+
// review). Neither is a real filesystem read — the file never reads any docs/ file.
|
|
204
|
+
'reviewer-step-dispatch.test.cjs': ['docs/adr/456-test-rigor-architecture.md', 'docs/spec.md'],
|
|
185
205
|
'run-tests-harness.test.cjs': ['docs/TESTING-SUITES.md'],
|
|
186
206
|
'runtime-name-policy.test.cjs': ['docs/customize/skills'],
|
|
187
207
|
'security-prompt-injection.security.test.cjs': ['docs/notes.md'],
|
|
188
208
|
'shipped-reference-cites.test.cjs': [],
|
|
189
|
-
|
|
209
|
+
// #4186: the record-session usage-contract test cites the documented
|
|
210
|
+
// signature in docs/CLI-TOOLS.md in its explanatory comment.
|
|
211
|
+
'state.test.cjs': ['docs/CLI-TOOLS.md', 'docs/CONFIGURATION.md', 'docs/reference/state-md.md'],
|
|
190
212
|
'worktree-safety.test.cjs': ['docs/SUMMARY.md'],
|
|
191
213
|
};
|
|
192
214
|
|