6 Commits

  • docs+chore: add README Security section; fix lint regressions on main
    - README: add a visible ## Security section (official sources, vuln reporting via SECURITY.md, GateGuard/IOC/AgentShield guardrails, security guide); make stats line a plain paragraph to clear MD028
    - eslint: empty catch comment in run-with-flags.js; drop unneeded escape in github-coordination/parsing.js; remove unused execFileSync import in its test (#2236 follow-ups)
    - markdownlint: wrap bare URLs in rules/vue/*.md (#2250 follow-up)
    
    npm run lint green; full suite 2836/2836.
  • fix: guard upsertCoordinationWorkItem behind dryRun check in applySync
    The store write was unconditional, persisting work items even during dry
    runs. Move it inside the !dryRun block alongside editIssue and initialize
    snapshot to null beforehand so results.push still receives snapshot: null
    for dry runs.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • fix: enforce policy.review.required gate in applyPublish
    applyPublish was forcing review='approved' for any state that wasn't
    'changes-requested', bypassing policy.review.required entirely. Add a
    guard that throws before buildIssueStateFromAction when review approval
    is required but not yet granted.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • fix: address code-review findings in github-coordination actions
    - Remove circular validation-status check in applyValidate that prevented
      fresh claims (validation='pending') from ever reaching 'passed'
    - Add staleCoordinationLabels helper to compute coordination:* labels to
      remove on state transitions; replaces hardcoded removeLabels:[] across
      all six editIssue call sites
    - Fix duplicate label writes in applySync: syncIssueLabels already calls
      editIssue for labels, so the follow-up editIssue now only updates body
    - Skip acquireLock finding: store.acquireLock does not exist; comment
      updated to explain why the fix was not applied
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • fix: address second round of code-review findings
    actions.js:
    - Add assertValidRepo/assertValidIssueNumber guards at the top of all
      action handlers (applyClaim, applySync, applyValidate, applyPublish,
      applyReview, applyDecompose, applyUnblock) for fast-fail validation
    - applyValidate: fix status transition — set 'validated' unconditionally
      when ok=true instead of preserving 'blocked' (was inconsistent with
      projectState becoming 'ready')
    
    gh-api.js:
    - runGh: preserve GITHUB_TOKEN by default; only delete when caller
      explicitly sets options.stripGithubToken=true (was deleting by
      default, breaking CI)
    
    parsing.js:
    - extractCoordinationState: throw SyntaxError on malformed JSON instead
      of silently returning null — lets callers distinguish bad JSON from
      absent marker
    - normalizeBodyForComparison: fix regex to match JSON-quoted form
      "lastSyncAt": ... instead of bare lastSyncAt: ...
    
    policy.js:
    - loadPolicy: validate that parsed JSON is a plain object before
      spreading; coerce nested fields (labels, review, validation,
      branchModel, project, fieldNames) to objects before merging
    
    state.js:
    - assertIssueClaimable: block re-claim on status alone (not status AND
      owner) to prevent {status:'claimed', owner:null} bypass; use
      state.owner || 'unknown' in error message
    - getCoordinationState: catch SyntaxError from extractCoordinationState,
      log warning to stderr, fall back to default state
    
    tests/lib:
    - Update malformed-JSON test to expect SyntaxError throw instead of null
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
  • refactor: apply code-review findings to github-native coordination
    scripts/github-coordination.js:
    - parseArgs: replace 13-entry if/else chain with BOOL_FLAGS/VALUE_FLAGS
      lookup maps; shrinks from 119 to ~45 lines
    - Extract dispatchCommand(options, ctx) and formatOutput(payload, options)
      from main(); main() shrinks to ~20 lines
    
    scripts/lib/github-coordination.js:
    - Split 1041-line monolith into 6 focused sub-modules under
      scripts/lib/github-coordination/ (policy, parsing, gh-api, state,
      actions, store); index becomes a thin re-export (~55 lines)
    - Document ECC_GH_SHIM trust boundary in runGh() (gh-api.js)
    - Document applyClaim() read→check→write race condition (actions.js)
    
    tests/lib/github-coordination.test.js:
    - Refactor runTests() to data-driven DESCRIPTORS array + runGroup()
      helper; runTests() shrinks to ~10 lines
    - Add 5 new edge-case tests: normalizeRepo('') and normalizeRepo('   ')
      throw, desiredLabelsForState for blocked/ready statuses, and
      buildIssueStateFromAction for validate action (15 → 20 tests)
    
    tests/scripts/github-coordination.test.js:
    - Replace console.log in test runner with process.stdout.write
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>