@opengsd/gsd-core 1.12.0 → 1.13.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-executor.md +63 -35
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +239 -67
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +195 -50
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-registry.cjs +514 -114
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +120 -4
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +85 -2
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +337 -13
- package/gsd-core/bin/lib/config-loader.cjs +3 -0
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- 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/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 +75 -22
- 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 +132 -15
- package/gsd-core/bin/lib/install-engine.cjs +184 -12
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +22 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/milestone.cjs +22 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-id.cjs +251 -9
- package/gsd-core/bin/lib/phase.cjs +774 -35
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
- package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
- 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/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
- package/gsd-core/bin/lib/roadmap.cjs +187 -58
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
- 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 +30 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
- package/gsd-core/bin/lib/state-transition.cjs +333 -44
- package/gsd-core/bin/lib/state.cjs +684 -125
- package/gsd-core/bin/lib/surface.cjs +23 -8
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1419 -515
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +273 -12
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +346 -16
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +3 -3
- 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/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 +2 -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 +14 -9
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/templates/phase-prompt.md +4 -0
- 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 +1 -1
- 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 +2 -0
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
- package/gsd-core/workflows/code-review-fix.md +2 -0
- package/gsd-core/workflows/code-review.md +73 -31
- package/gsd-core/workflows/complete-milestone.md +13 -4
- 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.md +1 -1
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
- 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/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 +32 -14
- package/gsd-core/workflows/execute-plan.md +8 -8
- 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.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- 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 +2 -0
- 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 +1 -1
- package/gsd-core/workflows/new-project.md +5 -3
- 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/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 +130 -12
- 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 +11 -3
- 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 +13 -3
- package/gsd-core/workflows/reapply-patches.md +2 -0
- 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 +1 -1
- 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 +41 -38
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +49 -3
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +283 -12
- 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 +1079 -0
- package/hooks/dist/gsd-statusline.js +7 -3
- package/hooks/dist/gsd-validate-commit.sh +444 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +283 -12
- 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 +1079 -0
- package/hooks/gsd-statusline.js +7 -3
- package/hooks/gsd-validate-commit.sh +444 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -5
- package/scripts/build-hooks.js +11 -4
- package/scripts/ci-test-scope.cjs +7 -0
- package/scripts/docs-guard-registry.cjs +10 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/lib/shellcheck-fetch.cjs +247 -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-docs-guard-registration.exempt-baseline.cjs +5 -0
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-test-file-count.allowlist.json +3 -1
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* lint-portable-grep.cjs — ban GNU-only `grep -P`/`--perl-regexp` in gsd
|
|
6
|
+
* workflow / agent / reference / command markdown (#4112 macOS regression).
|
|
7
|
+
*
|
|
8
|
+
* ## Why
|
|
9
|
+
*
|
|
10
|
+
* `-P`/`--perl-regexp` selects GNU grep's PCRE engine. Stock macOS ships BSD
|
|
11
|
+
* grep, which does not implement `-P` at all — the call errors out ("grep:
|
|
12
|
+
* invalid option -- P" or "unknown option") on that host, and a pipeline like
|
|
13
|
+
* `... | grep -oP '...' || true` swallows that error and silently resolves to
|
|
14
|
+
* an empty string instead of failing loudly.
|
|
15
|
+
*
|
|
16
|
+
* This is exactly what happened in `gsd-core/workflows/pause-work.md`'s
|
|
17
|
+
* Context Detection step (#4112, merged as #4140): the fix for the `$((`
|
|
18
|
+
* shell-syntax bug left `grep -oP 'phases/\K[^/]+'` in place. That construct
|
|
19
|
+
* had never been exercised on macOS before (the syntax error masked it), so
|
|
20
|
+
* fixing the syntax bug newly exposed a second, pre-existing macOS-only
|
|
21
|
+
* defect — phase/spike/sketch detection silently resolving to "" on macOS —
|
|
22
|
+
* which the PR's own new regression test then caught, but only in the
|
|
23
|
+
* post-merge `full test (macos-latest, ...)` matrix. That lane runs on push
|
|
24
|
+
* to the base branch, not on the pull_request event, so nothing pre-merge
|
|
25
|
+
* (gsd-test's Linux-only benches included) could have caught it before `next`
|
|
26
|
+
* went red.
|
|
27
|
+
*
|
|
28
|
+
* This is the same class of defect `lint-portable-timeout.cjs` (#2351) exists
|
|
29
|
+
* for: a GNU-coreutils assumption baked into workflow markdown that every
|
|
30
|
+
* pre-merge gate is blind to because none of them run on macOS. The fix here
|
|
31
|
+
* is the same shape — a static ratchet that catches the assumption in the
|
|
32
|
+
* markdown itself, before a shell ever executes it on the platform that lacks
|
|
33
|
+
* the flag.
|
|
34
|
+
*
|
|
35
|
+
* ## What PASSES
|
|
36
|
+
*
|
|
37
|
+
* - `grep -o '...'`, `grep -E '...'`, `egrep`/`fgrep` without `-P`.
|
|
38
|
+
* - `-P` appearing as part of an unrelated long option (`--path`, `--perl`) —
|
|
39
|
+
* only a `-P`/short-cluster-containing-`P` token or the literal
|
|
40
|
+
* `--perl-regexp` counts.
|
|
41
|
+
* - Prose that merely mentions "grep -P" outside of a fenced command context
|
|
42
|
+
* is still flagged (this lint does not parse fences) — the roots below are
|
|
43
|
+
* restricted to directive markdown where a `grep -P` mention is always
|
|
44
|
+
* either a real invocation or a specimen to fix, never neutral prose.
|
|
45
|
+
*
|
|
46
|
+
* ## What FAILS
|
|
47
|
+
*
|
|
48
|
+
* A `grep`/`egrep`/`fgrep` invocation whose flag cluster includes `P`
|
|
49
|
+
* (`-P`, `-oP`, `-Po`, `-iP`, ...) or the long form `--perl-regexp`.
|
|
50
|
+
*
|
|
51
|
+
* ## Portable replacements
|
|
52
|
+
*
|
|
53
|
+
* - Extracting a path component: `basename "$(dirname "$path")")` instead of
|
|
54
|
+
* `grep -oP 'seam/\K[^/]+'`.
|
|
55
|
+
* - Extracting a token after a flag: `sed -E 's#.*--flag[[:space:]]+([^[:space:]]+).*#\1#'`
|
|
56
|
+
* instead of `grep -oP '(?<=--flag )\S+'` — POSIX ERE (`-E`) has no
|
|
57
|
+
* lookbehind, so capture the group and let `sed` emit just it. `-E` (not
|
|
58
|
+
* GNU-only `-r`) is supported by both BSD and GNU `sed`. (A `/`-delimited
|
|
59
|
+
* sed command ending in a star-quantifier would place a star immediately
|
|
60
|
+
* before a slash, closing this very JSDoc block comment early — hence the
|
|
61
|
+
* `#`-delimited form used above.)
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
const fs = require('fs');
|
|
65
|
+
const path = require('path');
|
|
66
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
67
|
+
|
|
68
|
+
const ROOT = path.join(__dirname, '..');
|
|
69
|
+
|
|
70
|
+
// Surfaces whose markdown carries agent-executed bash. Mirrors
|
|
71
|
+
// lint-portable-timeout.cjs's roots — the same files that can ship shell
|
|
72
|
+
// snippets which a bash/zsh host on macOS actually executes.
|
|
73
|
+
const DEFAULT_ROOTS = ['gsd-core/workflows', 'gsd-core/references', 'agents', 'commands'];
|
|
74
|
+
|
|
75
|
+
// A `grep`/`egrep`/`fgrep` token, anchored to a command position (line start,
|
|
76
|
+
// right after `| & ; ( \` {`, or right after a shell keyword that opens a new
|
|
77
|
+
// command — `then`/`do`/`else`/`elif`) so prose mentioning "grep -P" in
|
|
78
|
+
// running text is still caught in these directive-markdown roots (see file
|
|
79
|
+
// header) without also matching an unrelated word ending in "grep". The
|
|
80
|
+
// keyword alternative is `\b`-bounded on both sides, so a bare mention of the
|
|
81
|
+
// word "then"/"do"/etc. with no following grep never matches on its own.
|
|
82
|
+
const GREP_INVOCATION_RE = /(?:^|[|&;(`{]|\b(?:then|do|else|elif)\b)[ \t]*(?:e|f)?grep\b/g;
|
|
83
|
+
|
|
84
|
+
// A `-P` short-flag cluster (e.g. `-P`, `-oP`, `-Po`, `-iP`) or the long form
|
|
85
|
+
// `--perl-regexp`, as a standalone token.
|
|
86
|
+
const PERL_FLAG_RE = /(?:^|\s)-[a-zA-Z]*P[a-zA-Z]*(?=\s|$)|--perl-regexp\b/;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Locate `grep -P`/`--perl-regexp` invocations in a block of text.
|
|
90
|
+
*
|
|
91
|
+
* Pure (no I/O): callers pass file contents; the caller reads files. For each
|
|
92
|
+
* `grep`-family invocation on a line, inspects only the segment from that
|
|
93
|
+
* invocation to the next `|`/`;`/end-of-line, so a `-P`-bearing token in a
|
|
94
|
+
* later, unrelated command on the same line is never mistaken for a grep flag.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} text file contents
|
|
97
|
+
* @returns {{ line: number, snippet: string }[]} findings (empty array = clean)
|
|
98
|
+
*/
|
|
99
|
+
function findPerlGrepInvocations(text) {
|
|
100
|
+
const findings = [];
|
|
101
|
+
const lines = String(text).split(/\r?\n/);
|
|
102
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
103
|
+
const line = lines[i];
|
|
104
|
+
GREP_INVOCATION_RE.lastIndex = 0;
|
|
105
|
+
let match;
|
|
106
|
+
while ((match = GREP_INVOCATION_RE.exec(line)) !== null) {
|
|
107
|
+
const startAt = match.index + match[0].length;
|
|
108
|
+
const rest = line.slice(startAt);
|
|
109
|
+
const nextPipe = rest.search(/[|;`]/);
|
|
110
|
+
const segment = nextPipe === -1 ? rest : rest.slice(0, nextPipe);
|
|
111
|
+
if (PERL_FLAG_RE.test(segment)) {
|
|
112
|
+
findings.push({ line: i + 1, snippet: line.trim() });
|
|
113
|
+
break; // one finding per line is enough context to fix it
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return findings;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function walkMarkdown(dir) {
|
|
121
|
+
const out = [];
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
125
|
+
} catch {
|
|
126
|
+
return out; // a missing root is not an error — some surfaces are optional
|
|
127
|
+
}
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const full = path.join(dir, entry.name);
|
|
130
|
+
if (entry.isDirectory()) out.push(...walkMarkdown(full));
|
|
131
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) out.push(full);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Scan the given roots (repo-relative) for `grep -P`/`--perl-regexp` invocations.
|
|
138
|
+
* @param {string[]} roots
|
|
139
|
+
* @returns {{ file: string, line: number, snippet: string }[]}
|
|
140
|
+
*/
|
|
141
|
+
function scan(roots = DEFAULT_ROOTS) {
|
|
142
|
+
const offenders = [];
|
|
143
|
+
for (const rel of roots) {
|
|
144
|
+
const abs = path.isAbsolute(rel) ? rel : path.join(ROOT, rel);
|
|
145
|
+
for (const file of walkMarkdown(abs)) {
|
|
146
|
+
const findings = findPerlGrepInvocations(fs.readFileSync(file, 'utf8'));
|
|
147
|
+
for (const f of findings) {
|
|
148
|
+
offenders.push({ file: path.relative(ROOT, file), line: f.line, snippet: f.snippet });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return offenders;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function main() {
|
|
156
|
+
const rootsEnv = process.env.GSD_LINT_PORTABLE_GREP_ROOTS;
|
|
157
|
+
const roots = rootsEnv ? rootsEnv.split(path.delimiter).filter(Boolean) : DEFAULT_ROOTS;
|
|
158
|
+
const offenders = scan(roots);
|
|
159
|
+
if (offenders.length > 0) {
|
|
160
|
+
const detail = offenders.map((o) => ` ${o.file}:${o.line} ${o.snippet}`).join('\n');
|
|
161
|
+
throw new ExitError(
|
|
162
|
+
1,
|
|
163
|
+
'lint-portable-grep: `grep -P`/`--perl-regexp` is not portable — stock macOS ships\n' +
|
|
164
|
+
'BSD grep, which has no `-P`, so the call errors and a `|| true`/`$(...)` fallback\n' +
|
|
165
|
+
'silently resolves to empty output instead of failing loudly (#4112). Use\n' +
|
|
166
|
+
'`basename "$(dirname "$path")")` for path-component extraction, or\n' +
|
|
167
|
+
'`sed -E \'s/.../\\1/\'` (POSIX ERE, no lookbehind needed) for token extraction:\n' +
|
|
168
|
+
detail,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
console.log(`ok lint-portable-grep: no GNU-only grep -P invocations in ${roots.length} root(s)`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { findPerlGrepInvocations, scan, DEFAULT_ROOTS };
|
|
175
|
+
|
|
176
|
+
if (require.main === module) runMain(main);
|