rhachet-roles-ehmpathy 1.9.1 → 1.10.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/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,260 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = initialize a .behavior directory for bhuild thoughtroute
|
|
4
|
+
#
|
|
5
|
+
# .why = standardize the behavior-driven development thoughtroute
|
|
6
|
+
# by scaffolding a structured directory with:
|
|
7
|
+
# - wish definition
|
|
8
|
+
# - vision statement
|
|
9
|
+
# - acceptance criteria
|
|
10
|
+
# - research prompts
|
|
11
|
+
# - distillation prompts
|
|
12
|
+
# - blueprint prompts
|
|
13
|
+
# - roadmap prompts
|
|
14
|
+
# - execution prompts
|
|
15
|
+
# - feedback template
|
|
16
|
+
#
|
|
17
|
+
# .how = creates .behavior/v${isodate}.${behaviorname}/ with
|
|
18
|
+
# all necessary scaffold files for the bhuild thoughtroute
|
|
19
|
+
#
|
|
20
|
+
# usage:
|
|
21
|
+
# init.bhuild.sh --name <behaviorname> [--dir <directory>]
|
|
22
|
+
#
|
|
23
|
+
# guarantee:
|
|
24
|
+
# - creates .behavior/ if missing
|
|
25
|
+
# - creates versioned behavior directory
|
|
26
|
+
# - findserts all thoughtroute files (creates if missing, skips if exists)
|
|
27
|
+
# - idempotent: safe to rerun
|
|
28
|
+
# - fail-fast on errors
|
|
29
|
+
######################################################################
|
|
30
|
+
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
# parse arguments
|
|
34
|
+
BEHAVIOR_NAME=""
|
|
35
|
+
TARGET_DIR="$PWD"
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case $1 in
|
|
38
|
+
--name)
|
|
39
|
+
BEHAVIOR_NAME="$2"
|
|
40
|
+
shift 2
|
|
41
|
+
;;
|
|
42
|
+
--dir)
|
|
43
|
+
TARGET_DIR="$2"
|
|
44
|
+
shift 2
|
|
45
|
+
;;
|
|
46
|
+
*)
|
|
47
|
+
echo "error: unknown argument '$1'"
|
|
48
|
+
echo "usage: init.bhuild.sh --name <behaviorname> [--dir <directory>]"
|
|
49
|
+
exit 1
|
|
50
|
+
;;
|
|
51
|
+
esac
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
# validate required arguments
|
|
55
|
+
if [[ -z "$BEHAVIOR_NAME" ]]; then
|
|
56
|
+
echo "error: --name is required"
|
|
57
|
+
echo "usage: init.bhuild.sh --name <behaviorname> [--dir <directory>]"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# generate isodate in format YYYY_MM_DD
|
|
62
|
+
ISO_DATE=$(date +%Y_%m_%d)
|
|
63
|
+
|
|
64
|
+
# trim trailing .behavior from TARGET_DIR if present
|
|
65
|
+
TARGET_DIR="${TARGET_DIR%/.behavior}"
|
|
66
|
+
TARGET_DIR="${TARGET_DIR%.behavior}"
|
|
67
|
+
|
|
68
|
+
# construct behavior directory path (absolute)
|
|
69
|
+
BEHAVIOR_DIR="$TARGET_DIR/.behavior/v${ISO_DATE}.${BEHAVIOR_NAME}"
|
|
70
|
+
|
|
71
|
+
# compute relative path from caller's $PWD for file contents
|
|
72
|
+
BEHAVIOR_DIR_REL="$(realpath --relative-to="$PWD" "$TARGET_DIR")/.behavior/v${ISO_DATE}.${BEHAVIOR_NAME}"
|
|
73
|
+
# normalize: remove leading ./ if present
|
|
74
|
+
BEHAVIOR_DIR_REL="${BEHAVIOR_DIR_REL#./}"
|
|
75
|
+
|
|
76
|
+
# create behavior directory (idempotent)
|
|
77
|
+
mkdir -p "$BEHAVIOR_DIR"
|
|
78
|
+
|
|
79
|
+
# helper: findsert file (create if missing, skip if exists)
|
|
80
|
+
findsert() {
|
|
81
|
+
local filepath="$1"
|
|
82
|
+
if [[ -f "$filepath" ]]; then
|
|
83
|
+
echo " [KEEP] $(basename "$filepath")"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
86
|
+
cat > "$filepath"
|
|
87
|
+
echo " [CREATE] $(basename "$filepath")"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# findsert 0.wish.md
|
|
91
|
+
findsert "$BEHAVIOR_DIR/0.wish.md" << 'EOF'
|
|
92
|
+
wish =
|
|
93
|
+
|
|
94
|
+
EOF
|
|
95
|
+
|
|
96
|
+
# findsert 1.vision.md
|
|
97
|
+
findsert "$BEHAVIOR_DIR/1.vision.md" << 'EOF'
|
|
98
|
+
|
|
99
|
+
EOF
|
|
100
|
+
|
|
101
|
+
# findsert 2.criteria.md
|
|
102
|
+
findsert "$BEHAVIOR_DIR/2.criteria.md" << 'EOF'
|
|
103
|
+
# usecase.1 = ...
|
|
104
|
+
given()
|
|
105
|
+
when()
|
|
106
|
+
then()
|
|
107
|
+
sothat()
|
|
108
|
+
then()
|
|
109
|
+
then()
|
|
110
|
+
sothat()
|
|
111
|
+
when()
|
|
112
|
+
then()
|
|
113
|
+
|
|
114
|
+
given()
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
# usecase.2 = ...
|
|
118
|
+
...
|
|
119
|
+
EOF
|
|
120
|
+
|
|
121
|
+
# findsert 3.1.research.domain._.v1.src
|
|
122
|
+
findsert "$BEHAVIOR_DIR/3.1.research.domain._.v1.src" << EOF
|
|
123
|
+
research the domain available in order to fulfill the wish declared
|
|
124
|
+
in $BEHAVIOR_DIR_REL/0.wish.md
|
|
125
|
+
|
|
126
|
+
specifically
|
|
127
|
+
- what are the domain objects that are involved with this wish
|
|
128
|
+
- entities
|
|
129
|
+
- events
|
|
130
|
+
- literals
|
|
131
|
+
- what are the domain operations
|
|
132
|
+
- getOne
|
|
133
|
+
- getAll
|
|
134
|
+
- setCreate
|
|
135
|
+
- setUpdate
|
|
136
|
+
- setDelete
|
|
137
|
+
- ...
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
use web search to discover and research
|
|
142
|
+
- cite every claim
|
|
143
|
+
- number each citation
|
|
144
|
+
- clone exact quotes from each citation
|
|
145
|
+
|
|
146
|
+
focus on these sdk's for reference, if provided
|
|
147
|
+
-
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
emit into $BEHAVIOR_DIR_REL/3.1.research.domain._.v1.i1.md
|
|
152
|
+
EOF
|
|
153
|
+
|
|
154
|
+
# findsert 3.2.distill.domain._.v1.src
|
|
155
|
+
findsert "$BEHAVIOR_DIR/3.2.distill.domain._.v1.src" << EOF
|
|
156
|
+
distill the declastruct domain.objects and domain.operations that would
|
|
157
|
+
- enable fulfillment of this wish $BEHAVIOR_DIR_REL/0.wish.md
|
|
158
|
+
- given the research declared here $BEHAVIOR_DIR_REL/3.1.research.domain._.v1.i1.md
|
|
159
|
+
|
|
160
|
+
procedure
|
|
161
|
+
1. declare the usecases and envision the contract that would be used to fulfill the usecases
|
|
162
|
+
2. declare the domain.objects, domain.operations, and access.daos that would fulfill this, via the declastruct pattern in this repo
|
|
163
|
+
|
|
164
|
+
emit into
|
|
165
|
+
- $BEHAVIOR_DIR_REL/3.2.distill.domain._.v1.i1.md
|
|
166
|
+
EOF
|
|
167
|
+
|
|
168
|
+
# findsert 3.3.blueprint.v1.src
|
|
169
|
+
findsert "$BEHAVIOR_DIR/3.3.blueprint.v1.src" << EOF
|
|
170
|
+
propose a blueprint for how we can implement the wish described
|
|
171
|
+
- in $BEHAVIOR_DIR_REL/0.wish.md
|
|
172
|
+
|
|
173
|
+
with the domain distillation declared
|
|
174
|
+
- in $BEHAVIOR_DIR_REL/3.2.distill.domain._.v1.i1.md
|
|
175
|
+
|
|
176
|
+
follow the patterns already present in this repo
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
reference the below for full context
|
|
181
|
+
- $BEHAVIOR_DIR_REL/0.wish.md
|
|
182
|
+
- $BEHAVIOR_DIR_REL/3.1.research.domain._.v1.i1.md
|
|
183
|
+
- $BEHAVIOR_DIR_REL/3.2.distill.domain._.v1.i1.md
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
emit to $BEHAVIOR_DIR_REL/3.3.blueprint.v1.i1.md
|
|
189
|
+
EOF
|
|
190
|
+
|
|
191
|
+
# findsert 4.1.roadmap.v1.src
|
|
192
|
+
findsert "$BEHAVIOR_DIR/4.1.roadmap.v1.src" << EOF
|
|
193
|
+
declare a roadmap,
|
|
194
|
+
|
|
195
|
+
- checklist style
|
|
196
|
+
- with ordered dependencies
|
|
197
|
+
- with behavioral acceptance criteria
|
|
198
|
+
- with behavioral acceptance verification at each step
|
|
199
|
+
|
|
200
|
+
for how to execute the blueprint specified in $BEHAVIOR_DIR_REL/3.3.blueprint.v1.i1.md
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
emit into $BEHAVIOR_DIR_REL/4.1.roadmap.v1.i1.md
|
|
205
|
+
EOF
|
|
206
|
+
|
|
207
|
+
# findsert 5.1.execution.phase0_to_phaseN.v1.src
|
|
208
|
+
findsert "$BEHAVIOR_DIR/5.1.execution.phase0_to_phaseN.v1.src" << EOF
|
|
209
|
+
bootup your mechanic's role via \`npx rhachet roles boot --repo ehmpathy --role mechanic\`
|
|
210
|
+
|
|
211
|
+
then, execute
|
|
212
|
+
- phase0 to phaseN
|
|
213
|
+
of roadmap
|
|
214
|
+
- $BEHAVIOR_DIR_REL/4.1.roadmap.v1.i1.md
|
|
215
|
+
|
|
216
|
+
ref:
|
|
217
|
+
- $BEHAVIOR_DIR_REL/3.3.blueprint.v1.i1.md
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
track your progress
|
|
222
|
+
|
|
223
|
+
emit todos and check them off into
|
|
224
|
+
- $BEHAVIOR_DIR_REL/5.1.execution.phase0_to_phaseN.v1.i1.md
|
|
225
|
+
EOF
|
|
226
|
+
|
|
227
|
+
# findsert .ref.[feedback].v1.[given].by_human.md
|
|
228
|
+
findsert "$BEHAVIOR_DIR/.ref.[feedback].v1.[given].by_human.md" << EOF
|
|
229
|
+
emit your response to the feedback into
|
|
230
|
+
- $BEHAVIOR_DIR_REL/.ref.[feedback].v1.[taken].by_robot.md
|
|
231
|
+
|
|
232
|
+
1. emit your response checklist
|
|
233
|
+
2. exec your response plan
|
|
234
|
+
3. emit your response checkoffs into the checklist
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
first, bootup your mechanics briefs again
|
|
239
|
+
|
|
240
|
+
npx rhachet roles boot --repo ehmpathy --role mechanic
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
---
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# blocker.1
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
# nitpick.2
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
# blocker.3
|
|
256
|
+
EOF
|
|
257
|
+
|
|
258
|
+
echo ""
|
|
259
|
+
echo "behavior thoughtroute initialized!"
|
|
260
|
+
echo " $BEHAVIOR_DIR"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = bind mechanic permissions to Claude settings
|
|
4
|
+
#
|
|
5
|
+
# .why = the mechanic role needs conservative permissions to operate
|
|
6
|
+
# safely while still being productive.
|
|
7
|
+
#
|
|
8
|
+
# this script manages permissions in .claude/settings.local.json:
|
|
9
|
+
# • replaces existing allows entirely (conservative)
|
|
10
|
+
# • extends denies by appending new entries (conservative)
|
|
11
|
+
# • extends asks by appending new entries (conservative)
|
|
12
|
+
# • idempotent: safe to rerun
|
|
13
|
+
#
|
|
14
|
+
# .how = uses jq to merge the permissions configuration
|
|
15
|
+
# into the existing settings structure, creating it if absent.
|
|
16
|
+
#
|
|
17
|
+
# guarantee:
|
|
18
|
+
# ✔ creates .claude/settings.local.json if missing
|
|
19
|
+
# ✔ preserves existing settings (hooks, other configs)
|
|
20
|
+
# ✔ replaces allow list entirely
|
|
21
|
+
# ✔ appends to deny list (no duplicates)
|
|
22
|
+
# ✔ appends to ask list (no duplicates)
|
|
23
|
+
# ✔ idempotent: safe to rerun
|
|
24
|
+
# ✔ fail-fast on errors
|
|
25
|
+
######################################################################
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
PROJECT_ROOT="$PWD"
|
|
30
|
+
SETTINGS_FILE="$PROJECT_ROOT/.claude/settings.local.json"
|
|
31
|
+
|
|
32
|
+
# define the permissions configuration to apply
|
|
33
|
+
PERMISSIONS_CONFIG=$(cat <<'EOF'
|
|
34
|
+
{
|
|
35
|
+
"permissions": {
|
|
36
|
+
"deny": [
|
|
37
|
+
"Bash(git commit:*)"
|
|
38
|
+
],
|
|
39
|
+
"ask": [
|
|
40
|
+
"Bash(chmod:*)",
|
|
41
|
+
"Bash(pnpm install:*)",
|
|
42
|
+
"Bash(pnpm add:*)"
|
|
43
|
+
],
|
|
44
|
+
"allow": [
|
|
45
|
+
"WebSearch",
|
|
46
|
+
"WebFetch(domain:github.com)",
|
|
47
|
+
"WebFetch(domain:www.npmjs.com)",
|
|
48
|
+
"WebFetch(domain:hub.docker.com)",
|
|
49
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
50
|
+
"Bash(THOROUGH=true npm run test:*)",
|
|
51
|
+
"Bash(AWS_PROFILE=ahbode.dev npm run test:integration:*)",
|
|
52
|
+
"Bash(npm run fix:*)",
|
|
53
|
+
"Bash(AWS_PROFILE=ahbode.dev npx jest:*)",
|
|
54
|
+
"Bash(AWS_PROFILE=ahbode.dev npm run deploy:dev:*)",
|
|
55
|
+
"Bash(AWS_PROFILE=ahbode.dev STAGE=dev npm run test:acceptance:*)",
|
|
56
|
+
"Bash(npm run start:testdb:*)",
|
|
57
|
+
"Bash(cat:*)",
|
|
58
|
+
"Bash(unzip:*)",
|
|
59
|
+
"Bash(npm view:*)"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
EOF
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# ensure .claude directory exists
|
|
67
|
+
mkdir -p "$(dirname "$SETTINGS_FILE")"
|
|
68
|
+
|
|
69
|
+
# initialize settings file if it doesn't exist
|
|
70
|
+
if [[ ! -f "$SETTINGS_FILE" ]]; then
|
|
71
|
+
echo "{}" > "$SETTINGS_FILE"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# apply permissions:
|
|
75
|
+
# - replace allow entirely
|
|
76
|
+
# - append to deny (unique)
|
|
77
|
+
# - append to ask (unique)
|
|
78
|
+
jq --argjson perms "$PERMISSIONS_CONFIG" '
|
|
79
|
+
# ensure .permissions exists
|
|
80
|
+
.permissions //= {} |
|
|
81
|
+
|
|
82
|
+
# replace allow entirely with our config
|
|
83
|
+
.permissions.allow = $perms.permissions.allow |
|
|
84
|
+
|
|
85
|
+
# append to deny (unique entries only)
|
|
86
|
+
.permissions.deny = ((.permissions.deny // []) + $perms.permissions.deny | unique) |
|
|
87
|
+
|
|
88
|
+
# append to ask (unique entries only)
|
|
89
|
+
.permissions.ask = ((.permissions.ask // []) + $perms.permissions.ask | unique)
|
|
90
|
+
' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp"
|
|
91
|
+
|
|
92
|
+
# check if any changes were made
|
|
93
|
+
if diff -q "$SETTINGS_FILE" "$SETTINGS_FILE.tmp" >/dev/null 2>&1; then
|
|
94
|
+
rm "$SETTINGS_FILE.tmp"
|
|
95
|
+
echo "👌 mechanic permissions already configured"
|
|
96
|
+
echo " $SETTINGS_FILE"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# atomic replace
|
|
101
|
+
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
|
102
|
+
|
|
103
|
+
echo "🔐 mechanic permissions configured successfully!"
|
|
104
|
+
echo " $SETTINGS_FILE"
|
|
105
|
+
echo ""
|
|
106
|
+
echo "✨ permissions applied:"
|
|
107
|
+
echo " • allow: replaced entirely"
|
|
108
|
+
echo " • deny: extended (no duplicates)"
|
|
109
|
+
echo " • ask: extended (no duplicates)"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
######################################################################
|
|
3
|
+
# .what = initialize Claude settings for mechanic role
|
|
4
|
+
#
|
|
5
|
+
# .why = mechanic needs both hooks and permissions configured to
|
|
6
|
+
# operate effectively. this script dispatches to both:
|
|
7
|
+
# • init.claude.hooks.sh - binds SessionStart hook
|
|
8
|
+
# • init.claude.permissions.sh - configures permissions
|
|
9
|
+
#
|
|
10
|
+
# single entry point for full Claude configuration.
|
|
11
|
+
#
|
|
12
|
+
# .how = runs both init scripts in sequence from the same directory.
|
|
13
|
+
#
|
|
14
|
+
# guarantee:
|
|
15
|
+
# ✔ runs both hooks and permissions initialization
|
|
16
|
+
# ✔ fail-fast on any error
|
|
17
|
+
# ✔ idempotent: safe to rerun
|
|
18
|
+
######################################################################
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
+
|
|
24
|
+
echo "🔧 init claude config for mechanic role..."
|
|
25
|
+
echo ""
|
|
26
|
+
|
|
27
|
+
# initialize hooks
|
|
28
|
+
"$SCRIPT_DIR/init.claude.hooks.sh"
|
|
29
|
+
echo ""
|
|
30
|
+
|
|
31
|
+
# initialize permissions
|
|
32
|
+
"$SCRIPT_DIR/init.claude.permissions.sh"
|
|
33
|
+
echo ""
|
|
34
|
+
|
|
35
|
+
echo "👌 claude config ready"
|
|
@@ -226,8 +226,14 @@ fi
|
|
|
226
226
|
echo "> $TEST_COMMAND" | tee -a "$LOG_FILE"
|
|
227
227
|
echo "" | tee -a "$LOG_FILE"
|
|
228
228
|
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
# For unit tests, strip color codes from log file while preserving them in terminal output
|
|
230
|
+
if [[ "$TEST_TYPE" == "unit" ]]; then
|
|
231
|
+
eval "$TEST_COMMAND" 2>&1 | tee >(sed 's/\x1B\[[0-9;]*[JKmsu]//g' >> "$LOG_FILE")
|
|
232
|
+
TEST_EXIT_CODE=${PIPESTATUS[0]}
|
|
233
|
+
else
|
|
234
|
+
eval "$TEST_COMMAND" 2>&1 | tee -a "$LOG_FILE"
|
|
235
|
+
TEST_EXIT_CODE=${PIPESTATUS[0]}
|
|
236
|
+
fi
|
|
231
237
|
|
|
232
238
|
echo "" | tee -a "$LOG_FILE"
|
|
233
239
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "rhachet-roles-ehmpathy",
|
|
3
3
|
"author": "ehmpathy",
|
|
4
4
|
"description": "empathetic software construction roles and skills, via rhachet",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.10.0",
|
|
6
6
|
"repository": "ehmpathy/rhachet-roles-ehmpathy",
|
|
7
7
|
"homepage": "https://github.com/ehmpathy/rhachet-roles-ehmpathy",
|
|
8
8
|
"keywords": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"fix:lint": "eslint -c ./.eslintrc.js src/**/*.ts --fix",
|
|
27
27
|
"build:clean": "rm dist/ -rf",
|
|
28
28
|
"build:compile": "tsc -p ./tsconfig.build.json",
|
|
29
|
-
"build:complete": "rsync -a --prune-empty-dirs --include='*/' --exclude='**/.route/**' --exclude='**/.scratch/**' --include='**/*.template.md' --include='**/.briefs/**/*.md' --include='**/.briefs/*.md' --include='**/.skills/**/*.sh' --include='**/.skills/*.sh' --exclude='*' src/ dist/",
|
|
29
|
+
"build:complete": "rsync -a --prune-empty-dirs --include='*/' --exclude='**/.route/**' --exclude='**/.scratch/**' --exclude='**/.behavior/**' --include='**/*.template.md' --include='**/.briefs/**/*.md' --include='**/.briefs/*.md' --include='**/.skills/**/*.sh' --include='**/.skills/*.sh' --exclude='*' src/ dist/",
|
|
30
30
|
"build": "npm run build:clean && npm run build:compile && npm run build:complete",
|
|
31
31
|
"test:commits": "LAST_TAG=$(git describe --tags --abbrev=0 @^ 2> /dev/null || git rev-list --max-parents=0 HEAD) && npx commitlint --from $LAST_TAG --to HEAD --verbose",
|
|
32
32
|
"test:types": "tsc -p ./tsconfig.build.json --noEmit",
|