@mmerterden/multi-agent-pipeline 14.1.1 → 14.2.2
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/CHANGELOG.md +162 -0
- package/README.md +10 -6
- package/README.tr.md +143 -0
- package/docs/architecture.md +23 -8
- package/docs/ecosystem.md +237 -0
- package/install/_plugin-skills.mjs +16 -2
- package/install/codex.mjs +9 -4
- package/install/templates/copilot-instructions.md +12 -9
- package/package.json +1 -1
- package/pipeline/commands/deploy.md +4 -1
- package/pipeline/commands/multi-agent/SKILL.md +8 -5
- package/pipeline/commands/multi-agent/analysis/SKILL.md +2 -2
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/channels/SKILL.md +15 -4
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/dev/SKILL.md +10 -23
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +10 -2
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +10 -24
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +10 -3
- package/pipeline/commands/multi-agent/help/SKILL.md +49 -11
- package/pipeline/commands/multi-agent/jira/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/language/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +6 -2
- package/pipeline/commands/multi-agent/log/SKILL.md +7 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/ship/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +340 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +7 -6
- package/pipeline/commands/multi-agent/test/SKILL.md +18 -8
- package/pipeline/commands/multi-agent/test-accessibility/SKILL.md +33 -0
- package/pipeline/commands/multi-agent/test-dark-mode/SKILL.md +33 -0
- package/pipeline/commands/multi-agent/test-dynamic-type/SKILL.md +33 -0
- package/pipeline/commands/multi-agent/test-screenshots/SKILL.md +41 -0
- package/pipeline/commands/multi-agent/testflight-validation/SKILL.md +28 -201
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -1
- package/pipeline/commands/sim-test.md +45 -36
- package/pipeline/lib/extract-conventions.sh +44 -15
- package/pipeline/lib/fetch-figma-annotations.sh +8 -1
- package/pipeline/lib/fetch-fortify.sh +23 -8
- package/pipeline/lib/figma-screenshot.sh +11 -1
- package/pipeline/lib/issue-fetcher.sh +76 -9
- package/pipeline/lib/md2confluence-v3.py +16 -2
- package/pipeline/lib/plan-todos.sh +5 -2
- package/pipeline/lib/post-pr-review.sh +8 -6
- package/pipeline/lib/shadow-git.sh +50 -9
- package/pipeline/lib/submodule-detector.sh +8 -1
- package/pipeline/multi-agent-refs/_input-parser.md +1 -1
- package/pipeline/multi-agent-refs/channels/confluence.md +3 -0
- package/pipeline/multi-agent-refs/channels/issue-comment.md +2 -2
- package/pipeline/multi-agent-refs/channels/jira.md +13 -2
- package/pipeline/multi-agent-refs/channels/pr-review-actions.md +1 -1
- package/pipeline/multi-agent-refs/channels/pr.md +20 -0
- package/pipeline/multi-agent-refs/channels/wiki.md +1 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +6 -5
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/generate-issue.md +2 -2
- package/pipeline/multi-agent-refs/issue-jira-triad.md +3 -3
- package/pipeline/multi-agent-refs/knowledge.md +1 -1
- package/pipeline/multi-agent-refs/payload-contracts.md +67 -0
- package/pipeline/multi-agent-refs/phases/modes.md +20 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +2 -2
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +8 -40
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +5 -3
- package/pipeline/multi-agent-refs/phases.md +6 -0
- package/pipeline/multi-agent-refs/rules.md +2 -0
- package/pipeline/schemas/prefs.schema.json +2 -2
- package/pipeline/scripts/audit-log-rotate.sh +10 -0
- package/pipeline/scripts/build-stack-plugins.mjs +8 -1
- package/pipeline/scripts/check-derived-drift.mjs +13 -1
- package/pipeline/scripts/diff-explain.mjs +41 -3
- package/pipeline/scripts/diff-risk-score.mjs +72 -8
- package/pipeline/scripts/gen-mode-dispatch.mjs +1 -1
- package/pipeline/scripts/learning-curve.mjs +8 -2
- package/pipeline/scripts/output-quality-check.sh +15 -4
- package/pipeline/scripts/phase-tracker.sh +21 -8
- package/pipeline/scripts/pre-commit-check.sh +69 -22
- package/pipeline/scripts/render-agent-log-cost.sh +8 -3
- package/pipeline/scripts/render-cost-summary.sh +42 -22
- package/pipeline/scripts/render-work-summary.sh +47 -13
- package/pipeline/scripts/review-scope.mjs +1 -1
- package/pipeline/scripts/run-aggregator.mjs +38 -14
- package/pipeline/scripts/smoke-schema-validation.sh +5 -1
- package/pipeline/scripts/test-gap-scan.mjs +45 -6
- package/pipeline/scripts/uninstall.mjs +39 -4
- package/pipeline/scripts/update-issue-progress.sh +12 -16
- package/pipeline/scripts/worktree-finalize.sh +23 -2
- package/pipeline/skills/.skills-index.json +58 -4
- package/pipeline/skills/shared/README.md +12 -7
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +13 -17
- package/pipeline/skills/shared/core/multi-agent-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-create-jira/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-dev/SKILL.md +4 -17
- package/pipeline/skills/shared/core/multi-agent-dev-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-dev-local/SKILL.md +5 -18
- package/pipeline/skills/shared/core/multi-agent-dev-local-autopilot/SKILL.md +8 -0
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +50 -12
- package/pipeline/skills/shared/core/multi-agent-language/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-local/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-local-autopilot/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-purge/SKILL.md +18 -3
- package/pipeline/skills/shared/core/multi-agent-ship/SKILL.md +4 -0
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +50 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +6 -5
- package/pipeline/skills/shared/core/multi-agent-test/SKILL.md +18 -8
- package/pipeline/skills/shared/core/multi-agent-test-accessibility/SKILL.md +37 -0
- package/pipeline/skills/shared/core/multi-agent-test-dark-mode/SKILL.md +37 -0
- package/pipeline/skills/shared/core/multi-agent-test-dynamic-type/SKILL.md +37 -0
- package/pipeline/skills/shared/core/multi-agent-test-screenshots/SKILL.md +44 -0
- package/pipeline/skills/shared/core/multi-agent-testflight-validation/SKILL.md +29 -101
- package/pipeline/skills/shared/external/firebase/SKILL.md +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +302 -0
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +144 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +156 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +175 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +865 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +335 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +344 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +130 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +264 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +298 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +529 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +187 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +171 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +184 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +26 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +173 -0
- package/pipeline/skills/skills-index.md +10 -4
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Canonical required-reading list for outward-facing payloads (PR body, Jira comment, closing report) plus the markup dialect per surface. Loaded by every mode that runs Phase 6 or Phase 7."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Outward-facing payload contracts
|
|
6
|
+
|
|
7
|
+
> Every mode that opens a PR, comments on a tracker, or closes out a run reads this file first. It does not restate the contracts - it names them, so no mode has to carry its own copy and drift from the others.
|
|
8
|
+
|
|
9
|
+
## Read before Phase 6
|
|
10
|
+
|
|
11
|
+
| Read | Before | Governs |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| [`channels/pr.md`]($HOME/.claude/multi-agent-refs/channels/pr.md) | assembling the PR body | fixed section set (`summary` → `changes` → `architecture` cond. → `verification` → `dependencies` cond. → `related`), Markdown-only rule, reviewer-preserving Bitbucket PUT payload |
|
|
14
|
+
| [`phases/phase-6-commit.md`]($HOME/.claude/multi-agent-refs/phases/phase-6-commit.md) | committing | commit convention, default-reviewer fetch, draft/ready prompt, push-must-succeed loop |
|
|
15
|
+
| [`rules.md`]($HOME/.claude/multi-agent-refs/rules.md) "External System Outputs" | any REST payload | real newlines, no HTML entities, no hand-rolled JSON, markup dialect per surface |
|
|
16
|
+
|
|
17
|
+
## Read before Phase 7
|
|
18
|
+
|
|
19
|
+
| Read | Before | Governs |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| [`channels/jira.md`]($HOME/.claude/multi-agent-refs/channels/jira.md) | posting the Jira comment | fixed section set incl. **Test Scenarios** (Given/When/Then, always present), markdown→wiki conversion table |
|
|
22
|
+
| [`channels/confluence.md`]($HOME/.claude/multi-agent-refs/channels/confluence.md) | writing a Confluence page | storage-format conversion, endpoint flavor |
|
|
23
|
+
| [`phases/phase-7-report.md`]($HOME/.claude/multi-agent-refs/phases/phase-7-report.md) | closing out | Timeline + Agent Activity + Cost Breakdown tables, missing-telemetry disclosure |
|
|
24
|
+
| [`tracker-contract.md`]($HOME/.claude/multi-agent-refs/tracker-contract.md) | every phase boundary | per-phase token narration, completion tile suffix |
|
|
25
|
+
|
|
26
|
+
## Markup dialect per surface
|
|
27
|
+
|
|
28
|
+
Language is not the only axis an external payload has. Every surface also has a markup dialect, and they are not interchangeable - the same body shipped to the wrong dialect renders as visible garbage, not as a slightly-off style.
|
|
29
|
+
|
|
30
|
+
| Surface | Dialect | Converter | Section set owned by |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| PR description (GitHub / Bitbucket / GitLab) | **Markdown**, no conversion | none - post the assembled markdown verbatim | `channels/pr.md` |
|
|
33
|
+
| GitHub issue body + comment | **Markdown**, no conversion | none | `channels/issue-comment.md` |
|
|
34
|
+
| Jira comment + issue description | **Jira wiki markup** | the table in `channels/jira.md`, applied by the model - no program exists | `channels/jira.md` |
|
|
35
|
+
| Confluence page body | **storage format** (XHTML) | `lib/md2confluence-v3.py` | `channels/confluence.md` |
|
|
36
|
+
| Wiki pages (`.md` files in a git repo) | **Markdown** | none | `channels/wiki.md` |
|
|
37
|
+
| Commit message | plain text | none | `rules/git-conventions.md` |
|
|
38
|
+
|
|
39
|
+
Assemble every body once in Markdown, then convert **only** on the branches that require it. The recurring defect is the reverse: the Jira wiki table is the only text-markup table in the doc set, so it reads like the default and gets applied to a PR body. In a Markdown target, `h2. Title` renders as literal text, `{{identifier}}` keeps its braces, `#` starts an H1 instead of a numbered list, and `*bold*` comes out italic. In a Jira target the mirror-image failure applies: `## Heading` and `**bold**` render literally.
|
|
40
|
+
|
|
41
|
+
A payload that is in the right language but the wrong dialect is a defect of the same severity as one that never posted. Both need a fix, not a follow-up ticket.
|
|
42
|
+
|
|
43
|
+
## Closing report (required)
|
|
44
|
+
|
|
45
|
+
The run ends with the phase tracker glyph block **and** the numbers behind it - per-phase duration and token spend, plus totals.
|
|
46
|
+
|
|
47
|
+
- Record spend as you go: `phase-tracker.sh tokens <N> <in> <out> [cached]` after **every** LLM call, including each Phase 4 reviewer subagent and each Phase 3 chunk. Counts are additive and nothing reconstructs them after the fact.
|
|
48
|
+
- Tag the model once per phase (`phase-tracker.sh model <N> <name>`) or the cost helper cannot price it and prints `-`.
|
|
49
|
+
- Durations come from the phase timestamps and survive a missed `tokens` call; token spend does not.
|
|
50
|
+
- If any phase has no token data, name those phases and say their cost is unavailable. Never print a report whose cost section is simply absent - the reader cannot tell "cheap run" from "nobody recorded it".
|
|
51
|
+
|
|
52
|
+
### Missing-telemetry disclosure (Phase 7, required)
|
|
53
|
+
|
|
54
|
+
Before composing the report, compute which phases carry no token data:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
STATE="$HOME/.claude/logs/multi-agent/$TASK_ID/tracker-state.json"
|
|
58
|
+
UNTRACKED=$(jq -r '[.phases[] | select((.tokens_in // 0) + (.tokens_out // 0) == 0) | .id] | join(", ")' "$STATE")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Non-empty `UNTRACKED` → both the agent-log report and the closing chat summary carry one line in `outputLanguage` naming those phase ids as cost-unavailable. Durations come from the phase timestamps and are reported either way.
|
|
62
|
+
|
|
63
|
+
`smoke-tracker-tokens-invocation.sh` only lints that the phase docs *mention* `phase-tracker.sh tokens`. It cannot observe a run that read the doc and skipped the call, so this run-time disclosure is the only thing between a dropped `tokens` call and an invisible failure.
|
|
64
|
+
|
|
65
|
+
## Fast modes are not exempt
|
|
66
|
+
|
|
67
|
+
`--dev`, `--dev --local`, and the autopilot variants skip Analysis, Planning, and the interactive test gate. They run Phase 6 and Phase 7 **unchanged**. A short pipeline is not a licence for an improvised payload shape, a missing Test Scenarios section, or a report without numbers.
|
|
@@ -105,6 +105,26 @@ No separate task breakdown - the agent handles scope autonomously.
|
|
|
105
105
|
|
|
106
106
|
**Combinable with autopilot**: `--dev autopilot` = fastest path. Init -> Dev -> Review (auto-fix) -> auto-commit -> auto-PR -> Report (Phase 5 skipped). Zero user interaction (except build failures after 3 retries, and review findings that survive 3 rework cycles).
|
|
107
107
|
|
|
108
|
+
### Intake warnings shared by the whole `--dev` family
|
|
109
|
+
|
|
110
|
+
These apply to `--dev`, `--dev autopilot`, `--dev local`, and `--dev local autopilot` alike. A mode entry doc may not carry its own copy - it points here.
|
|
111
|
+
|
|
112
|
+
**An analysis document was supplied.** Because Phase 1 and Phase 2 are skipped, no phase turns that document into a task breakdown. The doc becomes raw context for one Dev pass, and work comes out ordered by whatever the model read first: the bottom of the dependency chain lands, the screen wiring does not.
|
|
113
|
+
|
|
114
|
+
Say so before starting, once, and offer the choice:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
This mode skips Analysis and Planning, so the analysis document will not be turned
|
|
118
|
+
into a task breakdown. For analysis-driven screen work, /multi-agent or
|
|
119
|
+
/multi-agent:local run both phases.
|
|
120
|
+
1. Continue with --dev (doc as context only)
|
|
121
|
+
2. Switch to the full pipeline
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Autopilot picks 1 and logs the warning rather than asking.
|
|
125
|
+
|
|
126
|
+
**The branch already carries the work.** When the change was developed outside the pipeline, or by hand, the `--dev` family is the wrong entry point: it will try to develop again. `/multi-agent:ship` puts the existing diff through the same review, adds a build+test success gate, opens the PR, and posts the Jira technical-analysis + test-scenario comment - without re-developing. Offer it when the working tree or branch is already ahead of the base with the task's changes.
|
|
127
|
+
|
|
108
128
|
---
|
|
109
129
|
|
|
110
130
|
## Local Mode (`--local`)
|
|
@@ -35,7 +35,7 @@ Read preferences: `PREFS_FILE="$HOME/.claude/multi-agent-preferences.json"` (if
|
|
|
35
35
|
OUTPUT_LANG=$(jq -r '.global.outputLanguage // "en"' "$PREFS_FILE" 2>/dev/null || echo en)
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
From this point on,
|
|
38
|
+
From this point on, everything the user reads renders in `$OUTPUT_LANG`: conversational lines, `AskUserQuestion` `question`/`description`, and external payload bodies (PR/Jira/Confluence). English stays only on `label`/`header`, commit messages, branch names, PR titles, identifiers. Full matrix: `rules.md` "Language Application". Skipping this step is why a Turkish session gets an English wall of text - or an English picker.
|
|
39
39
|
|
|
40
40
|
**Model fallback date gate** (same step, once per run): read `prefs.global.modelFallback`. If `premiumTierUntil` is set and in the past, apply the date-gate trigger from `$HOME/.claude/multi-agent-refs/features/model-fallback.md` - `preferredModel` personas dispatch on `fallbackModel` for this run, with the one-line WARN. Dispatch-error and budget triggers in that contract apply per-dispatch later; nothing else to do here.
|
|
41
41
|
|
|
@@ -406,7 +406,7 @@ git -C $PROJECT_ROOT config user.email "{identity.email}"
|
|
|
406
406
|
|
|
407
407
|
`worktreePath` = `$PROJECT_ROOT`, `localMode` = `true`.
|
|
408
408
|
|
|
409
|
-
**If normal mode** (worktree - default): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Heal stale admin state first** (see "Worktree stale-lock heal" below) and **apply the residue guard** (see "Worktree residue guard" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json
|
|
409
|
+
**If normal mode** (worktree - default): 2. Worktree path: Jira → `.worktrees/{jiraId}/`, GitHub → `.worktrees/GH{issueNo}/`, free-text → `.worktrees/task-{shortId}/` 3. **Heal stale admin state first** (see "Worktree stale-lock heal" below) and **apply the residue guard** (see "Worktree residue guard" below), then `git -C $PROJECT_ROOT worktree add {path} -b {branch} origin/{baseBranch}` (if exists: enter, pull) 4. Set identity: `git -C {worktree-path} config user.name/email` 5. Create log dir + `agent-log.md` + `agent-state.json` at `$HOME/.claude/logs/multi-agent/{project}/{task-id}/`, never inside the worktree:
|
|
410
410
|
|
|
411
411
|
**Worktree stale-lock heal (required before every `worktree add`):** a run killed mid-`worktree add` (OOM, SIGTERM, disk full) leaves a locked or broken admin entry under `.git/worktrees/{id}/`, so the retry fails with `fatal: '<path>' already exists`. Always run the heal first - it is a no-op on a clean repo:
|
|
412
412
|
|
|
@@ -106,6 +106,8 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
|
|
|
106
106
|
|
|
107
107
|
#### Step 3 - PR Description (technical detail for reviewers)
|
|
108
108
|
|
|
109
|
+
**Section set + markup dialect: `channels/pr.md` - read it first.** Phase 7 channels replaces this body with that section set, so build to it. Required reading: `payload-contracts.md`.
|
|
110
|
+
|
|
109
111
|
Generate a structured PR description based on task type. The PR body targets **code reviewers** - it should be technical: what changed, why, architecture decisions, how to verify.
|
|
110
112
|
|
|
111
113
|
**required**: Run all generated text (PR body, commit message) through the `humanizer` skill before posting. This removes AI-generated patterns (inflated language, filler phrases, repetitive structure) and makes the output sound like a developer wrote it.
|
|
@@ -116,49 +118,15 @@ Generate a structured PR description based on task type. The PR body targets **c
|
|
|
116
118
|
- Bitbucket: similar patterns
|
|
117
119
|
- Use `Ref: #N`, `Related: #N`, or `See: {jiraId}` instead
|
|
118
120
|
|
|
119
|
-
**Default Reviewers**:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
Default reviewers for this project? (comma-separated GitHub usernames, or skip):
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Save to `prefs.projects[{project}].defaultReviewers: ["user1", "user2", "user3", "user4"]`
|
|
121
|
+
**Default Reviewers**: use `prefs.projects[{project}].defaultReviewers` when set; otherwise ask once ("Default reviewers for this project? comma-separated, or skip") and persist the answer there. Per-remote resolution rules are in "Default reviewers (required on every PR)" below - this is the prefs half only.
|
|
126
122
|
|
|
127
|
-
**Standard PR (bug fix, feature, refactor)
|
|
123
|
+
**Standard PR (bug fix, feature, refactor)** - invocation shape; body comes from a file so newlines survive:
|
|
128
124
|
|
|
129
125
|
```bash
|
|
130
126
|
REVIEWERS=$(jq -r '.projects["'$PROJECT'"].defaultReviewers // [] | map("--reviewer " + .) | join(" ")' "$PREFS_FILE")
|
|
131
127
|
gh pr create --base {baseBranch} --head {branch} $REVIEWERS \
|
|
132
128
|
--title "{type}({scope}): {summary} [{jiraId}]" \
|
|
133
|
-
--body
|
|
134
|
-
## Summary
|
|
135
|
-
{1-3 bullet points: what changed and why}
|
|
136
|
-
|
|
137
|
-
## Jira
|
|
138
|
-
{jiraId} - {jira summary or link}
|
|
139
|
-
|
|
140
|
-
## Technical Details
|
|
141
|
-
### Changes
|
|
142
|
-
{For each changed file group, explain WHAT changed and WHY}
|
|
143
|
-
|
|
144
|
-
### Architecture
|
|
145
|
-
{Any structural decisions: new files, moved code, pattern changes}
|
|
146
|
-
|
|
147
|
-
### Dependencies
|
|
148
|
-
{New imports, framework changes, package updates - or "None"}
|
|
149
|
-
|
|
150
|
-
## How to Test
|
|
151
|
-
1. {Step-by-step manual test instructions}
|
|
152
|
-
2. {Verify specific behavior}
|
|
153
|
-
|
|
154
|
-
## Checklist
|
|
155
|
-
- [ ] Build passes (0 errors, 0 warnings)
|
|
156
|
-
- [ ] Tests pass
|
|
157
|
-
- [ ] No hardcoded values (tokens used)
|
|
158
|
-
- [ ] Accessibility verified
|
|
159
|
-
- [ ] Dark mode verified
|
|
160
|
-
EOF
|
|
161
|
-
)"
|
|
129
|
+
--body-file /tmp/pr-body-$TASK_ID.md
|
|
162
130
|
```
|
|
163
131
|
|
|
164
132
|
**UI Component PR (when SwiftUI component detected):**
|
|
@@ -175,9 +143,9 @@ Same `gh pr create` pattern with component-specific body sections:
|
|
|
175
143
|
|
|
176
144
|
CRITICAL: PR description **MUST** preserve real newlines. Never embed `\n` literally - Bitbucket saves it verbatim. Never use HTML entities. Pattern: heredoc → `jq --rawfile` → `curl --data-binary @file`.
|
|
177
145
|
|
|
178
|
-
Required payload fields: `title`, `description` (via rawfile), `fromRef` (id + repo slug + project key), `toRef` (same structure), `state: "OPEN"`, `open: true`, `closed: false`, `locked: false`, `reviewers
|
|
146
|
+
Required payload fields: `title`, `description` (via rawfile), `fromRef` (id + repo slug + project key), `toRef` (same structure), `state: "OPEN"`, `open: true`, `closed: false`, `locked: false`, `reviewers` (the fetched list - `[]` only if that endpoint really returned empty).
|
|
179
147
|
|
|
180
|
-
To **update** an existing PR:
|
|
148
|
+
To **update** an existing PR: use the `channels/pr.md` PUT payload - required, not illustrative. It carries `reviewers`/`fromRef`/`toRef`/`draft` (a PUT clears omitted fields) and the GET's `version` unmodified (`version + 1` → 409). Refetch after, assert the reviewer count held. GitHub: `--body-file`.
|
|
181
149
|
|
|
182
150
|
**Default reviewers (required on every PR)**
|
|
183
151
|
|
|
@@ -307,7 +275,7 @@ update_sibling_links "${PR_URLS[@]}"
|
|
|
307
275
|
|
|
308
276
|
**Then finalize each repo's worktree**, serially and only now - `update_sibling_links` updates one PR per repo and the loop `cd`s per repo, so removing repo 1's worktree mid-loop breaks 2..N. Run `worktree-finalize.sh` once per repo with that repo's `--worktree` / `--project-root` / `--project`; each skips or removes independently.
|
|
309
277
|
|
|
310
|
-
`update_sibling_links` does an UPDATE call per PR (Bitbucket: PUT
|
|
278
|
+
`update_sibling_links` does an UPDATE call per PR (Bitbucket: the `channels/pr.md` PUT payload, current `version`; GitHub: `gh pr edit --body-file`). Idempotent.
|
|
311
279
|
|
|
312
280
|
##### GitHub issue body - list all PRs
|
|
313
281
|
|
|
@@ -153,8 +153,8 @@ Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emi
|
|
|
153
153
|
|
|
154
154
|
## Timeline
|
|
155
155
|
|
|
156
|
-
| # | Phase | Model | Duration | Status | Detail |
|
|
157
|
-
| --- | ----- | ----- | -------- | ------ | ------ |
|
|
156
|
+
| # | Phase | Agent | Model | Duration | Tokens (in/out) | Status | Detail |
|
|
157
|
+
| --- | ----- | ----- | ----- | -------- | --------------- | ------ | ------ |
|
|
158
158
|
|
|
159
159
|
## Agent Activity Report
|
|
160
160
|
|
|
@@ -200,7 +200,7 @@ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 7 task.completed \
|
|
|
200
200
|
|
|
201
201
|
**Per-run outcome metrics (evidence corpus):** also emit `run-metrics.mjs --state <agent-state.json>` and append/persist its JSON. It records the numbers that actually answer "did this run go well" - review iterations (rework loops), first-pass-clean, reviewer signal-to-noise (accepted / raw findings), consensus verdict, build outcome. Accumulating these across real runs is the real-world validation that golden tasks + benchmarks only approximate; keep the corpus so the pipeline's quality can be measured, not asserted.
|
|
202
202
|
|
|
203
|
-
**Cost Breakdown emission (mandatory):** as part of agent-log compose, append the per-task cost block produced by `render-agent-log-cost.sh
|
|
203
|
+
**Cost Breakdown emission (mandatory):** as part of agent-log compose, append the per-task cost block produced by `render-agent-log-cost.sh`:
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
206
|
COST_BLOCK=$(bash $HOME/.claude/scripts/render-agent-log-cost.sh "$TASK_ID" 2>/dev/null) && \
|
|
@@ -209,6 +209,8 @@ COST_BLOCK=$(bash $HOME/.claude/scripts/render-agent-log-cost.sh "$TASK_ID" 2>/d
|
|
|
209
209
|
|
|
210
210
|
This is independent of the channels-side `reportContent.costSummary` (which gates the PR-body Cost Summary). agent-log always carries the breakdown when telemetry is present.
|
|
211
211
|
|
|
212
|
+
**Missing-telemetry disclosure (required).** Name untracked phase ids as cost-unavailable in the report and closing summary. Mechanic: `payload-contracts.md`.
|
|
213
|
+
|
|
212
214
|
**Triage memory ingest (mandatory):** after Phase 4 produces a final triage output, persist the accepted/deferred/rejected rows into the per-repo triage corpus so Phase 1 enrichment and Phase 4 prior-art lookup can recall them on future tasks. Idempotent - re-running on the same task writes 0 rows.
|
|
213
215
|
|
|
214
216
|
```bash
|
|
@@ -63,6 +63,12 @@ $HOME/.claude/scripts/phase-tracker.sh update <N> failed # phase failed
|
|
|
63
63
|
$HOME/.claude/scripts/phase-tracker.sh update <N> skipped # --dev mode etc.
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
After every LLM call (counts are additive; skipping this is why runs end with durations but no cost - nothing reconstructs spend afterwards):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
$HOME/.claude/scripts/phase-tracker.sh tokens <N> <in> <out> [cached]
|
|
70
|
+
```
|
|
71
|
+
|
|
66
72
|
For sub-phase progress (e.g. Phase 1's parallel Explore agents, Phase 4's reviewer dispatch + triage + validator gate, Phase 6's commit + push + PR sub-steps):
|
|
67
73
|
|
|
68
74
|
```bash
|
|
@@ -122,6 +122,8 @@ This is the single source of truth. When a contributor or model is unsure where
|
|
|
122
122
|
|
|
123
123
|
4. **Terminal output, log lines, and `echo`/`printf` calls** also use real newlines - never `\n` literals. For multi-line messages, prefer heredoc.
|
|
124
124
|
|
|
125
|
+
5. **Markup dialect is a second axis beyond language.** Markdown: PR body, GitHub issue, wiki files. Jira: wiki markup. Confluence: storage format. Wrong dialect renders literally. Table: `payload-contracts.md`.
|
|
126
|
+
|
|
125
127
|
## Issue Management
|
|
126
128
|
|
|
127
129
|
- **NEVER auto-close** GitHub issues or Jira tickets. Closure requires team review (configurable, typically 4 approvals).
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
},
|
|
185
185
|
"appstore_connect_key_id": {
|
|
186
186
|
"type": ["string", "null"],
|
|
187
|
-
"description": "App Store Connect API key ID. Tier 1 of the App Store Connect access chain, used by /multi-agent:
|
|
187
|
+
"description": "App Store Connect API key ID. Tier 1 of the App Store Connect access chain, used by /multi-agent:store-ready Gate 2 (and its iOS alias /multi-agent:testflight-validation). An identifier rather than a secret; mapped anyway so every credential is read through the same layer. Creating an API key needs an Admin or App Manager role, which is why Tier 2 exists."
|
|
188
188
|
},
|
|
189
189
|
"appstore_connect_issuer_id": {
|
|
190
190
|
"type": ["string", "null"],
|
|
@@ -670,7 +670,7 @@
|
|
|
670
670
|
"type": "string",
|
|
671
671
|
"enum": ["en", "tr"],
|
|
672
672
|
"default": "en",
|
|
673
|
-
"description": "
|
|
673
|
+
"description": "Locked to 'en': the language of LLM-facing instructions (spec files, agent system prompts, reviewer/triage prompts) and picker structural chrome (AskUserQuestion label/header). What humans read is governed by outputLanguage instead - picker question/description, conversational lines, and external payload bodies (PR description, Jira comment, Confluence/Wiki). Always English regardless of either pref: commit messages, branch names, PR titles, code identifiers, agent-log.md. Canonical matrix: multi-agent-refs/rules.md 'Language Application'. Set via /multi-agent:language or /multi-agent:setup."
|
|
674
674
|
},
|
|
675
675
|
"outputLanguage": {
|
|
676
676
|
"type": "string",
|
|
@@ -80,6 +80,16 @@ if [ -s "$AUDIT_FILE" ]; then
|
|
|
80
80
|
if [ -f "$ROTATED" ] && command -v gzip >/dev/null 2>&1; then
|
|
81
81
|
gzip -f "$ROTATED" 2>/dev/null || true
|
|
82
82
|
fi
|
|
83
|
+
|
|
84
|
+
# gzip preserves the source file's mtime by default, so an archive rotated
|
|
85
|
+
# from log content already older than KEEP_DAYS would carry that same old
|
|
86
|
+
# mtime - and the retention sweep below deletes anything past KEEP_DAYS,
|
|
87
|
+
# so a just-created archive could be purged in this very run before ever
|
|
88
|
+
# being read. Retention has to count from rotation time, not from the age
|
|
89
|
+
# of the content it archived. gzip -f removes the plain file on success, so
|
|
90
|
+
# only touch whichever of the two actually exists.
|
|
91
|
+
[ -f "$ROTATED.gz" ] && touch "$ROTATED.gz"
|
|
92
|
+
[ -f "$ROTATED" ] && touch "$ROTATED"
|
|
83
93
|
fi
|
|
84
94
|
|
|
85
95
|
# Drop archives older than KEEP_DAYS
|
|
@@ -61,6 +61,7 @@ const COMMON_SKILLS = [
|
|
|
61
61
|
"agent-introspection-debugging",
|
|
62
62
|
"skill-creator",
|
|
63
63
|
"backlog",
|
|
64
|
+
"localization-reuse-map",
|
|
64
65
|
];
|
|
65
66
|
|
|
66
67
|
// Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
|
|
@@ -134,7 +135,13 @@ function treeFingerprint(dir) {
|
|
|
134
135
|
// --- 1. enumerate authoring source ---------------------------------------
|
|
135
136
|
if (!existsSync(EXTERNAL)) {
|
|
136
137
|
console.error(`source not found: ${EXTERNAL}`);
|
|
137
|
-
|
|
138
|
+
// --check-routing exists so CI can gate routing correctness without a
|
|
139
|
+
// checkout of the plugins repo, reading only this authoring source. Exiting
|
|
140
|
+
// 0 here regardless of --check-routing meant a misconfigured/missing
|
|
141
|
+
// source (wrong --pipeline path, an incomplete CI checkout) reported the
|
|
142
|
+
// gate as passing having audited nothing - the exact silent-green failure
|
|
143
|
+
// the gate exists to prevent elsewhere.
|
|
144
|
+
process.exit(args.includes("--check-routing") ? 1 : 0);
|
|
138
145
|
}
|
|
139
146
|
const allSkills = listDirs(EXTERNAL);
|
|
140
147
|
|
|
@@ -38,6 +38,18 @@ const CACHE = process.env.DERIVED_DRIFT_CACHE || join(homedir(), ".claude", "plu
|
|
|
38
38
|
|
|
39
39
|
const expandHome = (p) => p.replace(/^\$HOME\b|^~/, homedir());
|
|
40
40
|
|
|
41
|
+
// Array.prototype.sort() with no comparator sorts these lexicographically
|
|
42
|
+
// ("10.0.0" < "9.0.0" as strings), so a directory listing with both would
|
|
43
|
+
// treat 9.0.0 as newer.
|
|
44
|
+
function semverCompare(a, b) {
|
|
45
|
+
const pa = a.split(".").map(Number);
|
|
46
|
+
const pb = b.split(".").map(Number);
|
|
47
|
+
for (let i = 0; i < 3; i++) {
|
|
48
|
+
if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
function readJson(path) {
|
|
42
54
|
try {
|
|
43
55
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -116,7 +128,7 @@ function fromCache(entry) {
|
|
|
116
128
|
if (!existsSync(dir)) return null;
|
|
117
129
|
const versions = readdirSync(dir)
|
|
118
130
|
.filter((d) => /^\d+\.\d+\.\d+$/.test(d))
|
|
119
|
-
.sort();
|
|
131
|
+
.sort(semverCompare);
|
|
120
132
|
const newest = versions[versions.length - 1];
|
|
121
133
|
return newest
|
|
122
134
|
? { version: newest, source: "plugin cache", authoritative: false, behind: null }
|
|
@@ -117,6 +117,44 @@ function resolveBase() {
|
|
|
117
117
|
die("no base ref found (tried origin/main, main, origin/master, master) - pass --base");
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// git quotes a `diff --git` path (core.quotePath, on by default) whenever it
|
|
121
|
+
// has a non-ASCII byte or other unusual character - e.g. a Turkish filename
|
|
122
|
+
// renders as `diff --git "a/\303\226deme.swift" "b/\303\226deme.swift"`, not
|
|
123
|
+
// the bare a/...b/... form. The plain regex this replaced never matched that
|
|
124
|
+
// line at all, so every non-ASCII-named file dropped out of the explanation.
|
|
125
|
+
const DIFF_GIT_QUOTED_RE = /^diff --git "a\/((?:[^"\\]|\\.)*)" "b\/((?:[^"\\]|\\.)*)"$/;
|
|
126
|
+
const DIFF_GIT_UNQUOTED_RE = /^diff --git a\/(.+?) b\/(.+)$/;
|
|
127
|
+
|
|
128
|
+
function unquoteGitPath(s) {
|
|
129
|
+
const bytes = [];
|
|
130
|
+
for (let i = 0; i < s.length; i++) {
|
|
131
|
+
if (s[i] === "\\") {
|
|
132
|
+
const octal = s.slice(i + 1, i + 4);
|
|
133
|
+
if (/^[0-7]{3}$/.test(octal)) {
|
|
134
|
+
bytes.push(parseInt(octal, 8));
|
|
135
|
+
i += 3;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const simple = { "\\": 92, '"': 34, t: 9, n: 10, r: 13 };
|
|
139
|
+
const next = s[i + 1];
|
|
140
|
+
if (next in simple) {
|
|
141
|
+
bytes.push(simple[next]);
|
|
142
|
+
i += 1;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
bytes.push(s.charCodeAt(i));
|
|
147
|
+
}
|
|
148
|
+
return Buffer.from(bytes).toString("utf-8");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function matchDiffGitHeader(line) {
|
|
152
|
+
const q = line.match(DIFF_GIT_QUOTED_RE);
|
|
153
|
+
if (q) return unquoteGitPath(q[2]);
|
|
154
|
+
const u = line.match(DIFF_GIT_UNQUOTED_RE);
|
|
155
|
+
return u ? u[2] : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
120
158
|
/**
|
|
121
159
|
* Parse unified diff into a map of {path: [{ oldStart, newStart, lines: [...] }]}
|
|
122
160
|
* Each hunk's `lines` retains the leading +/- /space prefix.
|
|
@@ -127,9 +165,9 @@ function parseDiff(raw) {
|
|
|
127
165
|
let currentHunks = null;
|
|
128
166
|
let currentHunk = null;
|
|
129
167
|
for (const line of raw.split("\n")) {
|
|
130
|
-
const fileMatch =
|
|
131
|
-
if (fileMatch) {
|
|
132
|
-
currentPath = fileMatch
|
|
168
|
+
const fileMatch = matchDiffGitHeader(line);
|
|
169
|
+
if (fileMatch !== null) {
|
|
170
|
+
currentPath = fileMatch;
|
|
133
171
|
currentHunks = [];
|
|
134
172
|
files[currentPath] = currentHunks;
|
|
135
173
|
currentHunk = null;
|
|
@@ -104,9 +104,9 @@ function loadNumstat() {
|
|
|
104
104
|
const map = new Map();
|
|
105
105
|
let cur = null;
|
|
106
106
|
for (const line of text.split("\n")) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
cur =
|
|
107
|
+
const p = matchDiffGitHeader(line);
|
|
108
|
+
if (p !== null) {
|
|
109
|
+
cur = p;
|
|
110
110
|
if (!map.has(cur)) map.set(cur, { added: 0, removed: 0 });
|
|
111
111
|
continue;
|
|
112
112
|
}
|
|
@@ -122,7 +122,7 @@ function loadNumstat() {
|
|
|
122
122
|
return out.split("\n").map((line) => {
|
|
123
123
|
const [a, r, ...rest] = line.split("\t");
|
|
124
124
|
return {
|
|
125
|
-
path: rest.join("\t"),
|
|
125
|
+
path: unquotePathIfNeeded(rest.join("\t")),
|
|
126
126
|
added: a === "-" ? 0 : Number(a) || 0,
|
|
127
127
|
removed: r === "-" ? 0 : Number(r) || 0,
|
|
128
128
|
};
|
|
@@ -162,6 +162,60 @@ const TEST_PATH_GLOBS = [
|
|
|
162
162
|
/\.spec\.(ts|tsx|js|mjs|py)$/i,
|
|
163
163
|
];
|
|
164
164
|
|
|
165
|
+
// git quotes a path in a `diff --git` header (core.quotePath, on by default)
|
|
166
|
+
// whenever it contains a non-ASCII byte or other unusual character -
|
|
167
|
+
// "diff --git \"a/\\303\\226deme.swift\" \"b/\\303\\226deme.swift\"" for a
|
|
168
|
+
// Turkish filename, not the bare `a/`/`b/` form. `/^diff --git a\/(.+?)
|
|
169
|
+
// b\/(.+)$/` doesn't match a line starting with a quote at all, so every
|
|
170
|
+
// non-ASCII-named file was silently dropped from risk scoring.
|
|
171
|
+
const DIFF_GIT_QUOTED_RE = /^diff --git "a\/((?:[^"\\]|\\.)*)" "b\/((?:[^"\\]|\\.)*)"$/;
|
|
172
|
+
const DIFF_GIT_UNQUOTED_RE = /^diff --git a\/(.+?) b\/(.+)$/;
|
|
173
|
+
|
|
174
|
+
// Reverse git's C-style quoting: octal byte escapes (\NNN, one per raw byte -
|
|
175
|
+
// a multi-byte UTF-8 character is several consecutive \NNN triplets) plus
|
|
176
|
+
// the standard \\ \" \t \n \r escapes.
|
|
177
|
+
function unquoteGitPath(s) {
|
|
178
|
+
const bytes = [];
|
|
179
|
+
for (let i = 0; i < s.length; i++) {
|
|
180
|
+
if (s[i] === "\\") {
|
|
181
|
+
const octal = s.slice(i + 1, i + 4);
|
|
182
|
+
if (/^[0-7]{3}$/.test(octal)) {
|
|
183
|
+
bytes.push(parseInt(octal, 8));
|
|
184
|
+
i += 3;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const simple = { "\\": 92, '"': 34, t: 9, n: 10, r: 13 };
|
|
188
|
+
const next = s[i + 1];
|
|
189
|
+
if (next in simple) {
|
|
190
|
+
bytes.push(simple[next]);
|
|
191
|
+
i += 1;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
bytes.push(s.charCodeAt(i));
|
|
196
|
+
}
|
|
197
|
+
return Buffer.from(bytes).toString("utf-8");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// `git diff --numstat` quotes a non-ASCII/unusual path the same C-style way
|
|
201
|
+
// as the `diff --git` header line (e.g. `"\303\226deme.swift"`), just
|
|
202
|
+
// without the surrounding "diff --git a/...b/..." context to detect it by.
|
|
203
|
+
function unquotePathIfNeeded(s) {
|
|
204
|
+
if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') {
|
|
205
|
+
return unquoteGitPath(s.slice(1, -1));
|
|
206
|
+
}
|
|
207
|
+
return s;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Returns the b/-side path from a `diff --git` header line, or null if the
|
|
211
|
+
// line isn't one. Handles both the quoted and unquoted forms.
|
|
212
|
+
function matchDiffGitHeader(line) {
|
|
213
|
+
const q = line.match(DIFF_GIT_QUOTED_RE);
|
|
214
|
+
if (q) return unquoteGitPath(q[2]);
|
|
215
|
+
const u = line.match(DIFF_GIT_UNQUOTED_RE);
|
|
216
|
+
return u ? u[2] : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
165
219
|
function matchesAny(path, regexes) {
|
|
166
220
|
return regexes.some((r) => r.test(path));
|
|
167
221
|
}
|
|
@@ -170,6 +224,16 @@ function isTestPath(path) {
|
|
|
170
224
|
return matchesAny(path, TEST_PATH_GLOBS);
|
|
171
225
|
}
|
|
172
226
|
|
|
227
|
+
// pairedTestPaths only knows how to derive a test-file name for these
|
|
228
|
+
// extensions; for anything else (README.md, package.json, .yaml configs,
|
|
229
|
+
// lockfiles, ...) it falls back to the untouched filename as `base` with an
|
|
230
|
+
// empty `ext`, producing candidates like "README.mdTests." that can never
|
|
231
|
+
// match a real path. Gating no_test_change on this is what "source touched"
|
|
232
|
+
// in the comment above actually means - without it, the signal fired on
|
|
233
|
+
// every non-test file in a diff, README included, which made HIGH_STAKES in
|
|
234
|
+
// review-scope.mjs unavoidable for almost any real-world diff.
|
|
235
|
+
const PAIRABLE_SOURCE_EXT_RE = /\.(swift|kt|ts|tsx|js|mjs|py)$/;
|
|
236
|
+
|
|
173
237
|
function pairedTestPaths(srcPath) {
|
|
174
238
|
// Return likely test path candidates for a source file.
|
|
175
239
|
const parts = srcPath.split("/");
|
|
@@ -189,9 +253,9 @@ function parseHunks(diffText) {
|
|
|
189
253
|
const perFile = new Map();
|
|
190
254
|
let cur = null;
|
|
191
255
|
for (const line of diffText.split("\n")) {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
cur =
|
|
256
|
+
const p = matchDiffGitHeader(line);
|
|
257
|
+
if (p !== null) {
|
|
258
|
+
cur = p;
|
|
195
259
|
if (!perFile.has(cur)) perFile.set(cur, []);
|
|
196
260
|
continue;
|
|
197
261
|
}
|
|
@@ -271,7 +335,7 @@ function buildRow(stat, addedLines, allChangedPaths) {
|
|
|
271
335
|
}
|
|
272
336
|
|
|
273
337
|
// No-test-change: source touched + no matching test file in the changed set.
|
|
274
|
-
if (!isTestPath(path)) {
|
|
338
|
+
if (!isTestPath(path) && PAIRABLE_SOURCE_EXT_RE.test(path)) {
|
|
275
339
|
const candidates = pairedTestPaths(path).map((c) => c.toLowerCase());
|
|
276
340
|
const anyTest = allChangedPaths.some((p) => {
|
|
277
341
|
if (!isTestPath(p)) return false;
|
|
@@ -145,7 +145,7 @@ bash $HOME/.claude/scripts/phase-tracker.sh update 0 in_progress
|
|
|
145
145
|
bash $HOME/.claude/scripts/phase-tracker.sh update <N> in_progress|completed|failed|skipped
|
|
146
146
|
|
|
147
147
|
# After every LLM call (every CLI):
|
|
148
|
-
bash $HOME/.claude/scripts/phase-tracker.sh tokens <N> <in> <out>
|
|
148
|
+
bash $HOME/.claude/scripts/phase-tracker.sh tokens <N> <in> <out> [cached]
|
|
149
149
|
\`\`\`
|
|
150
150
|
|
|
151
151
|
### Visual channel - Claude Code (native TaskList widget, required)
|
|
@@ -78,8 +78,14 @@ for (const e of events) {
|
|
|
78
78
|
}
|
|
79
79
|
if (e.ts && (!t.firstTs || e.ts < t.firstTs)) t.firstTs = e.ts;
|
|
80
80
|
const d = e.details || {};
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// phase-4-review.md's actual review.completed emission (raw_count=/
|
|
82
|
+
// accepted=/deferred=/rejected=/approved=/duration_ms=) carries no
|
|
83
|
+
// review_cycles field - that name only appears on Phase 7's task.completed
|
|
84
|
+
// summary event, which this never matched. Phase 4 re-runs (and logs its
|
|
85
|
+
// own review.completed) once per rework cycle, so the cycle count for a
|
|
86
|
+
// task is how many review.completed events share its task_id - the same
|
|
87
|
+
// derivation aggregate-metrics.mjs already uses for this metric.
|
|
88
|
+
if (e.event === "review.completed") t.reviewCycles = (t.reviewCycles ?? 0) + 1;
|
|
83
89
|
if (e.event === "rework.started") t.rework += 1;
|
|
84
90
|
t.tokensIn += num(d.tokens_in);
|
|
85
91
|
t.tokensOut += num(d.tokens_out);
|
|
@@ -80,11 +80,22 @@ if [ -f "$PR_BODY" ]; then
|
|
|
80
80
|
else
|
|
81
81
|
ok "PR body free of HTML entities"
|
|
82
82
|
fi
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
# Canonical section set is channels/pr.md's: summary -> changes ->
|
|
84
|
+
# architecture (cond.) -> verification -> dependencies (cond.) -> related.
|
|
85
|
+
# Headings render in outputLanguage, so match the always-required three by
|
|
86
|
+
# either language rather than pinning English. "## How to Test" was the old
|
|
87
|
+
# phase-6 template's heading and is not part of the canonical set.
|
|
88
|
+
for sec in "Summary|Özet" "Changes|Değişiklikler" "Verification|Doğrulama"; do
|
|
89
|
+
label="${sec%%|*}"
|
|
90
|
+
grep -qE "^## ($sec)" "$PR_BODY" \
|
|
91
|
+
&& ok "PR body has '$label' section" \
|
|
92
|
+
|| warn "PR body missing '$label' section (canonical set: channels/pr.md)"
|
|
87
93
|
done
|
|
94
|
+
if grep -qE '^h[1-6]\. |\{\{[A-Za-z_]' "$PR_BODY"; then
|
|
95
|
+
bad "PR body contains Jira wiki markup (h2./{{code}}) - PR bodies are Markdown"
|
|
96
|
+
else
|
|
97
|
+
ok "PR body free of Jira wiki markup"
|
|
98
|
+
fi
|
|
88
99
|
else
|
|
89
100
|
warn "no .pr-body.md in task dir (skipped PR-body check)"
|
|
90
101
|
fi
|