rhachet-roles-ehmpathy 1.9.1 → 1.11.0
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/dist/logic/roles/coach/.briefs/claude.context-caching.md +76 -0
- package/dist/logic/roles/mechanic/.briefs/lessons/code.prod.typescript.types/bivariance_vs_contravariance.[lesson].md +95 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.errors.failfast/bad-practices/forbid.failhide.md +19 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.errors.failfast/best-practice/prefer.HelpfulError.wrap.md +54 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.narrative/bad-practices/forbid.else.md +54 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.narrative/best-practice/early-returns.named-checks.[demo].md +181 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.repo.structure/bad-practices/forbid.index.ts.md +3 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/code.test.howto/best-practice/howto.diagnose.[lesson].md +14 -0
- package/dist/logic/roles/mechanic/.briefs/patterns/lang.terms/best-practice/require.order.noun_adj.md +39 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.common.sh +58 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.del.sh +51 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.get.sh +51 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.set.sh +108 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.sh +46 -0
- package/dist/logic/roles/mechanic/.skills/git.worktree.test.sh +229 -0
- package/dist/logic/roles/mechanic/.skills/init.bhuild.sh +260 -0
- package/dist/logic/roles/mechanic/.skills/init.claude.permissions.sh +109 -0
- package/dist/logic/roles/mechanic/.skills/init.claude.sh +35 -0
- package/dist/logic/roles/mechanic/.skills/run.test.sh +8 -2
- package/package.json +2 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# 🧠 .brief: Claude Context Caching
|
|
2
|
+
|
|
3
|
+
## .what
|
|
4
|
+
Claude's context caching mechanism that reduces token costs by caching prompt prefixes
|
|
5
|
+
|
|
6
|
+
## .how.it.works
|
|
7
|
+
|
|
8
|
+
### cache key = exact prefix match
|
|
9
|
+
- Claude caches content based on the **exact sequence of tokens from the start** of the conversation
|
|
10
|
+
- a cache hit requires the **exact same prefix** to be present
|
|
11
|
+
- cache is valid for ~5 minutes of inactivity
|
|
12
|
+
|
|
13
|
+
### prefix-based matching
|
|
14
|
+
```
|
|
15
|
+
Turn 1: [system prompt] + [briefs: 30k tokens] → writes to cache
|
|
16
|
+
Turn 2: [system prompt] + [briefs: 30k tokens] + [user msg] + [response] → cache hit ✅
|
|
17
|
+
Turn 3: [system prompt] + [briefs: 30k tokens] + [history] + [new msg] → cache hit ✅
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### when cache hits occur
|
|
21
|
+
|
|
22
|
+
✅ **cache hit (free reads)** when:
|
|
23
|
+
- the content appears at the **exact same position** in the context
|
|
24
|
+
- everything before it is **identical** to the cached version
|
|
25
|
+
- less than ~5 minutes of inactivity
|
|
26
|
+
|
|
27
|
+
❌ **cache miss (full tokens charged)** when:
|
|
28
|
+
- anything changed in the context **before** the cached content (even a single token)
|
|
29
|
+
- the content moved to a different position in the conversation
|
|
30
|
+
- more than ~5 minutes passed with no activity
|
|
31
|
+
- new conversation started (different session)
|
|
32
|
+
|
|
33
|
+
## .implications.for.briefs
|
|
34
|
+
|
|
35
|
+
### sessionstart hooks
|
|
36
|
+
when loading briefs via sessionstart hooks:
|
|
37
|
+
|
|
38
|
+
1. **first boot**: full tokens cached (e.g., 30k tokens written to cache)
|
|
39
|
+
2. **within same session**: cached if prefix matches (free reads)
|
|
40
|
+
3. **new session**: full tokens charged again (new conversation = different prefix)
|
|
41
|
+
|
|
42
|
+
### optimization strategies
|
|
43
|
+
to maximize cache hits:
|
|
44
|
+
|
|
45
|
+
1. **load briefs once per session** (sessionstart hook pattern)
|
|
46
|
+
2. **keep them at consistent position** (ideally at the start)
|
|
47
|
+
3. **avoid regenerating them** if content hasn't changed
|
|
48
|
+
4. **batch related work** in same session to reuse cache
|
|
49
|
+
|
|
50
|
+
## .key.insight
|
|
51
|
+
|
|
52
|
+
the cache **does not move tokens around** or do LRU eviction - it's purely prefix-based matching
|
|
53
|
+
|
|
54
|
+
this means:
|
|
55
|
+
- you can't "refresh" cached content by using it again
|
|
56
|
+
- cached content must appear in the exact same position to hit
|
|
57
|
+
- any prefix changes invalidate the entire cache for that position
|
|
58
|
+
|
|
59
|
+
## .cost.model
|
|
60
|
+
|
|
61
|
+
### cache writes
|
|
62
|
+
- **first use**: 25% of normal input token cost
|
|
63
|
+
- **cache write**: happens on first occurrence of new content
|
|
64
|
+
|
|
65
|
+
### cache reads
|
|
66
|
+
- **cache hit**: 10% of normal input token cost
|
|
67
|
+
- **cache miss**: 100% of normal input token cost (no cache benefit)
|
|
68
|
+
|
|
69
|
+
### example: 30k token briefs
|
|
70
|
+
- **first load**: ~7.5k tokens charged (25% write cost)
|
|
71
|
+
- **subsequent hits**: ~3k tokens charged (10% read cost)
|
|
72
|
+
- **cache miss**: 30k tokens charged (100% cost)
|
|
73
|
+
|
|
74
|
+
## .sources
|
|
75
|
+
- based on Anthropic's context caching documentation
|
|
76
|
+
- observed behavior in production usage
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# method syntax for bivariance
|
|
2
|
+
|
|
3
|
+
## .what
|
|
4
|
+
|
|
5
|
+
typescript treats arrow function properties and method syntax differently for variance:
|
|
6
|
+
|
|
7
|
+
- **arrow function syntax** = contravariant on parameters
|
|
8
|
+
- **method syntax** = bivariant on parameters
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
// arrow function syntax (contravariant)
|
|
12
|
+
interface Dao {
|
|
13
|
+
get: (input: SomeInput) => Promise<Result>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// method syntax (bivariant)
|
|
17
|
+
interface Dao {
|
|
18
|
+
get(input: SomeInput): Promise<Result>;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## .why this matters
|
|
25
|
+
|
|
26
|
+
when checking if a specific type is assignable to a generic type (e.g., `Dao<Specific>` to `Dao<any>`), contravariance on function parameters can cause unexpected failures.
|
|
27
|
+
|
|
28
|
+
### example: the problem
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
interface DeclastructDao<TResourceClass> {
|
|
32
|
+
get: {
|
|
33
|
+
// arrow syntax = contravariant
|
|
34
|
+
byPrimary?: (input: RefByPrimary<TResourceClass>) => Promise<Resource | null>;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// specific dao where RefByPrimary resolves to {uuid: string}
|
|
39
|
+
const specificDao: DeclastructDao<typeof MyResource> = { ... };
|
|
40
|
+
|
|
41
|
+
// generic dao shape where RefByPrimary<any> resolves to {}
|
|
42
|
+
type GenericDao = DeclastructDao<any>;
|
|
43
|
+
|
|
44
|
+
// ❌ fails: {uuid: string} is not assignable to {}
|
|
45
|
+
const generic: GenericDao = specificDao;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
the contravariant check asks: "can a function accepting `{uuid: string}` be used where a function accepting `{}` is expected?"
|
|
49
|
+
|
|
50
|
+
answer: **no** — a function requiring `uuid` can't safely accept an empty object.
|
|
51
|
+
|
|
52
|
+
### example: the fix
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface DeclastructDao<TResourceClass> {
|
|
56
|
+
get: {
|
|
57
|
+
// method syntax = bivariant
|
|
58
|
+
byPrimary?(input: RefByPrimary<TResourceClass>): Promise<Resource | null>;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ✅ works: bivariance allows assignment in either direction
|
|
63
|
+
const generic: GenericDao = specificDao;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## .when to use
|
|
69
|
+
|
|
70
|
+
use method syntax when:
|
|
71
|
+
|
|
72
|
+
1. you need interfaces with generic type parameters to be assignable to their `<any>` variants
|
|
73
|
+
2. you're defining dao/repository patterns where specific implementations must fit into generic collections
|
|
74
|
+
3. the interface will be used in `Record<string, Interface<any>>` shapes
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## .tradeoff
|
|
79
|
+
|
|
80
|
+
bivariance is slightly less type-safe at the boundary — typescript won't catch certain mismatches. however, this is often the correct behavior for:
|
|
81
|
+
|
|
82
|
+
- generic collections of typed implementations
|
|
83
|
+
- plugin/provider patterns
|
|
84
|
+
- dao registries
|
|
85
|
+
|
|
86
|
+
the types remain fully enforced when actually calling the methods on a specific instance.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## .reference
|
|
91
|
+
|
|
92
|
+
this is intentional typescript behavior. method syntax preserves the historical bivariant behavior from before `--strictFunctionTypes` was introduced, because it matches how most developers expect object subtyping to work.
|
|
93
|
+
|
|
94
|
+
- [typescript handbook: type compatibility](https://www.typescriptlang.org/docs/handbook/type-compatibility.html)
|
|
95
|
+
- [typescript pr: strict function types](https://github.com/microsoft/TypeScript/pull/18654)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
failhide = any scenario where you hide real errors
|
|
2
|
+
|
|
3
|
+
the most common patterns is
|
|
4
|
+
|
|
5
|
+
try/catch
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
the only cases where a trycatch is allowed is if the catch has an allowlist of the errors it catches and CAREFULLY handles those, while throwing the rest up
|
|
10
|
+
|
|
11
|
+
otherwise, its a failhide hazard, where real errors will be silently hidden, leading to defects or hours of pointless debugging
|
|
12
|
+
|
|
13
|
+
never failhide
|
|
14
|
+
|
|
15
|
+
always failfast
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
this is a mega blocker
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
in some cases, its useful
|
|
2
|
+
|
|
3
|
+
to try/catch
|
|
4
|
+
|
|
5
|
+
to make an error more observable
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
e.g.,
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
try {
|
|
13
|
+
// do something that throws
|
|
14
|
+
await doSomething({ userUuid })
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!(error instanceof Error)) throw error;
|
|
17
|
+
throw Error(`doSomething.error: ${error.message}, for userUuid=${userUuid}`, { cause });
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
in these cases, its best to use HelpfulError.wrap instead
|
|
22
|
+
- it automatically instantiates a HelpfulError (or more specific variant if specified) for maximum observability
|
|
23
|
+
|
|
24
|
+
e.g.,
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { HelpfulError } from 'helpful-errors';
|
|
28
|
+
|
|
29
|
+
await HelpfulError.wrap(
|
|
30
|
+
async () => await doSomething({ userUuid }),
|
|
31
|
+
{
|
|
32
|
+
message: 'doSomething.error',
|
|
33
|
+
metadata: {
|
|
34
|
+
userUuid,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
)()
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
or
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
44
|
+
|
|
45
|
+
await UnexpectedCodePathError.wrap(
|
|
46
|
+
async () => await doSomething({ userUuid }),
|
|
47
|
+
{
|
|
48
|
+
message: 'doSomething.error',
|
|
49
|
+
metadata: {
|
|
50
|
+
userUuid,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
)()
|
|
54
|
+
```
|
package/dist/logic/roles/mechanic/.briefs/patterns/code.prod.narrative/bad-practices/forbid.else.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
else is a code REAK
|
|
2
|
+
|
|
3
|
+
its not even a smell
|
|
4
|
+
|
|
5
|
+
its such a bad smell that it reaks
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
else's can _always_ be better rewriten as an IFFE+EarlyReturn
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
see the narrative flow breifs for more context on the desired goal
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
-------
|
|
17
|
+
|
|
18
|
+
e.g.,
|
|
19
|
+
|
|
20
|
+
> no if else; only early returns; else is a code REAK
|
|
21
|
+
|
|
22
|
+
● You're right - the if/else violates the flow:narrative tactic. Let me
|
|
23
|
+
refactor to use early returns only:
|
|
24
|
+
|
|
25
|
+
● Update(src/domain.operations/vpcTunnel/setVpcTunnel.ts)
|
|
26
|
+
⎿ Updated src/domain.operations/vpcTunnel/setVpcTunnel.ts with 5
|
|
27
|
+
additions and 3 removals
|
|
28
|
+
81 if (portInUse) {
|
|
29
|
+
82 const cacheFilePresent = await isFilePresent({ path:
|
|
30
|
+
cachePath });
|
|
31
|
+
83
|
|
32
|
+
84 + // port used by unknown process; kill it to reclaim
|
|
33
|
+
85 + if (!cacheFilePresent) {
|
|
34
|
+
86 + killProcessOnPort({ port: input.from.port });
|
|
35
|
+
87 + }
|
|
36
|
+
88 +
|
|
37
|
+
89 // check if existing tunnel is ours and healthy
|
|
38
|
+
90 if (cacheFilePresent) {
|
|
39
|
+
91 const cacheContent = await fs.readFile(cachePath,
|
|
40
|
+
'utf-8');
|
|
41
|
+
...
|
|
42
|
+
100 // cleanup stale tunnel
|
|
43
|
+
101 if (isProcessAlive({ pid: cache.pid }))
|
|
44
|
+
process.kill(cache.pid, 'SIGTERM');
|
|
45
|
+
102 await fs.rm(cachePath, { force: true });
|
|
46
|
+
103 - } else {
|
|
47
|
+
104 - // port used by another process; kill it to reclaim
|
|
48
|
+
105 - killProcessOnPort({ port: input.from.port });
|
|
49
|
+
103 }
|
|
50
|
+
104 }
|
|
51
|
+
105
|
|
52
|
+
|
|
53
|
+
● Removed the else block - now both conditions are independent if statements
|
|
54
|
+
with no branching maze.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
|
|
2
|
+
● The check now walks up the prototype chain starting from error.constructor, checking if any class in the
|
|
3
|
+
inheritance hierarchy is named 'BadRequestError'. This handles cases where a custom error class extends
|
|
4
|
+
BadRequestError (or extends something that extends it, etc.).
|
|
5
|
+
|
|
6
|
+
> lets actually create a new constant called "isExtensionOfBadRequestError"
|
|
7
|
+
⎿ Found 3 new diagnostic issues in 1 file (ctrl+o to expand)
|
|
8
|
+
|
|
9
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
10
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 4 additions and 2 removals
|
|
11
|
+
8 */
|
|
12
|
+
9 export const decideIsBadRequestError = ({ error }: { error: Error }) => {
|
|
13
|
+
10 const isInstanceOfBadRequestError = error instanceof BadRequestError;
|
|
14
|
+
11 - const isNamedAfterBadRequestError = (() => {
|
|
15
|
+
12 - let proto = error.constructor;
|
|
16
|
+
11 + const isNamedAfterBadRequestError =
|
|
17
|
+
12 + error.constructor.name === 'BadRequestError';
|
|
18
|
+
13 + const isExtensionOfBadRequestError = (() => {
|
|
19
|
+
14 + let proto = Object.getPrototypeOf(error.constructor);
|
|
20
|
+
15 while (proto) {
|
|
21
|
+
16 if (proto.name === 'BadRequestError') return true;
|
|
22
|
+
17 proto = Object.getPrototypeOf(proto);
|
|
23
|
+
|
|
24
|
+
● Now I need to add the new constant to the return statement:
|
|
25
|
+
|
|
26
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
27
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 1 addition and 1 removal
|
|
28
|
+
18 }
|
|
29
|
+
19 return false;
|
|
30
|
+
20 })();
|
|
31
|
+
21 - return isInstanceOfBadRequestError || isNamedAfterBadRequestError;
|
|
32
|
+
21 + return isInstanceOfBadRequestError || isNamedAfterBadRequestError
|
|
33
|
+
+ || isExtensionOfBadRequestError;
|
|
34
|
+
22 };
|
|
35
|
+
23
|
|
36
|
+
24 /**
|
|
37
|
+
|
|
38
|
+
● Done. Now decideIsBadRequestError has three separate checks:
|
|
39
|
+
|
|
40
|
+
1. isInstanceOfBadRequestError - uses instanceof check
|
|
41
|
+
2. isNamedAfterBadRequestError - checks if the error's constructor is named 'BadRequestError'
|
|
42
|
+
3. isExtensionOfBadRequestError - walks up the prototype chain to check if any parent class is named
|
|
43
|
+
'BadRequestError'
|
|
44
|
+
|
|
45
|
+
> add a header comment for each constant
|
|
46
|
+
⎿ Found 1 new diagnostic issue in 1 file (ctrl+o to expand)
|
|
47
|
+
|
|
48
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
49
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 11 additions and 1 removal
|
|
50
|
+
7 * .what = procedure for deciding whether an error is a bad request error
|
|
51
|
+
8 */
|
|
52
|
+
9 export const decideIsBadRequestError = ({ error }: { error: Error }) => {
|
|
53
|
+
10 + // check if the error is an instance of BadRequestError
|
|
54
|
+
11 const isInstanceOfBadRequestError = error instanceof BadRequestError;
|
|
55
|
+
12 +
|
|
56
|
+
13 + // check if the error's constructor is named 'BadRequestError'
|
|
57
|
+
14 const isNamedAfterBadRequestError =
|
|
58
|
+
15 error.constructor.name === 'BadRequestError';
|
|
59
|
+
16 +
|
|
60
|
+
17 + // check if any parent class in the prototype chain is named 'BadRequestError'
|
|
61
|
+
18 const isExtensionOfBadRequestError = (() => {
|
|
62
|
+
19 let proto = Object.getPrototypeOf(error.constructor);
|
|
63
|
+
20 while (proto) {
|
|
64
|
+
...
|
|
65
|
+
18 }
|
|
66
|
+
19 return false;
|
|
67
|
+
20 })();
|
|
68
|
+
21 - return isInstanceOfBadRequestError || isNamedAfterBadRequestError ||
|
|
69
|
+
- isExtensionOfBadRequestError;
|
|
70
|
+
21 +
|
|
71
|
+
22 + return (
|
|
72
|
+
23 + isInstanceOfBadRequestError ||
|
|
73
|
+
24 + isNamedAfterBadRequestError ||
|
|
74
|
+
25 + isExtensionOfBadRequestError
|
|
75
|
+
26 + );
|
|
76
|
+
27 };
|
|
77
|
+
28
|
|
78
|
+
29 /**
|
|
79
|
+
|
|
80
|
+
● Done. Added header comments for each constant and also fixed the formatting on the return statement to
|
|
81
|
+
satisfy the eslint rule.
|
|
82
|
+
|
|
83
|
+
> use early returns after each check, rather than needoing to evalaute all three
|
|
84
|
+
⎿ Found 3 new diagnostic issues in 1 file (ctrl+o to expand)
|
|
85
|
+
|
|
86
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
87
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 8 additions and 16 removals
|
|
88
|
+
8 */
|
|
89
|
+
9 export const decideIsBadRequestError = ({ error }: { error: Error }) => {
|
|
90
|
+
10 // check if the error is an instance of BadRequestError
|
|
91
|
+
11 - const isInstanceOfBadRequestError = error instanceof BadRequestError;
|
|
92
|
+
11 + if (error instanceof BadRequestError) return true;
|
|
93
|
+
12
|
|
94
|
+
13 // check if the error's constructor is named 'BadRequestError'
|
|
95
|
+
14 - const isNamedAfterBadRequestError =
|
|
96
|
+
15 - error.constructor.name === 'BadRequestError';
|
|
97
|
+
14 + if (error.constructor.name === 'BadRequestError') return true;
|
|
98
|
+
15
|
|
99
|
+
16 // check if any parent class in the prototype chain is named 'BadRequestError'
|
|
100
|
+
17 - const isExtensionOfBadRequestError = (() => {
|
|
101
|
+
18 - let proto = Object.getPrototypeOf(error.constructor);
|
|
102
|
+
19 - while (proto) {
|
|
103
|
+
20 - if (proto.name === 'BadRequestError') return true;
|
|
104
|
+
21 - proto = Object.getPrototypeOf(proto);
|
|
105
|
+
22 - }
|
|
106
|
+
23 - return false;
|
|
107
|
+
24 - })();
|
|
108
|
+
17 + let proto = Object.getPrototypeOf(error.constructor);
|
|
109
|
+
18 + while (proto) {
|
|
110
|
+
19 + if (proto.name === 'BadRequestError') return true;
|
|
111
|
+
20 + proto = Object.getPrototypeOf(proto);
|
|
112
|
+
21 + }
|
|
113
|
+
22
|
|
114
|
+
23 - return (
|
|
115
|
+
24 - isInstanceOfBadRequestError ||
|
|
116
|
+
25 - isNamedAfterBadRequestError ||
|
|
117
|
+
26 - isExtensionOfBadRequestError
|
|
118
|
+
27 - );
|
|
119
|
+
23 + return false;
|
|
120
|
+
24 };
|
|
121
|
+
25
|
|
122
|
+
26 /**
|
|
123
|
+
|
|
124
|
+
● Done. Now the function uses early returns, so it short-circuits as soon as any check passes without needing
|
|
125
|
+
to evaluate the remaining checks.
|
|
126
|
+
|
|
127
|
+
> header comment on the last return, too
|
|
128
|
+
⎿ Found 3 new diagnostic issues in 1 file (ctrl+o to expand)
|
|
129
|
+
|
|
130
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
131
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 1 addition
|
|
132
|
+
20 proto = Object.getPrototypeOf(proto);
|
|
133
|
+
21 }
|
|
134
|
+
22
|
|
135
|
+
23 + // otherwise, not a bad request error
|
|
136
|
+
24 return false;
|
|
137
|
+
25 };
|
|
138
|
+
26
|
|
139
|
+
|
|
140
|
+
● Done.
|
|
141
|
+
|
|
142
|
+
> keep the names of the constants though; if (isXyz) is important for greater readability
|
|
143
|
+
⎿ Found 3 new diagnostic issues in 1 file (ctrl+o to expand)
|
|
144
|
+
|
|
145
|
+
● Update(src/logic/middlewares/badRequestErrorMiddleware.ts)
|
|
146
|
+
⎿ Updated src/logic/middlewares/badRequestErrorMiddleware.ts with 14 additions and 7 removals
|
|
147
|
+
8 */
|
|
148
|
+
9 export const decideIsBadRequestError = ({ error }: { error: Error }) => {
|
|
149
|
+
10 // check if the error is an instance of BadRequestError
|
|
150
|
+
11 - if (error instanceof BadRequestError) return true;
|
|
151
|
+
11 + const isInstanceOfBadRequestError = error instanceof BadRequestError;
|
|
152
|
+
12 + if (isInstanceOfBadRequestError) return true;
|
|
153
|
+
13
|
|
154
|
+
14 // check if the error's constructor is named 'BadRequestError'
|
|
155
|
+
15 - if (error.constructor.name === 'BadRequestError') return true;
|
|
156
|
+
15 + const isNamedAfterBadRequestError =
|
|
157
|
+
16 + error.constructor.name === 'BadRequestError';
|
|
158
|
+
17 + if (isNamedAfterBadRequestError) return true;
|
|
159
|
+
18
|
|
160
|
+
19 // check if any parent class in the prototype chain is named 'BadRequestError'
|
|
161
|
+
20 - let proto = Object.getPrototypeOf(error.constructor);
|
|
162
|
+
21 - while (proto) {
|
|
163
|
+
22 - if (proto.name === 'BadRequestError') return true;
|
|
164
|
+
23 - proto = Object.getPrototypeOf(proto);
|
|
165
|
+
24 - }
|
|
166
|
+
20 + const isExtensionOfBadRequestError = (() => {
|
|
167
|
+
21 + let proto = Object.getPrototypeOf(error.constructor);
|
|
168
|
+
22 + while (proto) {
|
|
169
|
+
23 + if (proto.name === 'BadRequestError') return true;
|
|
170
|
+
24 + proto = Object.getPrototypeOf(proto);
|
|
171
|
+
25 + }
|
|
172
|
+
26 + return false;
|
|
173
|
+
27 + })();
|
|
174
|
+
28 + if (isExtensionOfBadRequestError) return true;
|
|
175
|
+
29
|
|
176
|
+
30 // otherwise, not a bad request error
|
|
177
|
+
31 return false;
|
|
178
|
+
|
|
179
|
+
● Done. Now each check has a named constant (isInstanceOfBadRequestError, isNamedAfterBadRequestError,
|
|
180
|
+
isExtensionOfBadRequestError) followed by an early return, giving both readability and short-circuit
|
|
181
|
+
evaluation.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
if you have a failing test
|
|
2
|
+
|
|
3
|
+
1. make the error super clear via
|
|
4
|
+
1. observable error messages
|
|
5
|
+
2. clear log trails
|
|
6
|
+
|
|
7
|
+
2. when you detect which subcomponent has unexpected behavior
|
|
8
|
+
1. cover that subscomponent with tests of its own (unit, integration, etc)
|
|
9
|
+
2. if it does not have its own procedure, breakit out into its own procedure (own function + own file + own test suite)
|
|
10
|
+
|
|
11
|
+
that way
|
|
12
|
+
1. its easy to see exactly what is failing
|
|
13
|
+
2. its easy to prove we fixed it (or verify that the behavior is / isnt expected)
|
|
14
|
+
3. we can systematically track down the bug, one subcomponnent at a time
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
always use [noun][adjective] order
|
|
2
|
+
|
|
3
|
+
e.g.,
|
|
4
|
+
|
|
5
|
+
instead of
|
|
6
|
+
|
|
7
|
+
currentOwner = [adj][noun]
|
|
8
|
+
|
|
9
|
+
prefer
|
|
10
|
+
|
|
11
|
+
ownerCurrent = [noun][adj]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
instead of
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
foundUser
|
|
20
|
+
|
|
21
|
+
prefer
|
|
22
|
+
|
|
23
|
+
userFound
|
|
24
|
+
|
|
25
|
+
----
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
why?
|
|
29
|
+
|
|
30
|
+
because it enables autocomplete via common prefixes
|
|
31
|
+
|
|
32
|
+
e.g.,
|
|
33
|
+
|
|
34
|
+
userBefore
|
|
35
|
+
userAfter
|
|
36
|
+
userCreated
|
|
37
|
+
etc
|
|
38
|
+
|
|
39
|
+
can see via autocomplete suggestions all the variants of user available in the current scope, rather than need to remember what the variants are
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = shared helpers for git worktree management
|
|
4
|
+
#
|
|
5
|
+
# .why = centralizes path resolution and branch sanitization logic
|
|
6
|
+
# used by git.worktree.{get,set,del}.sh scripts
|
|
7
|
+
#
|
|
8
|
+
# .how = source this file to get access to:
|
|
9
|
+
# - resolve_worktrees_dir: computes $REPO_WORKTREES_DIR
|
|
10
|
+
# - sanitize_branch_name: converts branch to worktree name
|
|
11
|
+
# - get_repo_name: extracts repo name from gitroot
|
|
12
|
+
#
|
|
13
|
+
# guarantee:
|
|
14
|
+
# - works from root repo or from within a worktree
|
|
15
|
+
# - consistent path resolution across all worktree scripts
|
|
16
|
+
######################################################################
|
|
17
|
+
|
|
18
|
+
# resolve the worktrees directory for this repo
|
|
19
|
+
# handles both root repo and worktree contexts
|
|
20
|
+
resolve_worktrees_dir() {
|
|
21
|
+
local gitroot
|
|
22
|
+
gitroot="$(git rev-parse --show-toplevel)"
|
|
23
|
+
|
|
24
|
+
local reponame
|
|
25
|
+
reponame="$(basename "$gitroot")"
|
|
26
|
+
|
|
27
|
+
# detect if we're in a worktree (path contains _worktrees)
|
|
28
|
+
if [[ "$gitroot" == *"_worktrees"* ]]; then
|
|
29
|
+
# we're in a worktree - reuse same _worktrees/$reponame dir
|
|
30
|
+
echo "${gitroot%/*}"
|
|
31
|
+
else
|
|
32
|
+
# root repo - compute sibling _worktrees dir
|
|
33
|
+
echo "$(dirname "$gitroot")/_worktrees/$reponame"
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# sanitize branch name for use as directory name
|
|
38
|
+
# vlad/practs => vlad.practs
|
|
39
|
+
sanitize_branch_name() {
|
|
40
|
+
local branch="$1"
|
|
41
|
+
echo "${branch//\//.}"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# get the repo name (works from root repo or worktree)
|
|
45
|
+
get_repo_name() {
|
|
46
|
+
local gitroot
|
|
47
|
+
gitroot="$(git rev-parse --show-toplevel)"
|
|
48
|
+
|
|
49
|
+
# detect if we're in a worktree (path contains _worktrees)
|
|
50
|
+
if [[ "$gitroot" == *"_worktrees"* ]]; then
|
|
51
|
+
# extract repo name from _worktrees/$reponame/$worktree path
|
|
52
|
+
# gitroot = /path/to/_worktrees/$reponame/$worktree
|
|
53
|
+
local worktrees_parent="${gitroot%/*}" # /path/to/_worktrees/$reponame
|
|
54
|
+
basename "$worktrees_parent"
|
|
55
|
+
else
|
|
56
|
+
basename "$gitroot"
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = remove a git worktree
|
|
4
|
+
#
|
|
5
|
+
# .why = clean up worktrees no longer needed
|
|
6
|
+
#
|
|
7
|
+
# .how = removes worktree at @gitroot/../_worktrees/$reponame/$branch
|
|
8
|
+
#
|
|
9
|
+
# usage:
|
|
10
|
+
# git.worktree.del.sh <branch>
|
|
11
|
+
#
|
|
12
|
+
# guarantee:
|
|
13
|
+
# - idempotent: [DELETE] if exists, [SKIP] if not found
|
|
14
|
+
# - works from root repo or from within a worktree
|
|
15
|
+
######################################################################
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
|
|
21
|
+
# source shared helpers
|
|
22
|
+
source "$SCRIPT_DIR/git.worktree.common.sh"
|
|
23
|
+
|
|
24
|
+
# parse arguments
|
|
25
|
+
BRANCH="${1:-}"
|
|
26
|
+
|
|
27
|
+
# validate branch argument
|
|
28
|
+
if [[ -z "$BRANCH" ]]; then
|
|
29
|
+
echo "error: branch name required"
|
|
30
|
+
echo "usage: git.worktree.del.sh <branch>"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# resolve paths
|
|
35
|
+
REPO_WORKTREES_DIR="$(resolve_worktrees_dir)"
|
|
36
|
+
WORKTREE_NAME="$(sanitize_branch_name "$BRANCH")"
|
|
37
|
+
WORKTREE_PATH="$REPO_WORKTREES_DIR/$WORKTREE_NAME"
|
|
38
|
+
|
|
39
|
+
# delete logic
|
|
40
|
+
if [[ -d "$WORKTREE_PATH" ]]; then
|
|
41
|
+
# remove worktree via git
|
|
42
|
+
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || {
|
|
43
|
+
# fallback: manual removal if git worktree remove fails
|
|
44
|
+
rm -rf "$WORKTREE_PATH"
|
|
45
|
+
git worktree prune
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
echo "[DELETE] $WORKTREE_NAME"
|
|
49
|
+
else
|
|
50
|
+
echo "[SKIP] $WORKTREE_NAME (not found)"
|
|
51
|
+
fi
|