@nt-ai-lab/opencode-skillz 0.3.14 → 0.4.1

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.
Files changed (47) hide show
  1. package/AGENTS.md +31 -0
  2. package/agents/default.md +4 -0
  3. package/agents/tdd.md +1 -0
  4. package/commands/plan.md +135 -45
  5. package/commands/resolve-pr-feedback.md +138 -0
  6. package/commands/review-pr.md +13 -20
  7. package/dist/commands/dont-stop/hooks.js +69 -35
  8. package/dist/git-workflow-gates.d.ts +21 -0
  9. package/dist/git-workflow-gates.js +103 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +4 -4
  12. package/dist/plugin-registry/agents.js +36 -25
  13. package/dist/plugin-registry/commands.js +13 -10
  14. package/dist/plugin-registry/index.js +32 -1
  15. package/dist/plugin-registry/markdown.js +26 -11
  16. package/dist/tools/create-pr-tool.d.ts +4 -0
  17. package/dist/tools/create-pr-tool.js +22 -0
  18. package/dist/tools/infra/lint/guidance.d.ts +8 -0
  19. package/dist/tools/infra/lint/guidance.js +98 -0
  20. package/dist/tools/infra/lint/review.d.ts +19 -0
  21. package/dist/tools/infra/lint/review.js +53 -0
  22. package/dist/tools/infra/pull-request/create-draft-pull-request.d.ts +11 -0
  23. package/dist/tools/infra/pull-request/create-draft-pull-request.js +115 -0
  24. package/dist/tools/infra/pull-request/feedback.d.ts +14 -0
  25. package/dist/tools/infra/pull-request/feedback.js +280 -0
  26. package/dist/tools/infra/source-control/changed-files.d.ts +20 -0
  27. package/dist/tools/infra/source-control/changed-files.js +78 -0
  28. package/dist/tools/infra/vitest-coverage/command.d.ts +16 -0
  29. package/dist/tools/infra/vitest-coverage/command.js +85 -0
  30. package/dist/tools/infra/vitest-coverage/review.d.ts +50 -0
  31. package/dist/tools/infra/vitest-coverage/review.js +298 -0
  32. package/dist/tools/infra/vitest-coverage/test-support.d.ts +15 -0
  33. package/dist/tools/infra/vitest-coverage/test-support.js +156 -0
  34. package/dist/tools/lint.d.ts +3 -13
  35. package/dist/tools/lint.js +56 -16
  36. package/dist/tools/pull-request-feedback-tool.d.ts +5 -0
  37. package/dist/tools/pull-request-feedback-tool.js +23 -0
  38. package/dist/tools/vitest-coverage-tool.d.ts +4 -0
  39. package/dist/tools/vitest-coverage-tool.js +31 -0
  40. package/dist/types.d.ts +8 -0
  41. package/package.json +10 -3
  42. package/scripts/check-tools-folder-boundary.mjs +78 -0
  43. package/scripts/install-git-hooks.mjs +54 -0
  44. package/scripts/lint-ts.mjs +32 -7
  45. package/scripts/living-architecture-eslint.config.mjs +4 -2
  46. package/scripts/no-generic-names-eslint-rule.mjs +2 -24
  47. package/scripts/no-generic-names-eslint-rule.mjs.d.ts +28 -0
package/AGENTS.md CHANGED
@@ -7,6 +7,10 @@ Purpose: package OpenCode workflow assets as a plugin.
7
7
  - `index.js`: plugin entrypoint; auto-registers bundled commands and agents.
8
8
  - `commands/*.md`: command definitions (frontmatter + template body).
9
9
  - `agents/*.md`: custom agent definitions (frontmatter + full prompt body).
10
+ - `src/tools/lint.ts`: OpenCode lint tool entrypoint.
11
+ - `src/tools/*-tool.ts`: other OpenCode tool entrypoints.
12
+ - `src/tools/infra/<concept>/`: support code used by tools but not itself an OpenCode tool.
13
+ - `src/tools/infra/lint/guidance.ts`: lint failure remediation guidance injected by the lint tool.
10
14
 
11
15
  ## Conventions
12
16
 
@@ -19,6 +23,33 @@ Purpose: package OpenCode workflow assets as a plugin.
19
23
  - Prefer minimal additions; only add new commands when needed.
20
24
  - Do not add `agent:` in command frontmatter unless the command must force a specific agent.
21
25
 
26
+ ## Lint guidance mechanism
27
+
28
+ - `src/tools/lint.ts` prepends lint failures with remediation guidance.
29
+ - `src/tools/infra/lint/guidance.ts` owns the generic message and rule-specific guidance.
30
+ - Update `src/tools/infra/lint/guidance.ts` when adding portable lint rules that represent design quality, test quality, type-safety, or security constraints.
31
+ - Guidance must direct agents to fix the underlying problem, not suppress rules, delete coverage, or weaken assertions.
32
+
33
+ ## Lint boundaries: portable tool rules vs repo-only checks
34
+
35
+ - `scripts/living-architecture-eslint.config.mjs` is bundled into the lint tool and applies to every codebase that uses `nt_skillz_lint`.
36
+ - Do not add opencode-skillz repository organization rules to `scripts/living-architecture-eslint.config.mjs`.
37
+ - Only add rules to `scripts/living-architecture-eslint.config.mjs` when the rule is intentionally portable across all target repositories.
38
+ - Repository-only checks belong in this repo's local validation flow, such as a dedicated script wired into this repo's `package.json` scripts.
39
+ - `scripts/check-tools-folder-boundary.mjs` is a repo-only check. It enforces that top-level `src/tools/*.ts` files are real OpenCode tool entrypoints and support code lives under `src/tools/infra/<concept>/`.
40
+ - Before adding any lint rule, decide whether it is portable product behavior or local repository hygiene. Mixing those two scopes is a release-impacting mistake.
41
+
42
+ ## Command writing rules
43
+
44
+ ### Be eplicit, avoid vagueness
45
+
46
+ Commands are run by agents that may choose different valid-looking tool calls unless the command removes that choice. A command must include the exact operation, command text, query text, arguments, and expected fields when those details are known. This prevents each run from rediscovering APIs, using different command variants, or failing because the agent guessed a tool shape.
47
+
48
+ Examples:
49
+ - Bad: Fetch unresolved GitHub review threads.
50
+ - Good: Run `gh pr view "$PR_NUMBER" --json reviewThreads` and read unresolved threads from the returned `reviewThreads` field.
51
+ - Good: If GraphQL is required, include the full `gh api graphql ...` command, the full query, variables, and response path.
52
+
22
53
  ## Versioning strategy
23
54
 
24
55
  - Use SemVer in `package.json`.
package/agents/default.md CHANGED
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  description: Minimal default robot agent
3
3
  mode: primary
4
+ temperature: 2
4
5
  preload_commands: software-design, writing-tests
5
6
  ---
6
7
 
@@ -15,6 +16,7 @@ You are a tool that takes user commands and produces responses.
15
16
  - Never flatter, or mirror user emotion.
16
17
  - Use neutral phrasing such as "That is correct" or "That may not be correct" when confirming or challenging.
17
18
  - Keep responses concise, factual, and operational but use simple and clear terminology rather than advanced vocabulary and complex jargon.
19
+ - Use plain human language. Avoid stacked phrases such as "current PR branch lint config" or "invalid PR description lint output". Name the exact thing instead, such as the file, command, tool result, or PR description section.
18
20
 
19
21
 
20
22
  # Behavior rules (non-negotiable, 100% adherence mandatory)
@@ -27,4 +29,6 @@ You are a tool that takes user commands and produces responses.
27
29
 
28
30
  - NEVER bypass rules or do workarounds to make a problem go away. If there are lint violations in code, fix the violations don't disable linting. If test coverage thresholds are not met, add tests, don't ignore code from coverage.
29
31
 
32
+ - Do not sacrifice code quality or test coverage to satisfy lint rules. These rules are not objectives; they are signs that the code needs to be split, simplified, or clarified.
33
+
30
34
  - Tight feedback loops: validate your work regularly. Don't modify 50 files and then run lint and find 100 errors. Run lint after each file edit. Make small regular commits.
package/agents/tdd.md CHANGED
@@ -2,5 +2,6 @@
2
2
  description: TDD-focused agent with preloaded process commands
3
3
  mode: primary
4
4
  extends: default
5
+ temperature: 2
5
6
  preload_commands: tdd-process, tactical-ddd
6
7
  ---
package/commands/plan.md CHANGED
@@ -2,81 +2,171 @@
2
2
  description: Create an implementation plan.
3
3
  ---
4
4
 
5
- Plan:
5
+ Plan request:
6
6
  $ARGUMENTS
7
7
 
8
- Create a detailed implementation plan broken down into slices of functionality and not layer. Good Example: "Fuzzy searching on first name" is a slice of value. Bad Example: "Add data types" is just a layer of code that needs to be assembled later.
8
+ Create an implementation plan for the request. The plan must sequence value slices, not implementation layers.
9
9
 
10
- Search all relevant existing code. If some of the code is in other repos, look there as well. Don't be lazy, more too much research is better than not enough. If you're unsure ask the user. As a general rule, if the supporting documenting or existing code references another repository, you should almost certainly be looking there.
10
+ ## Clarification gate
11
11
 
12
- For each slide of value, challenge if it's needed. Find supporting evidence.
12
+ Before creating the final plan, resolve every question that affects the plan.
13
13
 
14
- ## Context
14
+ First try to answer questions through available evidence:
15
+ - existing code
16
+ - source files and tests
17
+ - repository documentation
18
+ - issues and pull requests
19
+ - Notion pages
20
+ - linked design documents
21
+ - external API documentation
22
+ - related repositories mentioned by code or documentation
15
23
 
16
- The plan starts with a context section explaining the problem that is being solved along with any relevant information like constraints. It also references any existing materials like notion pages. Context should be rich so that an engineer has all the information they need.
24
+ Do not ask the user questions already answered by evidence. Use the evidence and cite it in the plan.
17
25
 
18
- ## Slices
26
+ Ask the user only when evidence cannot answer a plan-affecting question, or when the answer is a product or business decision.
19
27
 
20
- List each slice of value that needs to be delivered and justify why it is necessary in a table with the column headings `slice`, `description`, `justification`.
28
+ Check for missing information that would change:
29
+ - the problem being solved
30
+ - user value
31
+ - scope
32
+ - explicit out-of-scope behavior
33
+ - constraints
34
+ - related code, docs, issues, Notion pages, or repositories
35
+ - dependencies or external systems
36
+ - acceptance and validation outcomes
37
+ - risks or edge cases
21
38
 
22
- ## Task checklist
39
+ If any plan-affecting question remains after research, do not create the final plan. Respond only with this format:
23
40
 
24
- A plan is broken down into tasks. One task for each slice of value like "Fuzzy searching on first name" and each task is broken down into subtask. The task itself and each subtask are checklist items. This is crucial so that progress can be recorded by the engineer.
41
+ ```md
42
+ ## Clarification needed
25
43
 
26
- Tasks should be detailed so that an engineer has all the information they need to implement the task.
44
+ <brief explanation of what could not be resolved through research>
27
45
 
28
- ## Linting
46
+ 1. <question>
47
+ Reason: <why this affects the plan>
48
+ Why evidence could not answer it: <what was checked and why it was insufficient>
49
+ Recommended: <optional recommendation with evidence, if available>
50
+ ```
29
51
 
30
- Include a lint check before each commit:
31
- - get the staged changed `.ts` and `.tsx` files
32
- - run `nt_skillz_lint` on the changed files
33
- - do not create the commit unless the lint check passes
52
+ Ask at most 5 clarification questions in one response. Ask only questions that materially affect the plan. If more than 5 plan-affecting questions exist, ask the 5 highest-impact questions first.
34
53
 
35
- ## Test Coverage
54
+ A recommended answer is not an assumption. Do not proceed with the final plan using a recommended answer until the user confirms it or provides a different answer.
36
55
 
37
- Include a 100% Vitest coverage check before each commit:
38
- - get the staged changed `.ts` and `.tsx` files
39
- - ignore tests, declaration files, config files, and fixtures
40
- - run `/nt-skillz:vitest-coverage <file>` for each remaining file
41
- - do not create the commit unless every remaining file passes with 100% Vitest coverage
56
+ The final plan must not contain open questions.
42
57
 
43
- ## Software design & architecture
58
+ ## Research expectations
59
+
60
+ Search all relevant existing code and documentation before finalizing the plan. If code or documentation references another repository that may affect the plan, inspect that repository before finalizing the plan.
61
+
62
+ If a required repository, document, issue, Notion page, or other evidence source is inaccessible and that evidence affects the plan, stop and report the blocker using the clarification-needed format. Do not guess and do not create the final plan.
63
+
64
+ Do not search unrelated repositories.
65
+
66
+ Every factual claim about existing behavior, constraints, dependencies, or related systems must be backed by evidence from code, docs, issues, Notion pages, linked materials, or referenced repositories. If no evidence was found, do not state the claim as fact.
67
+
68
+ ## Planning level
69
+
70
+ The proposed solution may discuss software and technical direction at a planning level. Include only solution design details that are strictly required to explain the plan or are directly supported by existing code and documentation.
71
+
72
+ Do not add new design elements just because they seem useful. Leave non-essential component structure, file layout, classes, functions, internal boundaries, and implementation mechanics to the software design and implementation steps.
73
+
74
+ Validation must describe observable outcomes and required quality gates. Do not prescribe exact test file names, test helper structure, mocks, fixtures, or implementation-level test mechanics unless they already exist and are directly relevant evidence.
75
+
76
+ ## Value slices
77
+
78
+ A value slice is a user-observable capability or behavior.
79
+
80
+ Good value slice: "Fuzzy searching on first name".
81
+ Bad value slice: "Add data types".
82
+
83
+ For each slice, challenge whether it is necessary. The `justification` column must explain the evidence that the slice is necessary. If the evidence is weak or unclear, do not include the slice in the final plan; ask for clarification instead.
84
+
85
+ Tasks must be organized by value slice so each slice can be implemented and verified independently.
86
+
87
+ Map technical work to the value slice it supports. For example, models, services, interfaces, schemas, and tests belong inside the value-slice task they enable.
44
88
 
45
- Leave a placeholder `<software design and architecture>`. This will be filled in by a following command.
89
+ Do not create standalone top-level tasks for technical layers unless that task independently delivers user value.
46
90
 
47
- ### Template
91
+ Each value slice must include an independent verification outcome. The outcome must prove that slice works without relying on later slices. If a slice cannot be independently verified, merge it with the slice that makes it valuable or explain why it must remain separate.
92
+
93
+ ## Output format
94
+
95
+ If the final output does not follow the required template exactly, the plan is invalid.
96
+
97
+ Do not add extra top-level sections. Do not rename headings. Do not omit required sections.
98
+
99
+ The final plan may use only these top-level headings:
100
+ - `## Context`
101
+ - `## Slices`
102
+ - `## Software design & architecture`
103
+ - `## Tasks`
104
+
105
+ Return exactly this Markdown structure:
48
106
 
49
107
  ```md
50
108
  ## Context
51
109
 
52
- - Problem:
53
- - Constraints:
54
- - Related materials:
110
+ ### Problem
55
111
 
56
- ## Software design & architecture
112
+ <prose only; describe the problem being solved, not the solution>
113
+
114
+ ### Evidence reviewed
115
+
116
+ <prose only; summarize the code, documentation, linked materials, and repositories that informed the plan. Include specific paths, document names, links, or repository names.>
117
+
118
+ ### Constraints
57
119
 
120
+ <prose only; describe constraints, dependencies, existing materials, affected systems, and known limits>
58
121
 
122
+ ### Proposed solution
123
+
124
+ <prose only; describe the intended technical direction at a planning level. Include only solution design details that are strictly required to explain the plan or are directly supported by existing code and documentation.>
125
+
126
+ ## Slices
127
+
128
+ | slice | description | justification |
129
+ |---|---|---|
130
+ | <value slice> | <user-observable capability or behavior> | <evidence that this slice is necessary> |
131
+
132
+ ## Software design & architecture
133
+
134
+ <software design and architecture>
59
135
 
60
136
  ## Tasks
61
137
 
62
- - [ ] Task 1: <slice of functionality>
63
- - [ ] Subtask 1.1
64
- - [ ] Subtask 1.2
65
- - [ ] Run `nt_skillz_lint` on changed `.ts` and `.tsx` files
66
- - [ ] Verify 100% test coverage using `/nt-skillz:vitest-coverage <file>`
67
- - [ ] Commit the changes
68
- - [ ] Task 2: <slice of functionality>
69
- - [ ] Subtask 2.1
70
- - [ ] Subtask 2.2
71
- - [ ] Run `nt_skillz_lint` on changed `.ts` and `.tsx` files
72
- - [ ] Verify 100% test coverage using `/nt-skillz:vitest-coverage <file>`
73
- - [ ] Commit the changes
138
+ - [ ] Task: <value slice>
139
+ - [ ] Read the software design and architecture section before implementation.
140
+ - [ ] Implement the value slice according to the software design and architecture.
141
+ - [ ] Verify outcome: <observable behavior that proves the slice works independently>.
142
+ - [ ] Verify the implementation aligns with the software design and architecture.
143
+ - [ ] Run `nt_skillz_lint` on changed `.ts` and `.tsx` files.
144
+ - [ ] Verify 100% test coverage using `/nt-skillz:vitest-coverage <file>`.
145
+ - [ ] Commit the changes.
74
146
  ```
75
147
 
76
- ## Important Notes
148
+ Tasks must be planning-level only. They must describe the value slice, intended outcome, and required validation. They must not prescribe internal design, files, components, classes, functions, or modules unless those details already exist and are directly relevant evidence.
149
+
150
+ ## Final self-check
151
+
152
+ Before returning the final plan, verify:
153
+ - The output uses exactly the required top-level headings.
154
+ - The output does not add extra top-level sections.
155
+ - The Problem section contains no solution language.
156
+ - The Context subsections are prose, not bullet lists.
157
+ - The Evidence reviewed section names specific sources.
158
+ - The Slices table exists and every slice includes evidence-backed justification.
159
+ - Every task maps to exactly one value slice.
160
+ - Every task stays at planning level and avoids premature design details.
161
+ - Every task includes an observable verification outcome.
162
+ - The software design and architecture section contains only `<software design and architecture>`.
163
+ - No assumptions are presented as facts.
164
+ - No open questions remain.
77
165
 
78
- - Stop if you cannot implement the plan as described. If the proposed design or functionality will not work in practice, discuss with the user
166
+ After producing the final plan, stop. Do not implement. Do not run the software design and architecture step. Wait for the user to invoke the next command.
79
167
 
80
- - Ensure you mark of each subtask when complete
168
+ ## Important notes
81
169
 
82
- - If parts of the plan are incomplete, missing, or placeholders refuse to implement and tell the user. Do not implement a flawed plan
170
+ - Stop if the plan cannot be completed without unanswered plan-affecting questions.
171
+ - If parts of a plan are incomplete, missing, or placeholders other than `<software design and architecture>` remain, refuse to implement and tell the user.
172
+ - During implementation, ensure each subtask is marked off when complete.
@@ -0,0 +1,138 @@
1
+ ---
2
+ description: Resolve approved GitHub pull request feedback with local evidence, validation, replies, and thread resolution
3
+ ---
4
+
5
+ Resolve GitHub pull request feedback for:
6
+ $ARGUMENTS
7
+
8
+ ## Required PR input
9
+
10
+ 1. Extract both values from `$ARGUMENTS`:
11
+ - pull request number
12
+ - full pull request URL
13
+ 2. If either value is missing, stop and ask for the missing value.
14
+ 3. Do not fetch repository metadata with `gh repo view`; use the local worktree and the provided pull request values.
15
+
16
+ ## Feedback discovery
17
+
18
+ 1. Call the `nt_skillz_pr_feedback` tool with exactly these arguments:
19
+
20
+ ```json
21
+ {
22
+ "pullRequestNumber": "<pull request number>",
23
+ "pullRequestUrl": "<full pull request URL>"
24
+ }
25
+ ```
26
+
27
+ 2. Use the tool output as the feedback source of truth.
28
+ 3. Do not replace the tool with ad hoc `gh pr view`, `gh pr diff`, `gh api`, or local-only review discovery commands.
29
+
30
+ ## Approval plan format
31
+
32
+ Before editing files, present every unresolved thread from the tool output using exactly this format:
33
+
34
+ ```md
35
+ ## Thread <thread_id>
36
+
37
+ ### Reviewer feedback
38
+ <copy the full Reviewer feedback section from the tool output>
39
+
40
+ ### Review context
41
+ <copy the full Review context section from the tool output>
42
+
43
+ ### Current local code
44
+ <copy the full Current local code section from the tool output>
45
+
46
+ ### Problem analysis
47
+ - Problem: <specific defect, missing test, design issue, or documentation mismatch visible in the feedback, diff hunk, and local code>
48
+ - Evidence: <specific evidence from the reviewer comment, diff hunk, and current local code>
49
+ - Scope: <exact files and behavior affected>
50
+
51
+ ### Proposed change
52
+ - Edit `<path>`: <exact code, test, or documentation behavior change>
53
+ - Leave unchanged: <exact files or behavior that will not be touched>
54
+
55
+ ### Validation
56
+ - During edits: call `nt_skillz_lint` after each small TypeScript change with the changed `.ts` or `.tsx` file paths only.
57
+ - Before commit: run `git diff --name-only --diff-filter=ACMR HEAD -- '*.ts' '*.tsx'`, then call `nt_skillz_lint` with every returned file path.
58
+ - Before commit coverage: run `git diff --name-only --diff-filter=ACMR HEAD -- '*.ts' '*.tsx'`, then run `/nt-skillz:vitest-coverage <file>` for each returned path and require 100% coverage unless the run prints `SKIP:`.
59
+ - Additional command: `<exact project command required to verify this feedback, or "none">`
60
+
61
+ ### GitHub reply to post after validation passes
62
+ `[Resolve] <exact response>`
63
+
64
+ ### Resolution action
65
+ Resolve thread after successful reply: yes
66
+ ```
67
+
68
+ ## Approval stop
69
+
70
+ 1. Stop after presenting the approval plan.
71
+ 2. Ask which thread fixes are approved.
72
+ 3. Do not edit files until the user approves specific thread IDs.
73
+ 4. If the user approves only some threads, edit only the approved thread fixes.
74
+ 5. If a thread cannot be mapped to a concrete file and behavior change, ask for clarification instead of editing.
75
+
76
+ ## Implementation standards
77
+
78
+ 1. Implement approved fixes only.
79
+ 2. Do not make unrelated refactors, formatting changes, dependency changes, or drive-by cleanups.
80
+ 3. After each small TypeScript code change, call `nt_skillz_lint` with only the `files` argument for the changed `.ts` or `.tsx` file or files from that step.
81
+ 4. Do not pass `base` or `head` to `nt_skillz_lint` during normal implementation work.
82
+ 5. Fix all lint errors on new code before continuing.
83
+ 6. If lint fails on existing code, ignore only errors that are unrelated and not near the changed code.
84
+ 7. Line-length limits do not count as existing code; fix line-length errors caused by new code.
85
+
86
+ ## Required validation before any commit
87
+
88
+ Before committing feedback fixes:
89
+
90
+ 1. Run `git diff --name-only --diff-filter=ACMR HEAD -- '*.ts' '*.tsx'`.
91
+ 2. Call `nt_skillz_lint` with `files` set to every path returned by step 1.
92
+ 3. Run `/nt-skillz:vitest-coverage <file>` for each path returned by step 1.
93
+ 4. Ignore only coverage runs that print a `SKIP:` line.
94
+ 5. Do not commit unless every remaining file has 100% Vitest coverage or the user explicitly confirms that 100% coverage is impossible for the relevant component.
95
+ 6. Run each approved thread's `Additional command` when the value is not `none`.
96
+ 7. Do not commit if any required validation fails.
97
+
98
+ ## Commit rule
99
+
100
+ 1. Do not commit unless the user explicitly requests a commit.
101
+ 2. If the user requests a commit, run the required validation before the commit.
102
+ 3. Commit only files changed for approved thread fixes.
103
+
104
+ ## GitHub reply and thread resolution
105
+
106
+ After validation passes for an approved thread:
107
+
108
+ 1. Reply to the thread with this exact GraphQL mutation:
109
+
110
+ ```bash
111
+ gh api graphql \
112
+ -f threadId='<thread_id>' \
113
+ -f body='[Resolve] <approved response body>' \
114
+ -f query='mutation($threadId: ID!, $body: String!) { addPullRequestReviewThreadReply(input: { pullRequestReviewThreadId: $threadId, body: $body }) { comment { url } } }'
115
+ ```
116
+
117
+ 2. Resolve the thread with this exact GraphQL mutation:
118
+
119
+ ```bash
120
+ gh api graphql \
121
+ -f threadId='<thread_id>' \
122
+ -f query='mutation($threadId: ID!) { resolveReviewThread(input: { threadId: $threadId }) { thread { id isResolved } } }'
123
+ ```
124
+
125
+ 3. Do not resolve a thread before the reply mutation succeeds.
126
+ 4. Do not resolve a thread when validation failed.
127
+ 5. Do not resolve a thread that was not approved by the user.
128
+
129
+ ## Final report
130
+
131
+ Report only:
132
+
133
+ - approved thread IDs fixed
134
+ - unapproved thread IDs left untouched
135
+ - validation commands run and pass/fail status
136
+ - GitHub reply URLs returned by the reply mutation
137
+ - resolved thread IDs returned by the resolution mutation
138
+ - blockers that require user action
@@ -49,26 +49,19 @@ Provide the subagent with:
49
49
  - the changed file list
50
50
  - the changed diff context
51
51
 
52
- ## Test Coverage Analysis
53
-
54
- Add a test coverage analysis results section to the PR description:
55
-
56
- 1. For each changed path from `gh pr diff <pr> --name-only`, run `/nt-skillz:vitest-coverage <file>`.
57
- 2. Ignore only runs that print a `SKIP:` line.
58
- 3. If every run prints a `SKIP:` line, use one fenced `text` block containing `No changed TypeScript source files.` as the coverage content.
59
- 4. Otherwise, build the PR coverage block in this exact shape:
60
- - `<!-- nt-skillz-coverage:start -->`
61
- - `## Coverage`
62
- - one `### \`<file>\`` heading for each non-`SKIP:` file
63
- - one fenced `text` block containing the exact raw `/nt-skillz:vitest-coverage <file>` output directly under that file heading
64
- - `<!-- nt-skillz-coverage:end -->`
65
- 5. Run `gh pr view <pr> --json body --jq '.body'` and use the returned text as the current PR body.
66
- 6. If the current PR body already contains both marker lines, replace only the text from `<!-- nt-skillz-coverage:start -->` through `<!-- nt-skillz-coverage:end -->` with the new coverage block.
67
- 7. If the current PR body does not contain both marker lines, append the new coverage block to the end of the PR body separated by two newlines.
68
- 8. Write the updated PR body to a temporary file.
69
- 9. Run `gh pr edit <pr> --body-file <temporary-file>`.
70
- 10. Do not summarize, interpret, or paraphrase the coverage output.
71
- 11. Do not modify any other part of the PR body.
52
+ ## Tool-Generated PR Description Sections
53
+
54
+ Add generated lint and coverage sections to the PR description:
55
+
56
+ 1. Invoke the `nt_skillz_lint` tool with `mode: "pr-review"` and the resolved PR identifier.
57
+ 2. Invoke the `nt_skillz_vitest_coverage` tool with `mode: "pr-review"` and the resolved PR identifier.
58
+ 3. Use the exact markdown returned by each tool. Do not summarize, interpret, paraphrase, or reformat tool output.
59
+ 4. Run `gh pr view <pr> --json body --jq '.body'` and use the returned text as the current PR body.
60
+ 5. If the current PR body already contains both marker lines for a generated section, replace only the text from the start marker through the end marker with the new tool output for that section.
61
+ 6. If the current PR body does not contain both marker lines for a generated section, append the new tool output to the end of the PR body separated by two newlines.
62
+ 7. Write the updated PR body to a temporary file.
63
+ 8. Run `gh pr edit <pr> --body-file <temporary-file>`.
64
+ 9. Do not modify any other part of the PR body.
72
65
 
73
66
  ## Basic PR checks
74
67
 
@@ -1,11 +1,19 @@
1
1
  import { CLEAR_DONT_STOP_COMMAND_NAME, DONT_STOP_COMMAND_NAME, } from "./register.js";
2
2
  import { createDontStopState } from "./state.js";
3
+ const statusStartMarker = "<dont-stop-status>";
4
+ const statusEndMarker = "</dont-stop-status>";
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
3
8
  function unwrapResponse(result) {
4
- return typeof result === "object" && result !== null && "data" in result ? result.data : result;
9
+ if (isRecord(result) && "data" in result) {
10
+ return result.data;
11
+ }
12
+ return result;
5
13
  }
6
14
  function normalizeCriteria(value) {
7
15
  return value
8
- .split(/\n|;/)
16
+ .split(/[\n;]/)
9
17
  .map((item) => item.replace(/^\s*[-*]\s*/, "").trim())
10
18
  .filter(Boolean);
11
19
  }
@@ -41,43 +49,70 @@ function buildSystemInstruction(criteria) {
41
49
  ].join("\n");
42
50
  }
43
51
  function parseAssistantStatus(text) {
44
- const match = text.match(/<dont-stop-status>([\s\S]*?)<\/dont-stop-status>/i);
45
- if (!match)
52
+ const lowerText = text.toLowerCase();
53
+ const startIndex = lowerText.indexOf(statusStartMarker);
54
+ const endIndex = lowerText.indexOf(statusEndMarker);
55
+ if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex)
46
56
  return null;
47
- const body = match[1];
48
- const stateMatch = body.match(/^\s*state\s*:\s*(continue|completion-requested|blocked-requested)\s*$/im);
49
- const justificationMatch = body.match(/^\s*justification\s*:\s*(.+)\s*$/im);
50
- const reasonMatch = body.match(/^\s*reason\s*:\s*(.+)\s*$/im);
57
+ const body = text.slice(startIndex + statusStartMarker.length, endIndex);
58
+ const fields = Object.fromEntries(body
59
+ .split("\n")
60
+ .map((line) => line.trim())
61
+ .filter((line) => line.includes(":"))
62
+ .map((line) => [line.slice(0, line.indexOf(":")).trim(), line.slice(line.indexOf(":") + 1).trim()]));
63
+ const state = parseAssistantState(fields.state);
51
64
  return {
52
- state: stateMatch?.[1] ?? "continue",
53
- justification: justificationMatch?.[1]?.trim() ?? "",
54
- reason: reasonMatch?.[1]?.trim() ?? "",
65
+ state,
66
+ justification: readStatusField(fields, "justification"),
67
+ reason: readStatusField(fields, "reason"),
55
68
  };
56
69
  }
57
- async function showToast(client, body) {
58
- try {
59
- await client.tui.showToast({ body });
70
+ function readStatusField(fields, key) {
71
+ const value = fields[key];
72
+ if (typeof value === "string") {
73
+ return value;
60
74
  }
61
- catch { }
75
+ return "";
76
+ }
77
+ function parseAssistantState(value) {
78
+ if (value === "completion-requested" || value === "blocked-requested") {
79
+ return value;
80
+ }
81
+ return "continue";
82
+ }
83
+ async function showToast(client, body) {
84
+ const toastWasShown = await client.tui.showToast({ body }).then(() => true, () => false);
85
+ if (!toastWasShown)
86
+ return;
87
+ }
88
+ function isMessagePart(value) {
89
+ return isRecord(value);
90
+ }
91
+ function isSessionMessage(value) {
92
+ if (!isRecord(value))
93
+ return false;
94
+ if (!("parts" in value))
95
+ return true;
96
+ return Array.isArray(value.parts) && value.parts.every(isMessagePart);
97
+ }
98
+ function readAssistantText(message) {
99
+ const parts = message.parts ?? [];
100
+ return parts
101
+ .filter((part) => part.type === "text" && typeof part.text === "string")
102
+ .map((part) => part.text)
103
+ .join("\n");
62
104
  }
63
105
  async function getLatestAssistantStatus(client, sessionID) {
64
106
  const result = unwrapResponse(await client.session.messages({ path: { id: sessionID } }));
65
- const messages = Array.isArray(result) ? result : [];
66
- for (let index = messages.length - 1; index >= 0; index -= 1) {
67
- const entry = messages[index];
68
- if (entry.info?.role !== "assistant")
69
- continue;
70
- const text = (entry.parts ?? [])
71
- .filter((part) => part.type === "text" && typeof part.text === "string")
72
- .map((part) => part.text)
73
- .join("\n");
74
- return parseAssistantStatus(text);
75
- }
107
+ const messages = Array.isArray(result) ? result.filter(isSessionMessage) : [];
108
+ const latestAssistantMessage = [...messages].reverse().find((entry) => entry.info?.role === "assistant");
109
+ if (latestAssistantMessage)
110
+ return parseAssistantStatus(readAssistantText(latestAssistantMessage));
76
111
  return null;
77
112
  }
78
113
  function buildContinuationPrompt(criteria, status) {
79
114
  const reportedState = status?.state ?? "missing";
80
- const reason = status?.reason || "none";
115
+ const reason = status?.reason ?? "none";
81
116
  return [
82
117
  "dont-stop remains active.",
83
118
  "",
@@ -96,10 +131,7 @@ function buildContinuationPrompt(criteria, status) {
96
131
  function buildReviewRequestKey(status) {
97
132
  return `${status.state}:${status.reason}`;
98
133
  }
99
- async function notifyReviewRequested(client, state, sessionID, status) {
100
- const session = state.get(sessionID);
101
- if (!session)
102
- return;
134
+ async function notifyReviewRequested(client, session, status) {
103
135
  const reviewKey = buildReviewRequestKey(status);
104
136
  if (session.pendingReviewKey === reviewKey)
105
137
  return;
@@ -116,16 +148,18 @@ function getDeletedSessionID(event) {
116
148
  if (event.type !== "session.deleted")
117
149
  return undefined;
118
150
  const properties = event.properties;
119
- if (!properties || typeof properties !== "object")
151
+ if (!isRecord(properties))
120
152
  return undefined;
121
153
  const info = properties.info;
122
- return typeof info?.id === "string" ? info.id : undefined;
154
+ if (!isRecord(info))
155
+ return undefined;
156
+ return typeof info.id === "string" ? info.id : undefined;
123
157
  }
124
158
  function getIdleSessionID(event) {
125
159
  if (event.type !== "session.idle")
126
160
  return undefined;
127
161
  const properties = event.properties;
128
- if (!properties || typeof properties !== "object")
162
+ if (!isRecord(properties))
129
163
  return undefined;
130
164
  const sessionID = properties.sessionID;
131
165
  return typeof sessionID === "string" ? sessionID : undefined;
@@ -181,7 +215,7 @@ async function handleEvent(client, state, event) {
181
215
  try {
182
216
  const latestStatus = await getLatestAssistantStatus(client, sessionID);
183
217
  if (latestStatus?.state === "completion-requested" || latestStatus?.state === "blocked-requested") {
184
- await notifyReviewRequested(client, state, sessionID, latestStatus);
218
+ await notifyReviewRequested(client, session, latestStatus);
185
219
  return;
186
220
  }
187
221
  session.pendingReviewKey = undefined;