@raquezha/notrace 0.0.5 → 0.0.7
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/CHANGELOG.md +16 -0
- package/README.md +122 -52
- package/assets/notrace-logo.svg +20 -0
- package/assets/notrace-mark.svg +18 -0
- package/assets/notrace-wordmark.svg +4 -0
- package/bin/notrace-compare.mjs +153 -0
- package/bin/notrace-review.mjs +123 -0
- package/dist/notrace/adapters.d.ts +32 -0
- package/dist/notrace/adapters.js +83 -0
- package/dist/notrace/index.d.ts +2 -0
- package/dist/notrace/index.js +335 -0
- package/dist/notrace/renderer.d.ts +4 -0
- package/dist/notrace/renderer.js +681 -0
- package/dist/notrace/types.d.ts +94 -0
- package/dist/notrace/types.js +1 -0
- package/extensions/notrace/adapters.ts +88 -0
- package/extensions/notrace/index.ts +393 -0
- package/extensions/notrace/renderer.ts +694 -0
- package/extensions/notrace/types.ts +109 -0
- package/package.json +10 -3
- package/templates/README.md +24 -0
- package/templates/dashboard.sample.html +399 -0
- package/templates/dashboard.sample.json +113 -0
- package/templates/notrace-logo.preview.png +0 -0
- package/templates/render-samples.mjs +44 -0
- package/templates/session.sample.html +499 -0
- package/templates/session.sample.json +127 -0
- package/templates/sessions/019ecec4-1c48-7b47-bdbf-cec3500978ed/notrace.html +313 -0
- package/templates/sessions/019ecec4-1c48-7b47-bdbf-cec3500978ed/notrace.json +129 -0
- package/templates/sessions/019ecfa1-7ac2-7c00-bbe9-541f37751201/notrace.html +313 -0
- package/templates/sessions/019ecfa1-7ac2-7c00-bbe9-541f37751201/notrace.json +129 -0
- package/templates/sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html +494 -0
- package/templates/sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.json +134 -0
- package/templates/sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html +493 -0
- package/templates/sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.json +133 -0
- package/templates/sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html +494 -0
- package/templates/sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.json +134 -0
- package/templates/sessions/019ed2ee-1003-76ee-b353-000000000004/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1003-76ee-b353-000000000004/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1004-76ee-b353-000000000005/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1004-76ee-b353-000000000005/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1005-76ee-b353-000000000006/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1005-76ee-b353-000000000006/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1006-76ee-b353-000000000007/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1006-76ee-b353-000000000007/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1007-76ee-b353-000000000008/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1007-76ee-b353-000000000008/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1008-76ee-b353-000000000009/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1008-76ee-b353-000000000009/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1009-76ee-b353-000000000010/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1009-76ee-b353-000000000010/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1010-76ee-b353-000000000011/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1010-76ee-b353-000000000011/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1011-76ee-b353-000000000012/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1011-76ee-b353-000000000012/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1012-76ee-b353-000000000013/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1012-76ee-b353-000000000013/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1013-76ee-b353-000000000014/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1013-76ee-b353-000000000014/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1014-76ee-b353-000000000015/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1014-76ee-b353-000000000015/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1015-76ee-b353-000000000016/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1015-76ee-b353-000000000016/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1016-76ee-b353-000000000017/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1016-76ee-b353-000000000017/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1017-76ee-b353-000000000018/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1017-76ee-b353-000000000018/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1018-76ee-b353-000000000019/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1018-76ee-b353-000000000019/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1019-76ee-b353-000000000020/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1019-76ee-b353-000000000020/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1020-76ee-b353-000000000021/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1020-76ee-b353-000000000021/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1021-76ee-b353-000000000022/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1021-76ee-b353-000000000022/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1022-76ee-b353-000000000023/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1022-76ee-b353-000000000023/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1023-76ee-b353-000000000024/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1023-76ee-b353-000000000024/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1024-76ee-b353-000000000025/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1024-76ee-b353-000000000025/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1025-76ee-b353-000000000026/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1025-76ee-b353-000000000026/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1026-76ee-b353-000000000027/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1026-76ee-b353-000000000027/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1027-76ee-b353-000000000028/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1027-76ee-b353-000000000028/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1028-76ee-b353-000000000029/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1028-76ee-b353-000000000029/notrace.json +130 -0
- package/templates/sessions/019ed2ee-1029-76ee-b353-000000000030/notrace.html +423 -0
- package/templates/sessions/019ed2ee-1029-76ee-b353-000000000030/notrace.json +130 -0
- package/templates/sessions/019ed2ee-5252-76ee-b353-ad925a6bad31/notrace.html +313 -0
- package/templates/sessions/019ed2ee-5252-76ee-b353-ad925a6bad31/notrace.json +129 -0
- package/tsconfig.json +1 -1
- package/dist/notrace.d.ts +0 -9
- package/dist/notrace.js +0 -914
- package/extensions/notrace.ts +0 -965
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @raquezha/notrace
|
|
2
2
|
|
|
3
|
+
## 0.0.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5a3e563: Improve session reports by rendering the session ID as a copyable chip under the notrace logo.
|
|
8
|
+
- 5a3e563: Enhance the trace header to include the active git branch alongside the repository name, and clarify the capture setting label.
|
|
9
|
+
- 7664e50: Polish notrace reliability and installed-package ergonomics: add review/compare package CLIs, validate run records before writing, atomically write private artifacts, recover from corrupt index JSON, and verify capture modes.
|
|
10
|
+
|
|
11
|
+
## 0.0.6
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- d349d36: Refresh the notrace UI and sample session rendering separately from the antigravity billing fix.
|
|
16
|
+
- 51fda83: fix: preserve assistant toolCall blocks in noheadroom compression and expose notrace failure metadata
|
|
17
|
+
- 7afa746: Package updates for antigravity, norpiv, and notrace.
|
|
18
|
+
|
|
3
19
|
## 0.0.5
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,101 +1,171 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/notrace-logo.svg" alt="notrace logo" width="240" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# notrace
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
**Traces in, lessons out.**
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
`notrace` is a local-first retrospective engine for the Pi Coding Agent.
|
|
10
|
+
It captures session evidence, writes a versioned `notrace.json` run record, renders a human-readable HTML report, and supports review/compare flows for workflow R&D.
|
|
6
11
|
|
|
7
|
-
##
|
|
12
|
+
## What notrace owns
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
When enabled, `notrace` is the durable retrospective layer for a session.
|
|
15
|
+
It aggregates:
|
|
16
|
+
- core Pi session telemetry
|
|
17
|
+
- workflow/task context
|
|
18
|
+
- optional dynamic extension telemetry
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **together**: RPIV `WORK.md [LOG]` may reference notrace artifacts, but `.workflow/` should not own them
|
|
20
|
+
Today, Pi is the first harness adapter.
|
|
21
|
+
The canonical run schema is designed so other harness adapters can be added later, but multi-harness support is not implemented in this package yet.
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
## What notrace does not own
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
`notrace` is **not**:
|
|
26
|
+
- the live Pi footer
|
|
27
|
+
- the Pi resume/session-switch UX
|
|
28
|
+
- a scraper of terminal status strings
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
- **Metrics dashboard**: Total tokens, input/output split, cache reads, cost (USD), duration
|
|
21
|
-
- **Machine-readable run record**: Normalized `notrace.json` sidecar for future retrospective/compare flows
|
|
22
|
-
- **Clickable `file://` links**: Artifact paths printed to console at session end for instant browser access
|
|
23
|
-
- **Workdir aware**: notrace-owned artifacts are planned to live under `.notrace/` for the root execution directory
|
|
24
|
-
- **RPIV attachment**: When a task has a `WORK.md`, notrace appends artifact/review references into `[LOG]`
|
|
25
|
-
- **HTML report**: Self-contained/offline report with a restrictive CSP and no remote font/network loads
|
|
26
|
-
- **Safer defaults**: Secret-key/value redaction, bounded payload sizes, metadata-only mode, private file permissions, and `.workflow`-confined artifact writes
|
|
30
|
+
Live footer output, resume hints, and extension footer badges may appear near `notrace` output during shutdown, but they are separate producers.
|
|
27
31
|
|
|
28
|
-
##
|
|
32
|
+
## Retrospective spine
|
|
29
33
|
|
|
34
|
+
1. **Capture evidence**: `notrace.json`
|
|
35
|
+
2. **Inspect**: `notrace.html`
|
|
36
|
+
3. **Review outcome**: `notrace.review.json`
|
|
37
|
+
4. **Compare attempts**: `compare:notrace`
|
|
38
|
+
|
|
39
|
+
## Storage
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
.notrace/
|
|
43
|
+
index.json
|
|
44
|
+
index.html
|
|
45
|
+
sessions/
|
|
46
|
+
<session-id>/
|
|
47
|
+
notrace.json
|
|
48
|
+
notrace.html
|
|
49
|
+
notrace.review.json
|
|
30
50
|
```
|
|
31
|
-
🔍 [notrace] Observability artifacts generated:
|
|
32
|
-
📂 file:///path/to/.notrace/sessions/<session-id>/notrace.html
|
|
33
|
-
📂 file:///path/to/.notrace/sessions/<session-id>/notrace.json
|
|
34
|
-
```
|
|
35
51
|
|
|
36
|
-
##
|
|
52
|
+
## Canonical run model
|
|
53
|
+
|
|
54
|
+
Generated `notrace.json` is the source of truth for runtime output, HTML rendering, and downstream tooling.
|
|
55
|
+
The record is versioned and centers on:
|
|
56
|
+
- `kind`
|
|
57
|
+
- `schemaVersion`
|
|
58
|
+
- `traceId`
|
|
59
|
+
- `repository`
|
|
60
|
+
- `session`
|
|
61
|
+
- `task`
|
|
62
|
+
- `captureMode`
|
|
63
|
+
- `conditions`
|
|
64
|
+
- `activity`
|
|
65
|
+
- `telemetry`
|
|
66
|
+
- `events`
|
|
67
|
+
|
|
68
|
+
Key rule:
|
|
69
|
+
- **consumed tokens** and **saved tokens** are separate metric families
|
|
70
|
+
- optimization telemetry belongs under `telemetry.extensions.*`
|
|
71
|
+
- presentation-only UI strings are not canonical evidence
|
|
72
|
+
|
|
73
|
+
## Dynamic extension telemetry
|
|
74
|
+
|
|
75
|
+
`notrace` can include optional structured telemetry from dynamic extensions.
|
|
76
|
+
Current first target is `noheadroom`.
|
|
77
|
+
|
|
78
|
+
If an extension is absent, `notrace` should still succeed.
|
|
79
|
+
If an extension is present, it can contribute a structured summary such as:
|
|
80
|
+
- loaded / enabled / active state
|
|
81
|
+
- optimization attempts
|
|
82
|
+
- tokens saved
|
|
83
|
+
- last applied compression summary
|
|
84
|
+
|
|
85
|
+
## Capture modes
|
|
86
|
+
|
|
87
|
+
Default capture mode is **full**.
|
|
37
88
|
|
|
38
89
|
```bash
|
|
39
|
-
# Load directly
|
|
40
90
|
pi --extension ./packages/notrace
|
|
41
|
-
|
|
42
|
-
# Via nothing mindset (dev, rpiv)
|
|
43
|
-
pi --dev
|
|
44
91
|
```
|
|
45
92
|
|
|
46
|
-
|
|
93
|
+
Optional modes:
|
|
47
94
|
|
|
48
95
|
```bash
|
|
49
|
-
|
|
96
|
+
NOTRACE_CAPTURE=redacted pi --extension ./packages/notrace
|
|
97
|
+
NOTRACE_CAPTURE=metadata pi --extension ./packages/notrace
|
|
98
|
+
NOTRACE_CAPTURE=full pi --extension ./packages/notrace
|
|
50
99
|
```
|
|
51
100
|
|
|
52
|
-
|
|
101
|
+
Mode meanings:
|
|
102
|
+
- `full`: full captured payloads; best for local debugging; highest sensitivity
|
|
103
|
+
- `redacted`: captured payloads with common secret-like values redacted
|
|
104
|
+
- `metadata`: minimal capture, no prompt/tool bodies
|
|
105
|
+
|
|
106
|
+
**Security warning:** `full` reports can contain prompts, tool arguments, tool outputs, local paths, model payloads, and secrets returned by tools. `redacted` mode removes common secret-shaped values and sensitive keys, but redaction is best-effort and can miss project-specific secrets. `metadata` mode is safest for sharing because prompt/tool bodies are omitted, but reports can still reveal repository names, paths, timing, models, providers, and workflow metadata. Do not publish generated reports without review.
|
|
107
|
+
|
|
108
|
+
## Review
|
|
109
|
+
|
|
110
|
+
From this monorepo:
|
|
53
111
|
|
|
54
112
|
```bash
|
|
55
|
-
npm run review:notrace --
|
|
113
|
+
npm run review:notrace -- \
|
|
114
|
+
.notrace/sessions/<id>/notrace.json \
|
|
56
115
|
--outcome partial \
|
|
57
116
|
--friction high \
|
|
58
117
|
--lesson "Headroom reduced tokens but needed manual steering." \
|
|
59
118
|
--next-change "Try same task with RepoScry enabled."
|
|
60
119
|
```
|
|
61
120
|
|
|
62
|
-
|
|
121
|
+
From an installed package:
|
|
63
122
|
|
|
64
|
-
|
|
123
|
+
```bash
|
|
124
|
+
npx -p @raquezha/notrace notrace-review \
|
|
125
|
+
.notrace/sessions/<id>/notrace.json \
|
|
126
|
+
--outcome partial \
|
|
127
|
+
--friction high \
|
|
128
|
+
--lesson "Headroom reduced tokens but needed manual steering." \
|
|
129
|
+
--next-change "Try same task with RepoScry enabled."
|
|
130
|
+
```
|
|
65
131
|
|
|
66
132
|
Review fields:
|
|
67
|
-
|
|
68
133
|
- `outcome`: `success`, `partial`, `failed`, `abandoned`, `inconclusive`
|
|
69
134
|
- `friction`: `low`, `medium`, `high`
|
|
70
|
-
- `lesson
|
|
71
|
-
- `nextChange
|
|
135
|
+
- `lesson`
|
|
136
|
+
- `nextChange`
|
|
137
|
+
|
|
138
|
+
## Compare
|
|
72
139
|
|
|
73
|
-
|
|
140
|
+
From this monorepo:
|
|
74
141
|
|
|
75
142
|
```bash
|
|
76
143
|
npm run compare:notrace -- \
|
|
77
|
-
|
|
78
|
-
|
|
144
|
+
.notrace/sessions/<baseline-id>/notrace.json \
|
|
145
|
+
.notrace/sessions/<candidate-id>/notrace.json
|
|
79
146
|
```
|
|
80
147
|
|
|
81
|
-
|
|
148
|
+
From an installed package:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npx -p @raquezha/notrace notrace-compare \
|
|
152
|
+
.notrace/sessions/<baseline-id>/notrace.json \
|
|
153
|
+
.notrace/sessions/<candidate-id>/notrace.json
|
|
154
|
+
```
|
|
82
155
|
|
|
83
|
-
|
|
84
|
-
- duration
|
|
85
|
-
- LLM calls
|
|
86
|
-
- tool calls
|
|
87
|
-
- tool errors
|
|
88
|
-
- total cost
|
|
89
|
-
- model/provider mix
|
|
90
|
-
- review sidecar outcome/friction/lesson when present
|
|
156
|
+
## Templates
|
|
91
157
|
|
|
92
|
-
|
|
158
|
+
HTML source-of-truth lives in `templates/`:
|
|
159
|
+
- `dashboard.sample.json`
|
|
160
|
+
- `session.sample.json`
|
|
161
|
+
- `dashboard.sample.html`
|
|
162
|
+
- `session.sample.html`
|
|
93
163
|
|
|
94
|
-
|
|
164
|
+
Refresh previews after renderer changes:
|
|
95
165
|
|
|
96
166
|
```bash
|
|
97
|
-
|
|
98
|
-
|
|
167
|
+
cd packages/notrace
|
|
168
|
+
npm run render:samples
|
|
99
169
|
```
|
|
100
170
|
|
|
101
171
|
## Build
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<svg viewBox="0 0 420 138" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="notrace">
|
|
2
|
+
<title>notrace logo</title>
|
|
3
|
+
<desc>Signal icon above and slightly overlapping the notrace wordmark.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
6
|
+
<stop offset="0%" stop-color="#E2754A"/>
|
|
7
|
+
<stop offset="100%" stop-color="#EDE2D2"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
</defs>
|
|
10
|
+
<g id="trace-icon" transform="translate(1 -5) scale(0.93)">
|
|
11
|
+
<path d="M6,50 C16,18 26,18 36,50 C46,82 54,82 60,50 C64,30 68,30 71,50"
|
|
12
|
+
fill="none" stroke="url(#fadeGrad)" stroke-width="4" stroke-linecap="round"/>
|
|
13
|
+
<line x1="74" y1="50" x2="79" y2="50" stroke="#D9C9B5" stroke-width="4" stroke-linecap="round" stroke-opacity="0.6"/>
|
|
14
|
+
<circle cx="85" cy="50" r="2.2" fill="#D9C9B5" opacity="0.5"/>
|
|
15
|
+
<circle cx="90" cy="50" r="1.4" fill="#EDE2D2" opacity="0.32"/>
|
|
16
|
+
<circle cx="94" cy="50" r="0.9" fill="#EDE2D2" opacity="0.15"/>
|
|
17
|
+
</g>
|
|
18
|
+
<text x="0" y="114" fill="#ECE3DA" style="fill:#ECE3DA" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">no</text>
|
|
19
|
+
<text x="82" y="114" fill="#D88462" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">trace</text>
|
|
20
|
+
</svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img">
|
|
2
|
+
<title>notrace logo mark</title>
|
|
3
|
+
<desc>A wave that smooths into a flat line, then fades into dots — color shifts from trace orange to no cream as it dissolves.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
6
|
+
<stop offset="0%" stop-color="#E2754A"/>
|
|
7
|
+
<stop offset="100%" stop-color="#EDE2D2"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
</defs>
|
|
10
|
+
<g id="trace-icon">
|
|
11
|
+
<path d="M6,50 C16,18 26,18 36,50 C46,82 54,82 60,50 C64,30 68,30 71,50"
|
|
12
|
+
fill="none" stroke="url(#fadeGrad)" stroke-width="4" stroke-linecap="round"/>
|
|
13
|
+
<line x1="74" y1="50" x2="79" y2="50" stroke="#D9C9B5" stroke-width="4" stroke-linecap="round" stroke-opacity="0.6"/>
|
|
14
|
+
<circle cx="85" cy="50" r="2.2" fill="#D9C9B5" opacity="0.5"/>
|
|
15
|
+
<circle cx="90" cy="50" r="1.4" fill="#EDE2D2" opacity="0.32"/>
|
|
16
|
+
<circle cx="94" cy="50" r="0.9" fill="#EDE2D2" opacity="0.15"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg viewBox="0 0 420 96" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="notrace wordmark">
|
|
2
|
+
<text x="0" y="76" fill="#ECE3DA" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">no</text>
|
|
3
|
+
<text x="82" y="76" fill="#D88462" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">trace</text>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve, relative, dirname, basename, extname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
function usage() {
|
|
6
|
+
console.error("Usage: notrace-compare <baseline-notrace.json> <candidate-notrace.json>");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const [, , baselineArg, candidateArg] = process.argv;
|
|
11
|
+
if (!baselineArg || !candidateArg) usage();
|
|
12
|
+
|
|
13
|
+
function loadReview(runPath) {
|
|
14
|
+
const reviewPath = join(dirname(runPath), `${basename(runPath, extname(runPath))}.review.json`);
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse(readFileSync(reviewPath, "utf8"));
|
|
17
|
+
if (data?.kind !== "notrace-review") return null;
|
|
18
|
+
return data;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function loadRun(filePath) {
|
|
25
|
+
const absolutePath = resolve(filePath);
|
|
26
|
+
const data = JSON.parse(readFileSync(absolutePath, "utf8"));
|
|
27
|
+
if (data?.kind !== "notrace-run") {
|
|
28
|
+
throw new Error(`${filePath} is not a notrace run record`);
|
|
29
|
+
}
|
|
30
|
+
return { path: absolutePath, data, review: loadReview(absolutePath) };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function fmtNumber(value) {
|
|
34
|
+
return Number(value || 0).toLocaleString();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function fmtUsd(value) {
|
|
38
|
+
return `$${Number(value || 0).toFixed(5)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fmtMs(value) {
|
|
42
|
+
const ms = Number(value || 0);
|
|
43
|
+
if (ms < 1000) return `${ms}ms`;
|
|
44
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fmtDelta(delta, formatter = fmtNumber, invertGood = false) {
|
|
48
|
+
const good = invertGood ? delta > 0 : delta < 0;
|
|
49
|
+
const bad = invertGood ? delta < 0 : delta > 0;
|
|
50
|
+
const sign = delta > 0 ? "+" : "";
|
|
51
|
+
const text = `${sign}${formatter(delta)}`;
|
|
52
|
+
if (good) return `${text} better`;
|
|
53
|
+
if (bad) return `${text} worse`;
|
|
54
|
+
return `${text} same`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function list(value) {
|
|
58
|
+
return Array.isArray(value) && value.length ? value.join(", ") : "-";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const baseline = loadRun(baselineArg);
|
|
62
|
+
const candidate = loadRun(candidateArg);
|
|
63
|
+
|
|
64
|
+
const a = baseline.data;
|
|
65
|
+
const b = candidate.data;
|
|
66
|
+
const aReview = baseline.review;
|
|
67
|
+
const bReview = candidate.review;
|
|
68
|
+
const aActivity = a.activity || {};
|
|
69
|
+
const bActivity = b.activity || {};
|
|
70
|
+
const aTotals = aActivity.totals || {};
|
|
71
|
+
const bTotals = bActivity.totals || {};
|
|
72
|
+
|
|
73
|
+
const rows = [
|
|
74
|
+
{
|
|
75
|
+
label: "Total tokens",
|
|
76
|
+
baseline: fmtNumber(aTotals.totalTokens),
|
|
77
|
+
candidate: fmtNumber(bTotals.totalTokens),
|
|
78
|
+
delta: fmtDelta((bTotals.totalTokens || 0) - (aTotals.totalTokens || 0))
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
label: "Input tokens",
|
|
82
|
+
baseline: fmtNumber(aTotals.inputTokens),
|
|
83
|
+
candidate: fmtNumber(bTotals.inputTokens),
|
|
84
|
+
delta: fmtDelta((bTotals.inputTokens || 0) - (aTotals.inputTokens || 0))
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
label: "Output tokens",
|
|
88
|
+
baseline: fmtNumber(aTotals.outputTokens),
|
|
89
|
+
candidate: fmtNumber(bTotals.outputTokens),
|
|
90
|
+
delta: fmtDelta((bTotals.outputTokens || 0) - (aTotals.outputTokens || 0))
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: "Duration",
|
|
94
|
+
baseline: fmtMs(aActivity.durationMs),
|
|
95
|
+
candidate: fmtMs(bActivity.durationMs),
|
|
96
|
+
delta: fmtDelta((bActivity.durationMs || 0) - (aActivity.durationMs || 0), fmtMs)
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
label: "LLM calls",
|
|
100
|
+
baseline: fmtNumber(aActivity.llmCallCount),
|
|
101
|
+
candidate: fmtNumber(bActivity.llmCallCount),
|
|
102
|
+
delta: fmtDelta((bActivity.llmCallCount || 0) - (aActivity.llmCallCount || 0))
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
label: "Tool calls",
|
|
106
|
+
baseline: fmtNumber(aActivity.toolCallCount),
|
|
107
|
+
candidate: fmtNumber(bActivity.toolCallCount),
|
|
108
|
+
delta: fmtDelta((bActivity.toolCallCount || 0) - (aActivity.toolCallCount || 0))
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
label: "Tool errors",
|
|
112
|
+
baseline: fmtNumber(aActivity.toolErrorCount),
|
|
113
|
+
candidate: fmtNumber(bActivity.toolErrorCount),
|
|
114
|
+
delta: fmtDelta((bActivity.toolErrorCount || 0) - (aActivity.toolErrorCount || 0))
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
label: "Cost (USD)",
|
|
118
|
+
baseline: fmtUsd(aTotals.totalCostUsd),
|
|
119
|
+
candidate: fmtUsd(bTotals.totalCostUsd),
|
|
120
|
+
delta: fmtDelta((bTotals.totalCostUsd || 0) - (aTotals.totalCostUsd || 0), fmtUsd)
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const labelWidth = Math.max(...rows.map((row) => row.label.length));
|
|
125
|
+
const baselineWidth = Math.max("Baseline".length, ...rows.map((row) => row.baseline.length));
|
|
126
|
+
const candidateWidth = Math.max("Candidate".length, ...rows.map((row) => row.candidate.length));
|
|
127
|
+
|
|
128
|
+
function pad(value, width) {
|
|
129
|
+
return String(value).padEnd(width);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log("notrace compare\n");
|
|
133
|
+
console.log(`Baseline : ${relative(process.cwd(), baseline.path) || baseline.path}`);
|
|
134
|
+
console.log(`Candidate: ${relative(process.cwd(), candidate.path) || candidate.path}`);
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log(`Task : ${b.task?.id || a.task?.id || "(none)"}`);
|
|
137
|
+
console.log(`Capture : ${a.captureMode} -> ${b.captureMode}`);
|
|
138
|
+
console.log(`Models : ${list(a.conditions?.models)} -> ${list(b.conditions?.models)}`);
|
|
139
|
+
console.log(`Providers : ${list(a.conditions?.providers)} -> ${list(b.conditions?.providers)}`);
|
|
140
|
+
console.log("");
|
|
141
|
+
console.log(`Review : ${(aReview?.outcome || "-")}/${(aReview?.friction || "-")} -> ${(bReview?.outcome || "-")}/${(bReview?.friction || "-")}`);
|
|
142
|
+
if (aReview?.lesson || bReview?.lesson) {
|
|
143
|
+
console.log(`Lessons : ${(aReview?.lesson || "-")} -> ${(bReview?.lesson || "-")}`);
|
|
144
|
+
}
|
|
145
|
+
if (aReview?.nextChange || bReview?.nextChange) {
|
|
146
|
+
console.log(`Next : ${(aReview?.nextChange || "-")} -> ${(bReview?.nextChange || "-")}`);
|
|
147
|
+
}
|
|
148
|
+
console.log("");
|
|
149
|
+
console.log(`${pad("Metric", labelWidth)} | ${pad("Baseline", baselineWidth)} | ${pad("Candidate", candidateWidth)} | Delta`);
|
|
150
|
+
console.log(`${"-".repeat(labelWidth)}-+-${"-".repeat(baselineWidth)}-+-${"-".repeat(candidateWidth)}-+-${"-".repeat(24)}`);
|
|
151
|
+
for (const row of rows) {
|
|
152
|
+
console.log(`${pad(row.label, labelWidth)} | ${pad(row.baseline, baselineWidth)} | ${pad(row.candidate, candidateWidth)} | ${row.delta}`);
|
|
153
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve, relative, dirname, basename, extname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const VALID_OUTCOMES = new Set(["success", "partial", "failed", "abandoned", "inconclusive"]);
|
|
6
|
+
const VALID_FRICTION = new Set(["low", "medium", "high"]);
|
|
7
|
+
|
|
8
|
+
function appendWorkLogEntry(taskDir, message) {
|
|
9
|
+
const workMd = join(taskDir, "WORK.md");
|
|
10
|
+
if (!existsSync(workMd)) return;
|
|
11
|
+
|
|
12
|
+
const text = readFileSync(workMd, "utf8");
|
|
13
|
+
const entry = `- ${new Date().toISOString()}: ${message}`;
|
|
14
|
+
|
|
15
|
+
if (!/^(## )?\[LOG\]\s*$/m.test(text)) {
|
|
16
|
+
writeFileSync(workMd, `${text.trimEnd()}\n\n## [LOG]\n${entry}\n`, { encoding: "utf8" });
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lines = text.split("\n");
|
|
21
|
+
const logIndex = lines.findIndex((line) => /^(## )?\[LOG\]\s*$/.test(line));
|
|
22
|
+
if (logIndex === -1) return;
|
|
23
|
+
|
|
24
|
+
let nextSectionIndex = lines.length;
|
|
25
|
+
for (let i = logIndex + 1; i < lines.length; i++) {
|
|
26
|
+
if (/^(## )?\[[A-Z0-9_-]+\]\s*$/.test(lines[i])) {
|
|
27
|
+
nextSectionIndex = i;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const before = lines.slice(0, nextSectionIndex);
|
|
33
|
+
const after = lines.slice(nextSectionIndex);
|
|
34
|
+
while (before.length > logIndex + 1 && before[before.length - 1]?.trim() === "") {
|
|
35
|
+
before.pop();
|
|
36
|
+
}
|
|
37
|
+
before.push(entry);
|
|
38
|
+
|
|
39
|
+
writeFileSync(workMd, `${[...before, ...after].join("\n").replace(/\n*$/, "\n")}`, { encoding: "utf8" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function usage() {
|
|
43
|
+
console.error("Usage: notrace-review <notrace.json> [--outcome value] [--friction value] [--lesson text] [--next-change text]");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
if (!args.length) usage();
|
|
49
|
+
|
|
50
|
+
const runPath = resolve(args[0]);
|
|
51
|
+
const flags = args.slice(1);
|
|
52
|
+
|
|
53
|
+
function takeFlag(name) {
|
|
54
|
+
const index = flags.indexOf(name);
|
|
55
|
+
if (index === -1) return undefined;
|
|
56
|
+
const value = flags[index + 1];
|
|
57
|
+
if (!value || value.startsWith("--")) {
|
|
58
|
+
throw new Error(`Missing value for ${name}`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const outcome = takeFlag("--outcome");
|
|
64
|
+
const friction = takeFlag("--friction");
|
|
65
|
+
const lesson = takeFlag("--lesson");
|
|
66
|
+
const nextChange = takeFlag("--next-change");
|
|
67
|
+
|
|
68
|
+
if (!existsSync(runPath)) {
|
|
69
|
+
throw new Error(`Run record not found: ${runPath}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const run = JSON.parse(readFileSync(runPath, "utf8"));
|
|
73
|
+
if (run?.kind !== "notrace-run") {
|
|
74
|
+
throw new Error(`Not a notrace run record: ${runPath}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (outcome && !VALID_OUTCOMES.has(outcome)) {
|
|
78
|
+
throw new Error(`Invalid outcome: ${outcome}`);
|
|
79
|
+
}
|
|
80
|
+
if (friction && !VALID_FRICTION.has(friction)) {
|
|
81
|
+
throw new Error(`Invalid friction: ${friction}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const reviewPath = join(dirname(runPath), `${basename(runPath, extname(runPath))}.review.json`);
|
|
85
|
+
const existing = existsSync(reviewPath)
|
|
86
|
+
? JSON.parse(readFileSync(reviewPath, "utf8"))
|
|
87
|
+
: {
|
|
88
|
+
schemaVersion: 1,
|
|
89
|
+
kind: "notrace-review",
|
|
90
|
+
traceId: run.traceId,
|
|
91
|
+
runRecord: basename(runPath),
|
|
92
|
+
outcome: null,
|
|
93
|
+
friction: null,
|
|
94
|
+
lesson: "",
|
|
95
|
+
nextChange: ""
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const review = {
|
|
99
|
+
...existing,
|
|
100
|
+
traceId: run.traceId,
|
|
101
|
+
runRecord: basename(runPath),
|
|
102
|
+
outcome: outcome ?? existing.outcome ?? null,
|
|
103
|
+
friction: friction ?? existing.friction ?? null,
|
|
104
|
+
lesson: lesson ?? existing.lesson ?? "",
|
|
105
|
+
nextChange: nextChange ?? existing.nextChange ?? ""
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
writeFileSync(reviewPath, `${JSON.stringify(review, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
109
|
+
|
|
110
|
+
if (run.repository?.cwd && (run.task?.dir || run.task?.path)) {
|
|
111
|
+
const taskDir = run.task?.dir
|
|
112
|
+
? resolve(run.task.dir)
|
|
113
|
+
: resolve(run.repository.cwd, run.task.path);
|
|
114
|
+
appendWorkLogEntry(taskDir, `notrace review recorded: outcome=${review.outcome ?? "-"}, friction=${review.friction ?? "-"}, review=${relative(taskDir, reviewPath)}`);
|
|
115
|
+
} else {
|
|
116
|
+
appendWorkLogEntry(dirname(runPath), `notrace review recorded: outcome=${review.outcome ?? "-"}, friction=${review.friction ?? "-"}, review=${basename(reviewPath)}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(`notrace review ✓ ${reviewPath}`);
|
|
120
|
+
console.log(` outcome : ${review.outcome ?? "-"}`);
|
|
121
|
+
console.log(` friction : ${review.friction ?? "-"}`);
|
|
122
|
+
console.log(` lesson : ${review.lesson || "-"}`);
|
|
123
|
+
console.log(` nextChange: ${review.nextChange || "-"}`);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { WorkflowContext } from "./types.js";
|
|
2
|
+
export interface WorkflowAdapter {
|
|
3
|
+
name: string;
|
|
4
|
+
detect(cwd: string): boolean;
|
|
5
|
+
getContext(cwd: string): WorkflowContext | null;
|
|
6
|
+
attach(context: WorkflowContext, artifacts: {
|
|
7
|
+
html: string;
|
|
8
|
+
record: string;
|
|
9
|
+
}): void;
|
|
10
|
+
}
|
|
11
|
+
export declare class NorpivAdapter implements WorkflowAdapter {
|
|
12
|
+
name: string;
|
|
13
|
+
detect(cwd: string): boolean;
|
|
14
|
+
getContext(cwd: string): WorkflowContext | null;
|
|
15
|
+
attach(context: WorkflowContext, artifacts: {
|
|
16
|
+
html: string;
|
|
17
|
+
record: string;
|
|
18
|
+
}): void;
|
|
19
|
+
}
|
|
20
|
+
export declare class ResearchAdapter implements WorkflowAdapter {
|
|
21
|
+
name: string;
|
|
22
|
+
detect(cwd: string): boolean;
|
|
23
|
+
getContext(cwd: string): WorkflowContext | null;
|
|
24
|
+
attach(): void;
|
|
25
|
+
}
|
|
26
|
+
export declare class GenericAdapter implements WorkflowAdapter {
|
|
27
|
+
name: string;
|
|
28
|
+
detect(): boolean;
|
|
29
|
+
getContext(): null;
|
|
30
|
+
attach(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function getActiveAdapter(cwd: string): WorkflowAdapter;
|