cc-discipline 2.13.1 → 2.13.5

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/init.sh CHANGED
@@ -470,6 +470,11 @@ _cc_hash() {
470
470
  # only overwrite a file that is byte-identical to what we would install anyway.
471
471
  _cc_hash_matches() {
472
472
  _f="$1"; _rec="$2"
473
+ # The manifest itself has no file extension, so .gitattributes rules keyed on
474
+ # *.sh/*.md/*.json never cover it and git may hand it back with CRLF. A
475
+ # trailing CR then rides along in the recorded digest and every comparison
476
+ # fails — the same bug this function exists to fix, one level up. Strip it.
477
+ _rec=${_rec%$'\r'}
473
478
  [ -n "$_rec" ] || return 1
474
479
  [ -f "$_f" ] || return 1
475
480
  case "$_rec" in
@@ -570,6 +575,7 @@ if [ -s "$OLD_MANIFEST" ]; then
570
575
  # user put inside the skill directory that we never installed and
571
576
  # therefore cannot account for.
572
577
  while read -r rel _; do
578
+ rel=${rel%$'\r'}
573
579
  case "$rel" in "$old_skill"/*) ;; *) continue ;; esac
574
580
  rm -f ".claude/skills/$rel"
575
581
  done < "$OLD_MANIFEST"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-discipline",
3
- "version": "2.13.1",
3
+ "version": "2.13.5",
4
4
  "description": "Discipline framework for Claude Code — rules, hooks, and agents that keep AI on track",
5
5
  "bin": {
6
6
  "cc-discipline": "bin/cli.js"
@@ -8,32 +8,47 @@
8
8
  # Read the tool input from stdin (JSON)
9
9
  INPUT=$(cat)
10
10
 
11
- # Extract fields
11
+ # Extract file_path first. Everything past the exemption checks needs more
12
+ # fields, but the exemptions need only this one — and in a docs-heavy repo most
13
+ # edits exit there, so pulling cwd before deciding whether we care costs ~40ms
14
+ # on every edit for nothing. Measured 2026-09-02: concluding "not my business"
15
+ # cost 297ms, of which 24ms was the actual decision.
12
16
  if command -v jq &>/dev/null; then
17
+ HAS_JQ=1
13
18
  FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
14
- CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
15
19
  else
20
+ HAS_JQ=0
16
21
  FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":\s*"[^"]*"' | head -1 | sed 's/"file_path":\s*"//;s/"//')
17
- CWD=$(echo "$INPUT" | grep -o '"cwd":\s*"[^"]*"' | head -1 | sed 's/"cwd":\s*"//;s/"//')
18
22
  fi
19
23
 
20
- # Extract just the filename for pattern matching (avoid false matches on directory names)
21
- BASENAME=$(basename "$FILE_PATH")
24
+ # Exemptions below use bash builtins only. `case` and parameter expansion fork
25
+ # nothing, where `echo | grep` forks two processes and `basename` one. The
26
+ # decisions are identical to the greps they replace — see
27
+ # tests/pre-edit-guard-matrix.sh, which runs both and compares.
22
28
 
23
- # Allow edits to docs/
24
- # Note: use -E (extended regex) for portability — BSD grep (macOS) doesn't support \| in basic mode
25
- if echo "$FILE_PATH" | grep -qE "^docs/|/docs/"; then
26
- exit 0
27
- fi
29
+ # Allow edits to docs/ — case-SENSITIVE, matching the original grep (no -i).
30
+ case "$FILE_PATH" in
31
+ docs/*|*/docs/*) exit 0 ;;
32
+ esac
28
33
 
29
- # Allow edits to test files (match filename only, not directory path)
30
- if echo "$BASENAME" | grep -qiE "test|spec"; then
31
- exit 0
32
- fi
34
+ # Just the filename, so directory names cannot cause a false match.
35
+ BASENAME=${FILE_PATH##*/}
33
36
 
34
- # Allow edits to config/meta files
35
- if echo "$BASENAME" | grep -qiE "\.(md|json|yaml|yml|toml|cfg|ini)$"; then
36
- exit 0
37
+ # Test files and config/meta files. nocasematch reproduces `grep -i`; it is set
38
+ # only around these two patterns and unset immediately, so nothing later in the
39
+ # script inherits it. Available since bash 3.1, so macOS's stock 3.2 is fine.
40
+ shopt -s nocasematch
41
+ case "$BASENAME" in
42
+ *test*|*spec*) exit 0 ;;
43
+ *.md|*.json|*.yaml|*.yml|*.toml|*.cfg|*.ini) exit 0 ;;
44
+ esac
45
+ shopt -u nocasematch
46
+
47
+ # cwd is needed only from here on, past every exemption.
48
+ if [ "$HAS_JQ" = 1 ]; then
49
+ CWD=$(echo "$INPUT" | jq -r '.cwd // empty' 2>/dev/null)
50
+ else
51
+ CWD=$(echo "$INPUT" | grep -o '"cwd":\s*"[^"]*"' | head -1 | sed 's/"cwd":\s*"//;s/"//')
37
52
  fi
38
53
 
39
54
  # Check for unresolved hypotheses in debug-log.md
@@ -1,45 +1,75 @@
1
1
  ---
2
2
  name: coplan
3
- description: Write the plan under discussion to docs/current-plan.md so Codex can review it against the real code. Stacks after /think.
4
- when_to_use: When a plan is worth a second opinion before building it — "coplan", "落个方案", "写下来给 codex 看", "把方案存一下" or whenever /coplan is stacked after /think.
3
+ description: Write the plan under discussion to docs/current-plan.md so an external reviewer can check it against the real code. User-invoked; stacks after /think.
4
+ when_to_use: Only when the user asks an explicit /coplan, or "落个方案" / "写下来给 codex 看" / "把方案存一下", or /coplan stacked after /think. Whether a plan is worth an external review is the user's judgement, not yours; do not reach for this on your own.
5
5
  ---
6
6
 
7
7
  Write the plan under discussion to `docs/current-plan.md`, then stop.
8
8
 
9
- This skill does not produce the plan and does not decide anything. It takes whatever is on the table — the approaches from `/think`, or a plan just described in conversation — and puts it where Codex can read it. Codex runs read-only from the repo root, so a plan **on disk** is one it can check **against the actual code**. That is the entire point: a plan pasted into a prompt can only be judged on its own terms, while a plan in the tree can be judged against what is really there.
9
+ This skill does not produce the plan and does not decide anything. It takes whatever is on the table — the approaches from `/think`, or a plan just described in conversation — and puts it where a reviewer can read it. Codex runs read-only from the repo root, so a plan **on disk** is one it can check **against the actual code**. That is the entire point: a plan pasted into a prompt can only be judged on its own terms, while a plan in the tree can be judged against what is really there.
10
10
 
11
- If there is no plan on the table yet — no approaches from `/think`, nothing described in conversation — **say so and stop**. Point at `/think` instead. Do not invent a plan just to have something to write: a fabricated plan sent for review wastes a Codex run and produces confident feedback on something nobody proposed.
11
+ If there is no plan on the table yet — no approaches from `/think`, nothing described in conversation — **say so and stop**. Point at `/think` instead. Do not invent a plan just to have something to write: a fabricated plan sent for review wastes a review run and produces confident feedback on something nobody proposed. Fabricating content when a skill is invoked is the hardest failure mode to catch in yourself, which makes this the most important line in this file.
12
+
13
+ ## The file is a scratch file, not a record
14
+
15
+ **It must not be tracked by git.** Before writing, make sure `docs/current-plan.md` is in `.gitignore`; add the line if it is missing. If the file is already tracked, say so and offer to untrack it with `git rm --cached docs/current-plan.md` — do not run that silently, and never commit this file yourself.
16
+
17
+ - **A tracked file that is always modified becomes a standing passenger.** It sits in every `git status`, so it rides into unrelated commits whenever anything sweeps the working tree. This has already happened: a commit whose message said it touched only documentation also carried temporary debug instrumentation onto `main`. Untracking removes the passenger; telling someone to be careful does not — `/commit` already says to stage selectively, and it happened anyway.
18
+ - **It is not where the decision lives.** What was decided and why belongs in `docs/progress.md` and in commit messages. Do not create an archive directory for old plans: a third place holding the same facts only guarantees two of them go stale.
19
+
20
+ **Known cost, accepted deliberately**: because the file is untracked, hand-edits between revisions no longer show up in `git diff`, so you lose the view of what you just changed. Self-contradictions have survived several revisions unnoticed for exactly this reason. That cost is smaller than instrumentation reaching `main`. If a revision matters enough to remember, write the one sentence into `progress.md` — if you cannot write that sentence, it did not matter.
12
21
 
13
22
  ## What to write
14
23
 
15
- **Overwrite the file completely.** One path, current version only — every earlier version survives in `git log -p docs/current-plan.md`, so nothing is lost and the directory never fills with dated files.
24
+ **Overwrite the file completely.** One plan at a time; a superseded draft has no further use.
16
25
 
17
26
  Include, in this order:
18
27
 
19
28
  1. **Task** — what is being asked, in a sentence or two, framed the way the user framed it rather than the way you re-scoped it.
20
29
  2. **Approaches** — two or three. For each: how it works, what it costs, what it gives up. If there is genuinely only one reasonable approach, say so and say why. Do not invent alternatives to fill the section.
21
30
  3. **Recommendation** — which one, and the reason. A recommendation, not a survey.
22
- 4. **Assumptions to verify** — the highest-value section for a reviewer that has the repo. Every claim the plan rests on that you have *not* checked: "assumes this is only called from X", "assumes the migration runs before Y", "assumes no other caller depends on the old shape". Codex can open the code and tell you which of these are false. **A plan usually fails here, not in its logic.**
23
- 5. **Risks and unknowns** — what could go wrong, and what you could not determine.
31
+ 4. **Assumptions to verify** — see below. This is the section reviewers falsify most.
32
+ 5. **Risks and unknowns** — what could go wrong, and what you could not determine. Anything a reviewer cannot settle by reading the repo belongs here, not in section 4.
24
33
  6. **Out of scope** — what this deliberately does not touch, so a reviewer does not flag choices as omissions.
25
34
 
26
35
  Write it for someone who has the codebase but was not in this conversation. No "as discussed above", no pronouns pointing at chat history.
27
36
 
37
+ ### Section 4 in detail
38
+
39
+ Two questions, not one. The second exists because the first does not reach the more expensive failure.
40
+
41
+ **"What am I relying on that I have not checked?"** Every claim the plan rests on: "assumes this is only called from X", "assumes the migration runs before Y", "assumes no other caller depends on the old shape".
42
+
43
+ Two rules for what belongs here:
44
+
45
+ - **It must be falsifiable by opening a named file or running one command.** "Assumes the quarterly cron will actually run" is not an assumption, it is an unknown — a reviewer can only answer "holds with caveats". Put it in section 5.
46
+ - **If you can check it yourself in under a minute, check it now** and write the conclusion instead. This section is for what you cannot cheaply verify. It is not a parking place for what you did not bother to look up, and treating it as one wastes a review run on something a `grep` would have settled.
47
+
48
+ **"What did I check, and how current was the source?"** For every load-bearing fact you *did* verify, name the source with a line number and say how fresh it is. A stale source is more dangerous than an unchecked assumption, because the reviewer opens the same file and inherits the same error — that has happened here: a plan cited `RELEASE_NOTES.md:31`, the review cited the same line, and both were wrong together. Flag anything sourced from a comment, a changelog, or a doc rather than from code that runs.
49
+
28
50
  ## Then stop
29
51
 
30
- Report the path and hand the review command over verbatim:
52
+ Report the path, then hand over a review request the user can send as-is. **A generic request produces a generic review** what makes a review land is naming what to check and demanding a shape for the answer. Fill the bracketed parts from the plan you just wrote:
31
53
 
32
54
  ```
33
55
  Plan written to docs/current-plan.md
34
56
 
35
57
  To have Codex review it:
36
- /codex:rescue 只评审方案不要修改任何文件,方案见 docs/current-plan.md
58
+ /codex:rescue 只评审方案,不要修改任何文件。方案见 docs/current-plan.md
59
+
60
+ 请对着 [列出具体文件/目录] 核实,逐条正面回答 "Assumptions to verify" 的编号,
61
+ 每条只给三样:编号 · 成立/不成立/查不到 · 证据(文件:行)。
62
+ 其余问题另列,不要展开成散文。
37
63
  ```
38
64
 
39
- **Do not run the review yourself** — it costs time and quota, and a small plan does not need one. That call belongs to the user. **Do not start implementing either**: writing a plan down is not approval to build it.
65
+ **Do not run the review yourself** — that call belongs to the user. **Do not start implementing either**: writing a plan down is not approval to build it.
40
66
 
41
67
  ## When stacked after /think
42
68
 
43
69
  `/think /coplan <task>` is the intended pairing. `/think` produces the approaches and stops for the user's choice; `/coplan` puts them on disk on the way past.
44
70
 
45
71
  Write the file with the approaches **still open**. The review is meant to inform the choice, not rubber-stamp one already made — so do not collapse the section down to your recommendation just because you have one.
72
+
73
+ ## What this skill does not do
74
+
75
+ The friction in this workflow is concentrated *after* the review comes back — verifying each finding locally and folding it in — and this skill is not in that path. Be aware of a related hazard while writing: putting an estimate in a plan file gives it a formal, citable form, and the next revision tends to treat it as established. Mark estimates as estimates in the text itself.