Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions openspec/specs/append-vs-modify-discipline/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Defines the action-scoped modify discipline that governs every IDD plugin action that modifies existing artifacts (issue bodies, comments, files, state fields). Every modify-action SHALL declare exactly one scope category from a canonical 7-category enumeration (`state-field-update`, `bounded-section-replace`, `audit-block-append`, `inline-replace-before-publish`, `verbatim-preserve`, `append-only`, `free-rewrite`). Undeclared modify-actions SHALL be refused at runtime regardless of actor identity (default-refuse), and `/idd-edit --replace` SHALL require an explicit `--scope` or `--section` flag. The discipline also defines authoritative-source resolution for checklist gates (Implementation Complete > Current Status > top-level Todo/Tasks/Checklist) with a legacy-scan fallback for pre-discipline issues, plus retroactive category labeling of existing IDD skills. Sourced from change `add-action-scoped-modify-discipline`.

**Implementation status (v2.74.0)**: discipline declared at spec level. Runtime enforcement landed for `bounded-section-replace` (`/idd-update` REPLACE), `state-field-update` (`/idd-clarify` status mutation, `spectra task done`), `audit-block-append` (5 IC_R011 PATCH sites), `inline-replace-before-publish` (`/idd-close` Step 3.5), Path C authoritative-source gate logic across 4 gate sites. **`/idd-edit` runtime enforcement of `--scope` / `--section` (Requirement 4) + user-authored verbatim-preserve guard (Requirement 5) deferred to follow-up issue [#154](https://github.com/PsychQuant/issue-driven-development/issues/154)** after 3 verify iterations (R1/R2/R3 on PR #153) showed bash-incremental impl introduces new bugs each pass. AI/user invocation SHALL apply Requirements 4-5 as discipline (read spec + apply manually) until #154 ships runtime gate. Requirements 4-5 SHALL be re-verified for runtime conformance when #154 closes.
**Implementation status (v2.75.0)**: discipline declared at spec level + runtime enforcement landed for ALL categories. Runtime gates for `bounded-section-replace` (`/idd-update` REPLACE + `/idd-edit --replace --scope`/`--section` per R4), `state-field-update` (`/idd-clarify` status mutation, `spectra task done`), `audit-block-append` (5 IC_R011 PATCH sites), `inline-replace-before-publish` (`/idd-close` Step 3.5), `verbatim-preserve` (`/idd-edit` R5 author-check + override pathway), Path C authoritative-source gate logic across 4 gate sites. **`/idd-edit` Requirements 4 + 5 runtime gates landed via [#154](https://github.com/PsychQuant/issue-driven-development/issues/154)** through extracted helper `plugins/issue-driven-dev/scripts/idd-edit-helper.sh` (parse-args / validate-target / section-replace subcommands) with 23 unit-test fixtures at `plugins/issue-driven-dev/scripts/tests/idd-edit/` — closes R1/R2/R3 bash-inline parser bug class observed on PR #153.

## Requirements

Expand Down Expand Up @@ -113,7 +113,7 @@ code:
---
### Requirement: /idd-edit --replace SHALL require scope flag

**Implementation status (v2.74.0)**: discipline declared; runtime enforcement deferred to [#154](https://github.com/PsychQuant/issue-driven-development/issues/154). AI / user invocations SHALL apply this requirement as discipline (include `--scope` / `--section` in invocation patterns) until #154 ships bash runtime gate. Scenarios below describe intended runtime behavior post-#154 — they are NOT currently enforced by `idd-edit/SKILL.md` (which is at pre-#150 baseline). Re-verify for runtime conformance when #154 closes.
**Implementation status (v2.75.0)**: landed via [#154](https://github.com/PsychQuant/issue-driven-development/issues/154). `/idd-edit` Step 1 invokes `plugins/issue-driven-dev/scripts/idd-edit-helper.sh parse-args` which enforces R4 gate (exit code 3 with actionable error message). Tested by fixture `10-replace-no-scope`. SKILL.md `## Runtime gates` section documents the gate matrix.

The `/idd-edit` skill SHALL require explicit scope when invoked with `--replace` mode. The skill SHALL accept either `--scope whole-comment` (explicit acknowledgment of full-comment overwrite) OR `--section <heading-within-comment>` (limit replacement to a named subsection within the comment). Invocations of `--replace` without either flag SHALL be refused. The `--append` and `--prepend-note` modes SHALL NOT require scope flags because their scope is inherently bounded (trailing block / leading errata marker respectively).

Expand Down Expand Up @@ -159,7 +159,7 @@ code:
---
### Requirement: /idd-edit SHALL refuse modifications to user-authored comments

**Implementation status (v2.74.0)**: discipline declared; runtime enforcement deferred to [#154](https://github.com/PsychQuant/issue-driven-development/issues/154). AI / user invocations SHALL apply this requirement as discipline (include `--override-user-content --reason="..."` when intentionally editing non-OWNER non-bot comments) until #154 ships bash runtime gate + `/idd-comment` errata flow integration. Scenarios below describe intended runtime behavior post-#154 — NOT currently enforced by `idd-edit/SKILL.md` (pre-#150 baseline). Re-verify for runtime conformance when #154 closes.
**Implementation status (v2.75.0)**: landed via [#154](https://github.com/PsychQuant/issue-driven-development/issues/154). `/idd-edit` Step 1.5 invokes `plugins/issue-driven-dev/scripts/idd-edit-helper.sh validate-target` which enforces R5 gate (exit code 4 with actionable error message + bot allowlist via `*[bot]` glob + OWNER passthrough + override pathway). `/idd-comment` errata Template auto-call handles exit 4 with helpful manual-invocation hint (D2 decision: refuse-with-message > auto-override, aligns with IC_R007 user-authored-intent spirit). Tested by fixtures `11-non-owner-no-override` (default OVERRIDE=false) + `12-non-owner-with-override` (override-pair guard) + `13-errata-refuse-message` (override+reason succeeds).

The `/idd-edit` skill SHALL refuse modifications targeting comments authored by users whose `author_association` is not `OWNER` and who are not in the known-bot allowlist (`github-actions[bot]`, `dependabot[bot]`, and other repo-configured bots). This protection SHALL apply to all three modes (`--append`, `--prepend-note`, `--replace`). Invocations targeting user-authored comments SHALL be refused unless the caller provides `--override-user-content` flag together with `--reason="<rationale>"` documenting the explicit decision to modify user content. This requirement is the comment-level instance of the `verbatim-preserve` category, aligned with IC_R007 (verbatim source preservation).

Expand Down
4 changes: 2 additions & 2 deletions plugins/issue-driven-dev/.claude-plugin/plugin.json

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions plugins/issue-driven-dev/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,92 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.75.0] - 2026-05-25

### Added

- **`plugins/issue-driven-dev/scripts/idd-edit-helper.sh`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): NEW extracted runtime helper with 3 subcommands. `parse-args` does positional shift over 7 space-form flags with explicit missing-value guards (`[ -z "${2:-}" ]` + `[[ "$2" == --* ]]`) + eq-form support + body-file readability pre-check + emits eval-friendly KEY=quoted-value via printf %q (preserves newlines). `validate-target` enforces R5 via single gh API call (author_association + login fetch) with `*[bot]` allowlist + OWNER passthrough + override pathway. `section-replace` uses awk-getline pattern (BSD/gnu safe — closes R3 C3 BSD awk -v multi-line newline reject). 5 distinct exit codes (0/1/2/3/4/5).

- **`plugins/issue-driven-dev/scripts/tests/idd-edit/{test.sh,fixtures/01-19/}`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): NEW test infrastructure per `.claude/scripts/tests/spectra-archive-post-ic/` precedent. **19 fixtures** cover R1/R2/R3 regression set + Round 1 verify finding additions: parser robustness (eq form / space form / missing value / next-flag-eats-value / unreadable file / multi-line body / single-line body / subsections / no-closing-heading), R4 gate (no scope/section refuse + invalid scope value refuse), R5 override pair guard (default=false / requires --reason / succeeds with both), validate-target via IDD_EDIT_HELPER_GH_MOCK env var (OWNER passthrough / bot allowlist / non-OWNER refuse / non-OWNER with override), section-replace CRLF input handling. All 19 GREEN.

- **`emit-audit-marker` helper subcommand** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154) Round 1 fix C3+M1): centralizes HTML-comment-escape so attacker-controlled `$REASON` / `$SECTION_FLAG` cannot forge audit trail. Strips `-->` tokens (→ `-\>`), newlines, control chars. Used by all 3 modes for both `edit` markers and `override` markers. Closes R5 forensic gap where override marker only emitted in `--replace` branch.

- **`IDD_EDIT_HELPER_GH_MOCK` env var** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154) Round 1 fix H3): test hook for `validate-target` subcommand. When set, reads mock JSON `{"login": ..., "assoc": ...}` instead of calling `gh api`. Unblocks unit-test coverage of bot allowlist / OWNER passthrough / refuse paths without live API. Used by fixtures 15-18.

- **`comment_id` numeric validation** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154) Round 1 fix C2): SKILL.md Step 1 enforces `[[ "$COMMENT_ID" =~ ^[0-9]+$ ]]` before substitution into `gh api repos/.../comments/$comment_id` URL or `/tmp/idd-edit-repl-$COMMENT_ID.md` filename. Closes Round 1 verify finding C2 path traversal via Step 0.7 PR↔issue correspondence input.

- **`--body-file` path-traversal documentation** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154) Round 1 fix H5): SKILL.md 鐵律 section explicitly notes `--body-file=/etc/passwd` would be read + pushed to public PATCH; user/caller validates path; restrict-to-subtree is future enhancement.

- **`/idd-edit` R4 runtime gate** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): `--replace` without `--scope whole-comment` OR `--section <heading>` → refuse with exit 3 + actionable error message + spec Requirement 4 citation. Closes #150 Requirement 4 runtime enforcement deferral. Tested by fixture 10.

- **`/idd-edit` R5 runtime gate** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): non-OWNER non-bot comment without `--override-user-content --reason="..."` → refuse with exit 4 + helpful manual-invocation message + spec Requirement 5 citation. Bot allowlist matches `*[bot]` glob (github-actions[bot], dependabot[bot], etc.). Override appends `<!-- idd:edit override-user-content date=... reason="..." -->` audit marker to comment body. Closes #150 Requirement 5 runtime enforcement deferral.

- **`/idd-comment` errata flow R5 integration** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): errata Template SPECIAL BEHAVIOUR sets `IDD_CALLER=idd-comment-errata` env var when auto-calling `/idd-edit --prepend-note`. On R5 refuse (exit 4), displays helpful message with exact manual invocation pattern (`--override-user-content --reason='errata clarification per IDD discipline'`). Per D2 decision (refuse-with-message > auto-override, aligns with IC_R007 user-authored-intent spirit).

### Refactored

- **`/idd-edit` SKILL.md Step 1** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): replaced inline `if [[ "$ARG" == comment:* ]]; then ... elif [[ "$ARG" == \#* ]]; then ...` pseudocode with `bash $CLAUDE_PLUGIN_ROOT/scripts/idd-edit-helper.sh parse-args "$@"` invocation + eval-import. AI no longer generates parser bash inline — uses tested helper. Closes R1/R2/R3 root cause: AI bash generation inconsistency.

- **`/idd-edit` SKILL.md Step 4 `--replace` mode** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): replaced inline `awk -v new_content="$BODY"` (R3 C3 BSD-broken) with `bash ... section-replace` invocation that uses awk-getline reading from file. Whole-comment path stays inline (no awk needed). Override audit marker appended when applicable.

- **`/idd-edit` SKILL.md frontmatter `argument-hint`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): updated to show new flag syntax — mode flags grouped with R4-required `--scope`/`--section` for `--replace` + new `--body-file`/`--reason`/`--override-user-content` flags.

- **`/idd-edit` SKILL.md `## 使用範例`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): 3 existing examples updated with new flag syntax; 2 new examples added (section-replace + errata override flow).

- **`/idd-edit` SKILL.md `## Batch mode`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): added per-target R4/R5 enforcement note + cross-link to [#158](https://github.com/PsychQuant/issue-driven-development/issues/158) for full batch+R5 semantics design.

- **`openspec/specs/append-vs-modify-discipline/spec.md`** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): Purpose / Requirement 4 / Requirement 5 preambles updated from "deferred to #154" to "landed via #154" + specific helper subcommand + tested-by fixture references. Removed "Re-verify for runtime conformance when #154 closes" cleanup tags.

### BREAKING (runtime)

- **`/idd-edit --replace` without `--scope`/`--section` now refuses** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): existing AI invocations of `/idd-edit comment:NNN --replace --body "..."` will get exit 3 + R4 message. Must add `--scope whole-comment` (full overwrite) or `--section "### Heading"` (named subsection). Discipline already declared in v2.73.0/v2.74.0 spec; v2.75.0 makes it runtime-enforced.

- **`/idd-edit` modifying non-OWNER non-bot comment now refuses** ([#154](https://github.com/PsychQuant/issue-driven-development/issues/154)): existing AI invocations targeting external collaborator / non-OWNER comments will get exit 4 + R5 message. Must add `--override-user-content --reason="..."` for explicit consent. `/idd-comment` errata flow auto-call gracefully handles this with helpful message.

### Round 1 verify fixes (all 11 pre-merge findings addressed)

- **C1**: Helper + test fixtures moved from `.claude/scripts/` to `plugins/issue-driven-dev/scripts/` — matches `process-attachments.sh` / `manifest-append.sh` precedent. `$CLAUDE_PLUGIN_ROOT/scripts/idd-edit-helper.sh` now resolves correctly in production install.
- **C2**: `comment_id` numeric validation (described above under Added).
- **C3+M1**: `emit-audit-marker` helper subcommand (described above under Added) — centralized HTML-escape, marker emission in all 3 modes.
- **B1**: SKILL.md Step 4 `--append` mode uses `$BODY_INPUT` (helper-exported) instead of undefined `$APPEND_BODY`.
- **B2**: SKILL.md Step 6 PATCH + Step 7 verify use `$REPO` (helper-exported, currently respects `--repo` flag only; walk-up config resolution deferred to future enhancement) instead of undefined `$GITHUB_REPO`.
- **H1**: helper `parse-args` R4 gate validates `--scope` value MUST be `whole-comment` (no other valid scopes today). Invalid value → exit 3 with hint.
- **H2**: SKILL.md Step 1 splits `parse-args` stdout/stderr via temp file. `eval` only sees `printf %q` quoted assignments; stderr never reaches eval (closes shell-injection via `cat`-on-directory `$()` POC).
- **H3**: `validate-target` test coverage via `IDD_EDIT_HELPER_GH_MOCK` env var (described above under Added) — 4 new fixtures.
- **H4**: `section-replace` heading-level counter rewritten via awk char-by-char (no `wc -c` trailing-newline off-by-one). CRLF input strip via `tr -d '\r'` on both input + replacement. Fixture 08 strengthened with exact-stdout match. New fixture 19 verifies CRLF.
- **H5**: `--body-file` path-traversal risk documented in SKILL.md 鐵律.
- **D1**: PR body + commit body Closes/auto-close trailer cleaned per `CLAUDE.md` Commit Conventions §「引用 trap pattern 作反例的寫作紀律」(#97).
- **M3**: Bot allowlist dead-code redundant patterns cleaned up.
- **M6**: `validate-target` guards against null `login`/`assoc` from malformed gh API response.

### Filed during implementation

- **[#155](https://github.com/PsychQuant/issue-driven-development/issues/155)** — sister concern: bash vs alternative layer for strict flag parsing (parking-lot P3; helper extraction proves bash + thin SKILL.md orchestration works)
- **[#156](https://github.com/PsychQuant/issue-driven-development/issues/156)** — sister concern: IDD plugin test framework (P2; partially pre-empted by reusing `.claude/scripts/tests/` fixture-dir precedent for #154 — same pattern now applies to 2 sites)
- **[#157](https://github.com/PsychQuant/issue-driven-development/issues/157)** — tangential: spec.md `<!-- @trace -->` blocks lack auto-updater (parking-lot P3)
- **[#158](https://github.com/PsychQuant/issue-driven-development/issues/158)** — tangential: `/idd-edit` batch mode + R5 interaction semantics decision (P2; single-target enforcement shipped, batch interaction follow-up)
- **[#160](https://github.com/PsychQuant/issue-driven-development/issues/160)** — sister bug: spectra-archive-post-ic/test.sh likely has same `grep -qF --` bug (parking-lot P3)
- **[#161](https://github.com/PsychQuant/issue-driven-development/issues/161)** — sister concern: IDD_CALLER env var registry codification (parking-lot P3)

### Round 2 verify fixes (R2 found 5 NEW HIGH + 4 MEDIUM introduced by R1 fix loop; R3 addresses all)

R2 findings + R3 fixes:

- **H6 (quote injection in `emit-audit-marker`)** — 4-way confluence (Logic + Security + DA + Codex independently surfaced). `--reason='ok" date="1970-01-01" forged="yes'` was forging audit attributes by breaking out of value's double-quote scope. R3 fix: `${val//\"/\&quot;}` (note bash `&`-as-backref gotcha required `\&` escape). Also closes latent key-injection (H7 defense in depth). Fixture 20 GREEN.
- **H7 (batch mode broken)** — SKILL.md Step 1 for-loop closed BEFORE Steps 1.5-7, silently processing only LAST target. R3 fix: restructured to accumulate `RESOLVED_COMMENT_IDS` array; added explicit "Per-target outer loop" subsection wrapping Steps 1.5-7.
- **H8 (`IDD_EDIT_HELPER_GH_MOCK` ungated)** — R5 author check bypassable via attacker-crafted env (classic LD_PRELOAD pattern). R3 fix: helper now requires `IDD_EDIT_HELPER_TEST_MODE=1` paired with the mock var, refuses with explicit error otherwise. Fixture 21 GREEN.
- **H9 (CHANGELOG false claim)** — Line 58 claimed `$REPO` "respects walk-up config" but walk-up never implemented. R3 fix: corrected entry to acknowledge --repo flag only + defer walk-up to future enhancement.
- **H10 (`--body-file` path traversal)** — R1 doc-only fix insufficient; `/etc/passwd` still readable. R3 fix: helper `validate_body_file_path()` refuses `/etc/* /var/* /sys/* /proc/* /private/etc|var/* $HOME/.ssh|.aws|.gnupg|.kube|.docker/*` unless `IDD_EDIT_HELPER_ALLOW_UNSAFE_BODY_FILE=1` escape hatch set. Fixtures 22 (refuse) + 23 (safe path) GREEN.
- **M-R2-1 (null guard incomplete)** — Only caught `<null>` literal, not empty string from jq parse failure. R3 fix: extended to `[ -z "$author_login" ]` + `2>/dev/null` on jq calls.
- **M-R2-2 (`tr` range excluded TAB/CR)** — R3 fix: range now `\000-\037` (full ctrl chars); comment corrected.
- **M-R2-3 (marker format change unannounced)** — Marker values now double-quoted (e.g. `mode="replace"` not `mode=replace`). Downstream parsers should accept either form; this entry documents the change.
- **M-R2-4 (fixtures don't cover SKILL.md↔helper integration)** — Architectural gap acknowledged; not addressed in R3 to avoid scope inflation. Filed as follow-up [#163](https://github.com/PsychQuant/issue-driven-development/issues/163).
- **N1-3 doc drift** — Stale `.claude/scripts/` refs in spec.md / SKILL.md / test.sh + PR body "13 fixtures" outdated → R3 sed pass over 5 sites; PR body updated to "23 fixtures" (current count post-R3).

### Filed during R3 fix loop

- **[#163](https://github.com/PsychQuant/issue-driven-development/issues/163)** — sister: SKILL.md↔helper integration test layer (M-R2-4) — pragmatic test layer that exercises SKILL.md orchestration end-to-end with mock gh, not just helper standalone

## [2.74.0] - 2026-05-25

### Added
Expand Down
Loading