@nt-ai-lab/opencode-skillz 0.3.7 → 0.3.9

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.
@@ -0,0 +1,202 @@
1
+ # Component Design Prompt
2
+
3
+ Work with the user to design the software components for a new feature. Help the user to understand how the new code will look to avoid nasty surprises when reviewing the PR that require a lot of wasted time fixing problems with the design.
4
+
5
+ Focus on presenting diverse options. The user will choose their preferred option. Don't hesitate to iterate and ask questions rather than making assumptions.
6
+
7
+ 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. You can't design new code without understanding how it fits into the bigger picture.
8
+
9
+ ## Pre-flight checklist
10
+
11
+ 1. If the requirements are missing or unclear and you need to make assumptions, stop and discuss the options with the user.
12
+ 2. If is is not clear where the new code should be placed, or there are multiple viable options, stop and discuss the options with the user.
13
+ 3. If you have even 1% doubt on anything required to produce the design, stop and discuss the options with the user.
14
+
15
+ ## Task
16
+
17
+ Generate 3 or more component design options.
18
+
19
+ Options must be as unique as possible.
20
+
21
+ Example criteria for identifying unique options:
22
+
23
+ 1. number of components => all of the code in 1 monolithic script vs breakign each fine-grained responsiblity into it's own component
24
+ 2. size of components
25
+ 3. touching existing code vs adding new code
26
+ 4. introducing dependencies
27
+ 5. coupling vs cohesion
28
+ 6. DDD vs non-DDD
29
+
30
+ If two options have the same components with the same responsibilities, they are not unique.
31
+
32
+ ## Component Naming Guidelines
33
+
34
+ All components must have names that comply with the following guidelines.
35
+
36
+ ### Intention Revealing
37
+
38
+ A name must describe as clearly and precisely as possible what the the thing is and does.
39
+
40
+ Good examples:
41
+ - `aysnc-file-reader`
42
+ - `date-selector`
43
+ - `tax-calculator`
44
+
45
+ Bad examples:
46
+ - `data-manager`: what kind of data? how does it manage the data? The name tells us almost nothing here.
47
+ - `orders-service`: it does something related to orders but we don't know what? Easy to dump multiple unrelated things into this (like domain logic and external service calls)
48
+
49
+ ### Domain-driven
50
+
51
+ A name should use established domain terminology wherever possible and should not invent new words and phrases that do not exist in the domain
52
+
53
+ ### Compound noun phrases
54
+
55
+ Use this pattern as the default: `[Domain Object][Business Action][Role Noun]`.
56
+
57
+ Example: `InvoicePaymentCollector`
58
+
59
+ - `Invoice` = domain object
60
+ - `Payment` = business object/action target
61
+ - `Collector` = role noun / responsibility noun
62
+
63
+ ### Forbidden terms
64
+
65
+ The following should be avoided at all costs unless truly necessary and reflective of the business domain:
66
+
67
+ - `util`
68
+ - `helper`
69
+ - `manager`
70
+ - `service`
71
+
72
+ If you are about to use one of these words, first look for a more precise alternative. "what kind of util?", "what kind of service?"...
73
+
74
+ ## Component Design Guidelines
75
+
76
+ The following guidelines should be applied when designing components.
77
+
78
+ ### Layering
79
+
80
+ Components should be put into the correct layer based on the type of logic they contain.
81
+
82
+ - `domain`: Business rules and domain logic. This should be kept pure and isolated from technical concerns like database transactions. A domain expert should be able to read it and understand it
83
+
84
+ - `use-case`: The use-case layer is like a menu, it describes the operations the application suppors like `place-order`, `cancel-order` and so on. Each use case is responsible for orchestrating domain logic and technical concerns. The most common pattern is start transaction => load domain object => invoke domain object => save domain object => return results
85
+
86
+ - `infra`: Technical capabilities live in here like database transactions, persistence, external service clients and so on. Use dedicated sub-folders to properly organize like `/persistence`, `/external-service`
87
+
88
+ - `/infra/{gateway}`: This sub-layer handles receiving inputs from the outside world and returning responses to the outside world, like gateways sitting at the edge of the application. It's common to see http controllers and event handlers in this layer. Examples of `{gateway}` include `http`, `event-handlers`
89
+
90
+ Each codebase has it's own layering conventions that should be respected, but when no layering convention exists use the above as the default.
91
+
92
+ ### Component Archetypes
93
+
94
+ The following are common component archetypes (non-exhaustive, do not be constrained by this list). If a comoponent matches the description of multiple of these, splitting into multiple component each aligned with a single archetype is an alternative option. However, when components are small are handle 1 thing well, it's ok to ecompass multiple archetypes.
95
+
96
+ 1. **entrypoint**: the part of the application that talks to the outside world, like a http controller. A thin layer that parses inputs and hands off to a `use-case` and formats the response
97
+ 2. **coordinator**: Responsible for orchesstrating multiplpe components that starts a transaction, loads a database object and invokes an operation on it. A `use-case`: is a good example of a coordinator
98
+ 3. **stateless calculator**: takes an input, makes a decision, and returns an output.
99
+ 4. **mapper**: mapps from one format to another
100
+ 5. **aggregate**: manages the lifecycle of a domain concept protecting invariants. Will only operations to be performed if they permitted in the current state.
101
+ 6. **value-object**: an abstraction on top a certain piece of data like a `date`. Provides an API with domain terminology for operating on the underlying value. The underlying value cannot be accessed directly.
102
+ 7. **external service client**: provides methods that represent operations that can be called on a 3rd party service over the network
103
+ 8. **validator**: accepts as input some data and indicates a result indicating whether the data is valid according to specify rules, like an email validator
104
+ 9. **factory**: responsible for the construction of an object.
105
+ 10. **repository**: persists and loads objects, typically to and from a database. In domain-driven design a repository is responsible for saving and loading entire aggregates only and should never be used for partial or ad-hoc loading.
106
+ 11. **query service**: used to fetch information about an application, typically by querying a database. In domain-driven design, query services are used for read operations and repositories are used for write operations (although repositories can be used for read operations as well)
107
+
108
+ ### General guidelines
109
+
110
+ 1. **Maximum file size is 400 lines**, enforced by lint rules. A component must be decomposed into multiple smaller components when it reaches this limit
111
+
112
+ ## Output Format
113
+
114
+ # Design Options: [Feature Name]
115
+
116
+ ## Option 1: [Name]
117
+
118
+ Describe this option by outlining the philosophy behind it and it's key characteristics.
119
+
120
+ ### Diagram
121
+
122
+ Rules:
123
+ - Mark every node `[existing]`, `[new]`, or `[changed]`.
124
+ - Show actual dependencies and calls, not a fake straight-line sequence.
125
+ - A line means the source component directly calls or depends on the target component.
126
+ - Do not connect two components if they do not directly call each other.
127
+ - Use branches when one component calls multiple dependencies.
128
+ - Label every line with the request, method call, response, event, or query.
129
+ - Keep it small.
130
+
131
+ ```text
132
+ Client
133
+ |
134
+ | request / methodCall()
135
+ v
136
+ [changed] ExistingEntryPoint
137
+ |
138
+ | methodCall()
139
+ v
140
+ [new] NewComponent
141
+ |\
142
+ | \ callDependencyA()
143
+ | v
144
+ | [existing] ExistingDependencyA
145
+ |
146
+ | callDependencyB()
147
+ v
148
+ [new] NewDependencyB
149
+ ```
150
+
151
+ ### Components
152
+
153
+ | Component | Status | Role Archetypes | Responsibilities | Estimated Size |
154
+ |---|---|---|---|---|
155
+ | `ComponentName` | New / Existing / Changed | `entrypoint`, `coordinator`, `custom:bulk-copy-script` | <ul><li>Responsibility one</li><li>Responsibility two</li></ul> | Small / Medium / Large, or estimated lines |
156
+
157
+ **note:**comopnent names must adhere to the component naming guidelines defined in this document
158
+
159
+ **note:** role archetypes must use names from the Component Archetypes section when applicable. If a component does not match one of the listed archetypes, use a custom archetype prefixed with `custom:`. Example: `custom:bulk-copy-script`.
160
+
161
+ ### New Dependencies
162
+
163
+ | Dependency | Status | Used By | Purpose |
164
+ |---|---|---|---|
165
+ | `DependencyName` | New / Existing / Changed | `ComponentName` | One sentence |
166
+
167
+ ### Code Shape
168
+
169
+ List the main new or changed files only.
170
+
171
+ ```text
172
+ src/
173
+ api/
174
+ ExistingEntryPoint.ts [changed]
175
+ feature/
176
+ NewComponent.ts [new]
177
+ ```
178
+
179
+ ### Why This Option Is Unique
180
+
181
+ Explain the uniqueness using only these criteria:
182
+
183
+ - number of components
184
+ - size of components
185
+ - touching existing code vs adding new code
186
+ - introducing dependencies
187
+
188
+ ## Option 2: [Name]
189
+
190
+ Use the same format as Option 1.
191
+
192
+ ## Option 3: [Name]
193
+
194
+ Use the same format as Option 1.
195
+
196
+ ## Recommendation
197
+
198
+ Recommend one option in 1 short paragraph.
199
+
200
+ ## Approval
201
+
202
+ Ask the user which option to approve, reject, or combine.
@@ -16,3 +16,12 @@ After each small TypeScript code change, call `nt_skillz_lint` with only the `fi
16
16
  - all lint errors on new code must be addressed before continuing
17
17
  - if the lint fails on existing code, ignore the error unless it is very close to the new code
18
18
  - line-length limits do not count as existing code; if new code causes a file-length lint error, it must be fixed
19
+
20
+ ## Test Coverage
21
+
22
+ Before each commit:
23
+
24
+ 1. Run `git diff --name-only --cached --diff-filter=ACMR -- '*.ts' '*.tsx'`.
25
+ 2. For each returned path, run `/nt-skillz:vitest-coverage <file>`.
26
+ 3. Ignore only runs that print a `SKIP:` line.
27
+ 4. Do not create the commit unless every remaining file has 100% Vitest coverage (or it's impossible to achieve 100% test coverage for the relevant component)
@@ -0,0 +1,82 @@
1
+ ---
2
+ description: Create an implementation plan.
3
+ ---
4
+
5
+ Plan:
6
+ $ARGUMENTS
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.
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.
11
+
12
+ For each slide of value, challenge if it's needed. Find supporting evidence.
13
+
14
+ ## Context
15
+
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.
17
+
18
+ ## Slices
19
+
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`.
21
+
22
+ ## Task checklist
23
+
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.
25
+
26
+ Tasks should be detailed so that an engineer has all the information they need to implement the task.
27
+
28
+ ## Linting
29
+
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
34
+
35
+ ## Test Coverage
36
+
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
42
+
43
+ ## Software design & architecture
44
+
45
+ Leave a placeholder `<software design and architecture>`. This will be filled in by a following command.
46
+
47
+ ### Template
48
+
49
+ ```md
50
+ ## Context
51
+
52
+ - Problem:
53
+ - Constraints:
54
+ - Related materials:
55
+
56
+ ## Software design & architecture
57
+
58
+
59
+
60
+ ## Tasks
61
+
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
74
+ ```
75
+
76
+ ## Important Notes
77
+
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
79
+
80
+ - Ensure you mark of each subtask when complete
81
+
82
+ - If parts of the plan are incomplete, missing, or placeholders refuse to implement and tell the user. Do not implement a flawed plan
@@ -0,0 +1,42 @@
1
+ ---
2
+ description: Run Vitest coverage for a pull request and update the PR description coverage block
3
+ ---
4
+
5
+ Review pull request coverage for:
6
+ $ARGUMENTS
7
+
8
+ ## Software Design Compliance
9
+
10
+ Ensure all new and modified code complies with `/nt-skillz:software-design`
11
+
12
+ Leave in-line feedback on the PR for lines that do not comply. Prefix messages with [Software Design]
13
+
14
+ ## Test Quality
15
+
16
+ Ensure all new and modified tests comply with `/nt-skillz-writing-tests`
17
+
18
+ Leave in-line feedback on the PR for lines that do not comply. Prefix messages with [Test Quality]
19
+
20
+ ## Test Coverage Analysis
21
+
22
+ Add a test coverage analysis results section to the PR description:
23
+
24
+ 1. Resolve the pull request from `$ARGUMENTS`.
25
+ 2. If `$ARGUMENTS` does not identify a PR URL or PR number, stop and ask for one.
26
+ 3. Run `gh pr diff <pr> --name-only`.
27
+ 4. For each changed path from that output, run `/nt-skillz:vitest-coverage <file>`.
28
+ 5. Ignore only runs that print a `SKIP:` line.
29
+ 6. If every run prints a `SKIP:` line, use one fenced `text` block containing `No changed TypeScript source files.` as the coverage content.
30
+ 7. Otherwise, build the PR coverage block in this exact shape:
31
+ - `<!-- nt-skillz-coverage:start -->`
32
+ - `## Coverage`
33
+ - one `### \`<file>\`` heading for each non-`SKIP:` file
34
+ - one fenced `text` block containing the exact raw `/nt-skillz:vitest-coverage <file>` output directly under that file heading
35
+ - `<!-- nt-skillz-coverage:end -->`
36
+ 8. Run `gh pr view <pr> --json body --jq '.body'` and use the returned text as the current PR body.
37
+ 9. 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.
38
+ 10. 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.
39
+ 11. Write the updated PR body to a temporary file.
40
+ 12. Run `gh pr edit <pr> --body-file <temporary-file>`.
41
+ 13. Do not summarize, interpret, or paraphrase the coverage output.
42
+ 14. Do not modify any other part of the PR body.
@@ -0,0 +1,35 @@
1
+ ---
2
+ description: Reusable Vitest coverage workflow for TypeScript source files
3
+ ---
4
+
5
+ Run Vitest coverage for:
6
+ $ARGUMENTS
7
+
8
+ Treat `$ARGUMENTS` as exactly one repository-relative file path and run this exact workflow.
9
+
10
+ 1. If the path does not end with `.ts` or `.tsx`, print exactly `SKIP: not a TypeScript source file` and stop.
11
+ 2. If the path matches any of these patterns, print exactly `SKIP: excluded TypeScript file` and stop:
12
+ - `*.spec.ts`
13
+ - `*.spec.tsx`
14
+ - `*.test.ts`
15
+ - `*.test.tsx`
16
+ - `*.d.ts`
17
+ - `*.config.ts`
18
+ - `*.config.tsx`
19
+ - any path containing `/fixtures/`
20
+ - any path containing `/__fixtures__/`
21
+ 3. Walk upward from the file directory and stop at the nearest directory containing `package.json`. Use that directory as the package root.
22
+ 4. If the file has no ancestor `package.json`, stop and report a blocker.
23
+ 5. Convert the file path to a path relative to the package root.
24
+ 6. From the package root, run exactly this command:
25
+ `vitest related <file> --run --coverage.enabled --coverage.include=<file> --coverage.reporter=text`
26
+ 7. Capture the raw Vitest `text` coverage output for that file.
27
+ 8. This coverage check fails unless the raw output contains a coverage row for `<file>`.
28
+ 9. This coverage check fails unless the coverage row for `<file>` shows all of the following:
29
+ - `100` in `% Stmts`
30
+ - `100` in `% Branch`
31
+ - `100` in `% Funcs`
32
+ - `100` in `% Lines`
33
+ 10. This coverage check fails if the coverage row for `<file>` has any uncovered line numbers.
34
+ 11. If the coverage check fails, print the raw failing Vitest `text` coverage output without summarizing it.
35
+ 12. If the coverage check passes, print the raw Vitest `text` coverage output without summarizing it.
@@ -26,6 +26,7 @@ class LintExecutionError extends Error {
26
26
  super(message);
27
27
  }
28
28
  }
29
+ const ansiEscapeSequencePattern = new RegExp(String.raw `\u001B\[[0-?]*[ -/]*[@-~]`, "g");
29
30
  const toolDirectory = path.dirname(fileURLToPath(import.meta.url));
30
31
  const toolRepositoryRoot = path.resolve(toolDirectory, "..", "..");
31
32
  const eslintConfigPath = path.join(toolRepositoryRoot, "scripts", "living-architecture-eslint.config.mjs");
@@ -130,6 +131,9 @@ function createLintTitle(filePaths, baseReference) {
130
131
  }
131
132
  return "Lint current TypeScript files";
132
133
  }
134
+ function removeAnsiEscapeSequences(value) {
135
+ return value.replaceAll(ansiEscapeSequencePattern, "");
136
+ }
133
137
  async function runEslint(repositoryRoot, lintTargets) {
134
138
  const previousLintRepositoryRoot = process.env.NT_SKILLZ_LINT_REPO_ROOT;
135
139
  process.env.NT_SKILLZ_LINT_REPO_ROOT = repositoryRoot;
@@ -142,7 +146,7 @@ async function runEslint(repositoryRoot, lintTargets) {
142
146
  try {
143
147
  const lintResults = await eslint.lintFiles(lintTargets);
144
148
  const formatter = await eslint.loadFormatter("stylish");
145
- const formattedOutput = (await formatter.format(lintResults)).trim();
149
+ const formattedOutput = removeAnsiEscapeSequences((await formatter.format(lintResults)).trim());
146
150
  const errorCount = lintResults.reduce((count, lintResult) => count + lintResult.errorCount + lintResult.fatalErrorCount, 0);
147
151
  return {
148
152
  exitCode: errorCount > 0 ? 1 : 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nt-ai-lab/opencode-skillz",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Bundled OpenCode commands and agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,8 +22,6 @@ if (!lintRepositoryRoot) {
22
22
 
23
23
  const typescriptFiles = ['**/*.ts', '**/*.tsx']
24
24
  const testFiles = ['**/*.spec.ts', '**/*.spec.tsx', '**/*.test.ts', '**/*.test.tsx']
25
- const thinLayerFiles = ['**/entrypoint/**/*.ts', '**/commands/**/*.ts', '**/queries/**/*.ts']
26
- const thinLayerIgnoredFiles = ['**/*.spec.ts', '**/*.test.ts']
27
25
  const ignoredPaths = [
28
26
  '**/dist',
29
27
  '**/out-tsc',
@@ -58,21 +56,6 @@ const noEmptyStringFallbackRule = {
58
56
 
59
57
  const restrictedSyntaxRules = ['error', noLetRule, noGenericErrorRule, noEmptyStringFallbackRule]
60
58
 
61
- const entrypointRestrictedSyntaxRules = [
62
- 'error',
63
- noLetRule,
64
- noGenericErrorRule,
65
- {
66
- selector: 'FunctionDeclaration:not([parent.type="ExportNamedDeclaration"])',
67
- message: 'Entrypoints must not define private functions. Move logic to commands/, queries/, or infra/.',
68
- },
69
- {
70
- selector: 'VariableDeclarator > ArrowFunctionExpression',
71
- message: 'Entrypoints must not define private arrow functions. Move logic to commands/, queries/, or infra/.',
72
- },
73
- noEmptyStringFallbackRule,
74
- ]
75
-
76
59
  const restrictedImportPatterns = [
77
60
  {
78
61
  group: ['*/utils/*', '*/utils', '*/utilities'],
@@ -229,20 +212,6 @@ export default tseslint.config(
229
212
  '@stylistic/object-property-newline': ['error', { allowAllPropertiesOnSameLine: false }],
230
213
  },
231
214
  },
232
- {
233
- files: thinLayerFiles,
234
- ignores: thinLayerIgnoredFiles,
235
- rules: {
236
- 'max-lines': ['error', { max: 150, skipBlankLines: true, skipComments: true }],
237
- },
238
- },
239
- {
240
- files: ['**/entrypoint/**/*.ts'],
241
- ignores: thinLayerIgnoredFiles,
242
- rules: {
243
- 'no-restricted-syntax': entrypointRestrictedSyntaxRules,
244
- },
245
- },
246
215
  {
247
216
  files: typescriptFiles,
248
217
  plugins: {