codeharness 0.19.5 → 0.21.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/patches/AGENTS.md CHANGED
@@ -17,7 +17,25 @@ patches/
17
17
  retro/enforcement.md — Retrospective quality metrics
18
18
  ```
19
19
 
20
- Subdirectories map to BMAD workflow roles.
20
+ Subdirectories map to BMAD workflow roles (or analysis categories like `observability/`).
21
+
22
+ ## Observability Module (`observability/`)
23
+
24
+ Semgrep YAML rules for static analysis of observability gaps. Each `.yaml` file is a standalone Semgrep config — no build step required. Deleting a rule file removes that check.
25
+
26
+ **Rules:**
27
+ - `catch-without-logging.yaml` — Detects catch blocks without error/warn logging (WARNING)
28
+ - `function-no-debug-log.yaml` — Detects functions without debug/info logging (INFO)
29
+ - `error-path-no-log.yaml` — Detects error paths (throw/return err) without preceding log (WARNING)
30
+
31
+ **Test fixtures** (`.ts` files alongside rules, annotated with `// ruleid:` / `// ok:` comments):
32
+ - `catch-without-logging.ts` — Test cases for catch-without-logging rule
33
+ - `function-no-debug-log.ts` — Test cases for function-no-debug-log rule
34
+ - `error-path-no-log.ts` — Test cases for error-path-no-log rule
35
+
36
+ **Testing:** `semgrep --test patches/observability/` runs annotated test fixtures.
37
+
38
+ **Customization:** Edit YAML rules to add custom logger patterns (e.g., `logger.error(...)` for winston). Rules use `pattern-not` / `pattern-not-inside` to detect absence of logging.
21
39
 
22
40
  ## How Patches Work
23
41
 
@@ -0,0 +1,27 @@
1
+ # patches/observability/ — Semgrep Rules for Observability Gap Detection
2
+
3
+ Standalone Semgrep YAML rules for static analysis of observability gaps. Each `.yaml` file is a complete Semgrep config — no build step, no TypeScript. Deleting a rule file removes that check.
4
+
5
+ ## Rules
6
+
7
+ | File | Purpose | Severity |
8
+ |------|---------|----------|
9
+ | catch-without-logging.yaml | Detects catch blocks without error/warn logging | WARNING |
10
+ | function-no-debug-log.yaml | Detects functions without debug/info logging | INFO |
11
+ | error-path-no-log.yaml | Detects error paths (throw/return err) without preceding log | WARNING |
12
+
13
+ ## Test Fixtures
14
+
15
+ | File | Purpose |
16
+ |------|---------|
17
+ | catch-without-logging.ts | Test cases for catch-without-logging rule (annotated with `// ruleid:` / `// ok:`) |
18
+ | function-no-debug-log.ts | Test cases for function-no-debug-log rule |
19
+ | error-path-no-log.ts | Test cases for error-path-no-log rule |
20
+
21
+ ## Testing
22
+
23
+ Run `semgrep --test patches/observability/` to execute all test fixtures against their rules.
24
+
25
+ ## Customization
26
+
27
+ Edit YAML rules to add custom logger patterns (e.g., `logger.error(...)` for winston). Rules use `pattern-not` / `pattern-not-inside` to detect absence of logging.
@@ -0,0 +1,36 @@
1
+ // Test cases for catch-without-logging Semgrep rule
2
+
3
+ // ruleid: catch-without-logging
4
+ try { doSomething(); } catch (e) { /* no logging at all */ }
5
+
6
+ // ruleid: catch-without-logging
7
+ try {
8
+ riskyOperation();
9
+ } catch (err) {
10
+ cleanup();
11
+ }
12
+
13
+ // ok: catch-without-logging
14
+ try { doSomething(); } catch (e) { console.error('failed', e); }
15
+
16
+ // ok: catch-without-logging
17
+ try {
18
+ riskyOperation();
19
+ } catch (err) {
20
+ console.warn('operation failed', err);
21
+ cleanup();
22
+ }
23
+
24
+ // ok: catch-without-logging
25
+ try {
26
+ riskyOperation();
27
+ } catch (err) {
28
+ logger.error('operation failed', err);
29
+ }
30
+
31
+ // ok: catch-without-logging
32
+ try {
33
+ riskyOperation();
34
+ } catch (err) {
35
+ logger.warn('operation failed', err);
36
+ }
@@ -0,0 +1,47 @@
1
+ // Test cases for error-path-no-log Semgrep rule
2
+
3
+ function badThrow() {
4
+ // ruleid: error-path-no-log
5
+ throw new Error('something went wrong');
6
+ }
7
+
8
+ function badReturn() {
9
+ // ruleid: error-path-no-log
10
+ return err('something went wrong');
11
+ }
12
+
13
+ function goodThrow() {
14
+ // ok: error-path-no-log
15
+ console.error('about to throw');
16
+ throw new Error('something went wrong');
17
+ }
18
+
19
+ function goodReturn() {
20
+ // ok: error-path-no-log
21
+ console.error('returning error');
22
+ return err('something went wrong');
23
+ }
24
+
25
+ function goodThrowWithLogger() {
26
+ // ok: error-path-no-log
27
+ logger.error('about to throw');
28
+ throw new Error('something went wrong');
29
+ }
30
+
31
+ function goodReturnWithLogger() {
32
+ // ok: error-path-no-log
33
+ logger.error('returning error');
34
+ return err('something went wrong');
35
+ }
36
+
37
+ function goodThrowWithWarn() {
38
+ // ok: error-path-no-log
39
+ console.warn('about to throw');
40
+ throw new Error('something went wrong');
41
+ }
42
+
43
+ function goodReturnWithLoggerWarn() {
44
+ // ok: error-path-no-log
45
+ logger.warn('returning error');
46
+ return err('something went wrong');
47
+ }
@@ -0,0 +1,54 @@
1
+ // Test cases for function-no-debug-log Semgrep rule
2
+
3
+ // ruleid: function-no-debug-log
4
+ function processData(input: string) {
5
+ return input.trim();
6
+ }
7
+
8
+ // ruleid: function-no-debug-log
9
+ function handleRequest(req: any) {
10
+ const result = compute(req);
11
+ return result;
12
+ }
13
+
14
+ // ruleid: function-no-debug-log
15
+ const transformData = (input: string) => {
16
+ return input.toUpperCase();
17
+ };
18
+
19
+ // ok: function-no-debug-log
20
+ function processDataWithLog(input: string) {
21
+ console.log('processing data', input);
22
+ return input.trim();
23
+ }
24
+
25
+ // ok: function-no-debug-log
26
+ function handleRequestWithDebug(req: any) {
27
+ console.debug('handling request', req);
28
+ const result = compute(req);
29
+ return result;
30
+ }
31
+
32
+ // ok: function-no-debug-log
33
+ function serviceCall(params: any) {
34
+ logger.debug('service call', params);
35
+ return fetch(params.url);
36
+ }
37
+
38
+ // ok: function-no-debug-log
39
+ function anotherService(params: any) {
40
+ logger.info('another service call', params);
41
+ return fetch(params.url);
42
+ }
43
+
44
+ // ok: function-no-debug-log
45
+ const transformWithLog = (input: string) => {
46
+ console.log('transforming', input);
47
+ return input.toUpperCase();
48
+ };
49
+
50
+ // ok: function-no-debug-log
51
+ const arrowWithDebug = (input: string) => {
52
+ logger.debug('arrow function', input);
53
+ return input.toLowerCase();
54
+ };
@@ -0,0 +1,36 @@
1
+ // Test cases for catch-without-logging Semgrep rule
2
+
3
+ // ruleid: catch-without-logging
4
+ try { doSomething(); } catch (e) { /* no logging at all */ }
5
+
6
+ // ruleid: catch-without-logging
7
+ try {
8
+ riskyOperation();
9
+ } catch (err) {
10
+ cleanup();
11
+ }
12
+
13
+ // ok: catch-without-logging
14
+ try { doSomething(); } catch (e) { console.error('failed', e); }
15
+
16
+ // ok: catch-without-logging
17
+ try {
18
+ riskyOperation();
19
+ } catch (err) {
20
+ console.warn('operation failed', err);
21
+ cleanup();
22
+ }
23
+
24
+ // ok: catch-without-logging
25
+ try {
26
+ riskyOperation();
27
+ } catch (err) {
28
+ logger.error('operation failed', err);
29
+ }
30
+
31
+ // ok: catch-without-logging
32
+ try {
33
+ riskyOperation();
34
+ } catch (err) {
35
+ logger.warn('operation failed', err);
36
+ }
@@ -0,0 +1,35 @@
1
+ rules:
2
+ - id: catch-without-logging
3
+ patterns:
4
+ - pattern: |
5
+ try { ... } catch ($ERR) { ... }
6
+ - pattern-not: |
7
+ try { ... } catch ($ERR) {
8
+ ...
9
+ console.error(...)
10
+ ...
11
+ }
12
+ - pattern-not: |
13
+ try { ... } catch ($ERR) {
14
+ ...
15
+ console.warn(...)
16
+ ...
17
+ }
18
+ - pattern-not: |
19
+ try { ... } catch ($ERR) {
20
+ ...
21
+ logger.error(...)
22
+ ...
23
+ }
24
+ - pattern-not: |
25
+ try { ... } catch ($ERR) {
26
+ ...
27
+ logger.warn(...)
28
+ ...
29
+ }
30
+ message: "Catch block without error logging — observability gap"
31
+ languages: [typescript, javascript]
32
+ severity: WARNING
33
+ metadata:
34
+ category: observability
35
+ cwe: "CWE-778: Insufficient Logging"
@@ -0,0 +1,47 @@
1
+ // Test cases for error-path-no-log Semgrep rule
2
+
3
+ function badThrow() {
4
+ // ruleid: error-path-no-log
5
+ throw new Error('something went wrong');
6
+ }
7
+
8
+ function badReturn() {
9
+ // ruleid: error-path-no-log
10
+ return err('something went wrong');
11
+ }
12
+
13
+ function goodThrow() {
14
+ // ok: error-path-no-log
15
+ console.error('about to throw');
16
+ throw new Error('something went wrong');
17
+ }
18
+
19
+ function goodReturn() {
20
+ // ok: error-path-no-log
21
+ console.error('returning error');
22
+ return err('something went wrong');
23
+ }
24
+
25
+ function goodThrowWithLogger() {
26
+ // ok: error-path-no-log
27
+ logger.error('about to throw');
28
+ throw new Error('something went wrong');
29
+ }
30
+
31
+ function goodReturnWithLogger() {
32
+ // ok: error-path-no-log
33
+ logger.error('returning error');
34
+ return err('something went wrong');
35
+ }
36
+
37
+ function goodThrowWithWarn() {
38
+ // ok: error-path-no-log
39
+ console.warn('about to throw');
40
+ throw new Error('something went wrong');
41
+ }
42
+
43
+ function goodReturnWithLoggerWarn() {
44
+ // ok: error-path-no-log
45
+ logger.warn('returning error');
46
+ return err('something went wrong');
47
+ }
@@ -0,0 +1,68 @@
1
+ rules:
2
+ - id: error-path-no-log
3
+ patterns:
4
+ - pattern-either:
5
+ - pattern: throw $ERR;
6
+ - pattern: return err(...);
7
+ - pattern-not-inside: |
8
+ {
9
+ ...
10
+ console.error(...)
11
+ ...
12
+ throw $ERR;
13
+ }
14
+ - pattern-not-inside: |
15
+ {
16
+ ...
17
+ console.warn(...)
18
+ ...
19
+ throw $ERR;
20
+ }
21
+ - pattern-not-inside: |
22
+ {
23
+ ...
24
+ logger.error(...)
25
+ ...
26
+ throw $ERR;
27
+ }
28
+ - pattern-not-inside: |
29
+ {
30
+ ...
31
+ logger.warn(...)
32
+ ...
33
+ throw $ERR;
34
+ }
35
+ - pattern-not-inside: |
36
+ {
37
+ ...
38
+ console.error(...)
39
+ ...
40
+ return err(...);
41
+ }
42
+ - pattern-not-inside: |
43
+ {
44
+ ...
45
+ console.warn(...)
46
+ ...
47
+ return err(...);
48
+ }
49
+ - pattern-not-inside: |
50
+ {
51
+ ...
52
+ logger.error(...)
53
+ ...
54
+ return err(...);
55
+ }
56
+ - pattern-not-inside: |
57
+ {
58
+ ...
59
+ logger.warn(...)
60
+ ...
61
+ return err(...);
62
+ }
63
+ message: "Error path without logging — observability gap"
64
+ languages: [typescript, javascript]
65
+ severity: WARNING
66
+ metadata:
67
+ category: observability
68
+ cwe: "CWE-778: Insufficient Logging"
@@ -0,0 +1,54 @@
1
+ // Test cases for function-no-debug-log Semgrep rule
2
+
3
+ // ruleid: function-no-debug-log
4
+ function processData(input: string) {
5
+ return input.trim();
6
+ }
7
+
8
+ // ruleid: function-no-debug-log
9
+ function handleRequest(req: any) {
10
+ const result = compute(req);
11
+ return result;
12
+ }
13
+
14
+ // ruleid: function-no-debug-log
15
+ const transformData = (input: string) => {
16
+ return input.toUpperCase();
17
+ };
18
+
19
+ // ok: function-no-debug-log
20
+ function processDataWithLog(input: string) {
21
+ console.log('processing data', input);
22
+ return input.trim();
23
+ }
24
+
25
+ // ok: function-no-debug-log
26
+ function handleRequestWithDebug(req: any) {
27
+ console.debug('handling request', req);
28
+ const result = compute(req);
29
+ return result;
30
+ }
31
+
32
+ // ok: function-no-debug-log
33
+ function serviceCall(params: any) {
34
+ logger.debug('service call', params);
35
+ return fetch(params.url);
36
+ }
37
+
38
+ // ok: function-no-debug-log
39
+ function anotherService(params: any) {
40
+ logger.info('another service call', params);
41
+ return fetch(params.url);
42
+ }
43
+
44
+ // ok: function-no-debug-log
45
+ const transformWithLog = (input: string) => {
46
+ console.log('transforming', input);
47
+ return input.toUpperCase();
48
+ };
49
+
50
+ // ok: function-no-debug-log
51
+ const arrowWithDebug = (input: string) => {
52
+ logger.debug('arrow function', input);
53
+ return input.toLowerCase();
54
+ };
@@ -0,0 +1,114 @@
1
+ rules:
2
+ - id: function-no-debug-log
3
+ patterns:
4
+ - pattern-either:
5
+ - pattern: |
6
+ function $FUNC(...) { ... }
7
+ - pattern: |
8
+ const $FUNC = (...) => { ... }
9
+ - pattern: |
10
+ let $FUNC = (...) => { ... }
11
+ - pattern: |
12
+ $FUNC(...) { ... }
13
+ - pattern-not: |
14
+ function $FUNC(...) {
15
+ ...
16
+ console.log(...)
17
+ ...
18
+ }
19
+ - pattern-not: |
20
+ function $FUNC(...) {
21
+ ...
22
+ console.debug(...)
23
+ ...
24
+ }
25
+ - pattern-not: |
26
+ function $FUNC(...) {
27
+ ...
28
+ logger.debug(...)
29
+ ...
30
+ }
31
+ - pattern-not: |
32
+ function $FUNC(...) {
33
+ ...
34
+ logger.info(...)
35
+ ...
36
+ }
37
+ - pattern-not: |
38
+ const $FUNC = (...) => {
39
+ ...
40
+ console.log(...)
41
+ ...
42
+ }
43
+ - pattern-not: |
44
+ const $FUNC = (...) => {
45
+ ...
46
+ console.debug(...)
47
+ ...
48
+ }
49
+ - pattern-not: |
50
+ const $FUNC = (...) => {
51
+ ...
52
+ logger.debug(...)
53
+ ...
54
+ }
55
+ - pattern-not: |
56
+ const $FUNC = (...) => {
57
+ ...
58
+ logger.info(...)
59
+ ...
60
+ }
61
+ - pattern-not: |
62
+ let $FUNC = (...) => {
63
+ ...
64
+ console.log(...)
65
+ ...
66
+ }
67
+ - pattern-not: |
68
+ let $FUNC = (...) => {
69
+ ...
70
+ console.debug(...)
71
+ ...
72
+ }
73
+ - pattern-not: |
74
+ let $FUNC = (...) => {
75
+ ...
76
+ logger.debug(...)
77
+ ...
78
+ }
79
+ - pattern-not: |
80
+ let $FUNC = (...) => {
81
+ ...
82
+ logger.info(...)
83
+ ...
84
+ }
85
+ - pattern-not: |
86
+ $FUNC(...) {
87
+ ...
88
+ console.log(...)
89
+ ...
90
+ }
91
+ - pattern-not: |
92
+ $FUNC(...) {
93
+ ...
94
+ console.debug(...)
95
+ ...
96
+ }
97
+ - pattern-not: |
98
+ $FUNC(...) {
99
+ ...
100
+ logger.debug(...)
101
+ ...
102
+ }
103
+ - pattern-not: |
104
+ $FUNC(...) {
105
+ ...
106
+ logger.info(...)
107
+ ...
108
+ }
109
+ message: "Function without debug/info logging — observability gap"
110
+ languages: [typescript, javascript]
111
+ severity: INFO
112
+ metadata:
113
+ category: observability
114
+ cwe: "CWE-778: Insufficient Logging"
@@ -79,10 +79,8 @@ driver_build_command() {
79
79
  CLAUDE_CMD_ARGS+=("--plugin-dir" "$plugin_dir")
80
80
  fi
81
81
 
82
- # Output format
83
- if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
84
- CLAUDE_CMD_ARGS+=("--output-format" "json")
85
- fi
82
+ # Output format — always stream-json for real-time NDJSON output
83
+ CLAUDE_CMD_ARGS+=("--output-format" "stream-json" "--verbose" "--include-partial-messages")
86
84
 
87
85
  # Allowed tools
88
86
  if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
@@ -123,30 +121,6 @@ driver_supports_live_output() {
123
121
  return 0
124
122
  }
125
123
 
126
- # Prepare command arguments for live stream-json output
127
- driver_prepare_live_command() {
128
- LIVE_CMD_ARGS=()
129
- local skip_next=false
130
-
131
- for arg in "${CLAUDE_CMD_ARGS[@]}"; do
132
- if [[ "$skip_next" == "true" ]]; then
133
- LIVE_CMD_ARGS+=("stream-json")
134
- skip_next=false
135
- elif [[ "$arg" == "--output-format" ]]; then
136
- LIVE_CMD_ARGS+=("$arg")
137
- skip_next=true
138
- else
139
- LIVE_CMD_ARGS+=("$arg")
140
- fi
141
- done
142
-
143
- if [[ "$skip_next" == "true" ]]; then
144
- return 1
145
- fi
146
-
147
- LIVE_CMD_ARGS+=("--verbose" "--include-partial-messages")
148
- }
149
-
150
124
  # Stream filter for raw Claude stream-json events
151
125
  driver_stream_filter() {
152
126
  echo '