@narrativetrace/cli 0.1.3 → 0.2.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/README.md +77 -6
- package/dist/cli-bin.cjs +234 -513
- package/dist/cli-bin.cjs.map +1 -1
- package/dist/cli-bin.d.cts +1 -0
- package/dist/cli-bin.d.ts +1 -0
- package/dist/cli-bin.js +249 -13
- package/dist/cli-bin.js.map +1 -1
- package/dist/index.cjs +9 -520
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -91
- package/dist/index.d.ts +1 -91
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/agents/add-narrative-tracing/SKILL.md +124 -0
- package/skills/agents/narrativetrace-doctor/SKILL.md +49 -0
- package/skills/catalogue.json +17 -0
- package/skills/claude/add-narrative-tracing/SKILL.md +126 -0
- package/skills/claude/narrativetrace-doctor/SKILL.md +51 -0
- package/dist/chunk-WUK4GWFW.js +0 -523
- package/dist/chunk-WUK4GWFW.js.map +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-narrative-tracing
|
|
3
|
+
description: "Installs NarrativeTrace into a TypeScript project and gets it to a first trace. Use when NarrativeTrace is not yet installed, a project needs its very first traced call, or traces need to reach a real logger instead of a bare console.log. Installs @narrativetrace/core-node and @narrativetrace/proxy with the project's real package manager, wraps a class with traceObject, renders and runs the first trace, then wires a pino/winston/OpenTelemetry-style consumer so traces reach your logger. Runs narrativetrace doctor to confirm the install is correctly wired — narrativetrace-doctor owns diagnosis from there — then previews installing the NarrativeTrace agent skills for next time (`narrativetrace init --dry-run`); applying that preview is left to the reader. Say 'add narrative tracing to my service', 'install narrativetrace', 'get a trace in 60 seconds', 'wrap this class so I can see a trace', or 'send my traces to my logger' to invoke it."
|
|
4
|
+
when_to_use: "A project does not have NarrativeTrace yet, or has the packages installed but has never produced a trace, or traces print to the console but nothing forwards them to a real logger."
|
|
5
|
+
allowed-tools: pnpm, npx, node
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# add-narrative-tracing
|
|
9
|
+
|
|
10
|
+
## 1. Install with the real toolchain
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm install --frozen-lockfile
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**verify:** `npx @narrativetrace/cli doctor --json | node -e "const r=JSON.parse(require('fs').readFileSync(0,'utf8')); const bad=r.findings.filter(f=>f.id.startsWith('toolchain.')&&f.status!=='pass'); if(bad.length>0){console.error(JSON.stringify(bad)); process.exit(1);}"`
|
|
17
|
+
|
|
18
|
+
**failure:** vitest peer mismatch breaks the library's own build from a clean install — an installed vitest version outside @narrativetrace/vitest's declared peer range. Fix: run the narrativetrace-doctor skill's toolchain.vitest-peer check, then install a version satisfying the printed range
|
|
19
|
+
|
|
20
|
+
## 2. First trace: wrap, call, render, run
|
|
21
|
+
|
|
22
|
+
<!-- snippet: examples/sixty-seconds/index.js -->
|
|
23
|
+
```js
|
|
24
|
+
// index.js
|
|
25
|
+
import { NarrativeTraceConfig, SyncNarrativeContext, renderMarkdownBody } from "@narrativetrace/core-node";
|
|
26
|
+
import { traceObject } from "@narrativetrace/proxy";
|
|
27
|
+
|
|
28
|
+
class OrderService {
|
|
29
|
+
placeOrder(customerId, productId, quantity) {
|
|
30
|
+
return `ORD-${customerId}-${productId}-${quantity}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const context = new SyncNarrativeContext(new NarrativeTraceConfig());
|
|
35
|
+
const service = traceObject(new OrderService(), context, {
|
|
36
|
+
placeOrder: ["customerId", "productId", "quantity"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
service.placeOrder("C1", "P1", 2);
|
|
40
|
+
console.log(renderMarkdownBody(context.captureTrace()));
|
|
41
|
+
```
|
|
42
|
+
<!-- /snippet -->
|
|
43
|
+
|
|
44
|
+
**verify:** `node index.js`
|
|
45
|
+
|
|
46
|
+
**failure:** parameters render as arg0, arg1, ... — parameter names of a class you don't own (or a build that strips them) are lost at compile time. Fix: pass them explicitly: traceObject(target, context, { methodName: ["paramA", "paramB"] })
|
|
47
|
+
|
|
48
|
+
## 3. Send it to your logger
|
|
49
|
+
|
|
50
|
+
<!-- snippet: examples/sixty-seconds/index-with-logger.js -->
|
|
51
|
+
```js
|
|
52
|
+
// index-with-logger.js
|
|
53
|
+
import {
|
|
54
|
+
NarrativeTraceConfig,
|
|
55
|
+
SyncNarrativeContext,
|
|
56
|
+
DualPathPipeline, // fans events out to two consumers: the pino bridge and the in-memory buffer
|
|
57
|
+
BufferedEventConsumer, // keeps captureTrace() working alongside the logger
|
|
58
|
+
parseTraceparent, // turns a traceparent header into the trace id fixed below
|
|
59
|
+
renderMarkdownBody,
|
|
60
|
+
} from "@narrativetrace/core-node";
|
|
61
|
+
import { traceObject } from "@narrativetrace/proxy";
|
|
62
|
+
import { createPinoEventConsumer } from "@narrativetrace/pino"; // bridges trace events into Pino
|
|
63
|
+
import pino from "pino";
|
|
64
|
+
|
|
65
|
+
class OrderService {
|
|
66
|
+
placeOrder(customerId, productId, quantity) {
|
|
67
|
+
return `ORD-${customerId}-${productId}-${quantity}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// A documented constant for THIS example only — never the library default, which always
|
|
72
|
+
// generates a random trace id — so nt.traceName/trace_id below stay the same phrase every time
|
|
73
|
+
// this page's output is regenerated.
|
|
74
|
+
const FIXED_TRACEPARENT = "00-a1b2c3d4a1b2c3d4a1b2c3d4a1b2c3d4-a1b2c3d4a1b2c3d4-01";
|
|
75
|
+
const fixedTraceId = parseTraceparent(FIXED_TRACEPARENT);
|
|
76
|
+
|
|
77
|
+
// Any pino instance works. `sync: true` is this script's own need, not the library's: pino's
|
|
78
|
+
// default destination writes on a later tick, so a script that logs and then prints can have the
|
|
79
|
+
// two land in either order — and a short-lived process can exit before the last line is flushed.
|
|
80
|
+
const logger = pino(pino.destination({ sync: true }));
|
|
81
|
+
const pinoConsumer = createPinoEventConsumer(logger, { levels: { enter: "info", return: "info" } });
|
|
82
|
+
const pipeline = new DualPathPipeline(pinoConsumer, new BufferedEventConsumer());
|
|
83
|
+
const context = new SyncNarrativeContext(
|
|
84
|
+
new NarrativeTraceConfig(),
|
|
85
|
+
undefined, // parentResolver — a plain script has no ambient parent span to resolve
|
|
86
|
+
pipeline, // routes events to both the logger above and the buffer captureTrace() reads
|
|
87
|
+
null, // rootParentOverride — no inbound parent span for this root call
|
|
88
|
+
undefined, // serviceIdentity — not needed for this example
|
|
89
|
+
fixedTraceId, // seeds the trace id an inbound traceparent header would carry on a real request
|
|
90
|
+
);
|
|
91
|
+
const service = traceObject(new OrderService(), context, {
|
|
92
|
+
placeOrder: ["customerId", "productId", "quantity"],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
service.placeOrder("C1", "P1", 2);
|
|
96
|
+
console.log(renderMarkdownBody(context.captureTrace()));
|
|
97
|
+
```
|
|
98
|
+
<!-- /snippet -->
|
|
99
|
+
|
|
100
|
+
**verify:** `node index-with-logger.js`
|
|
101
|
+
|
|
102
|
+
## 4. Run the doctor and resolve its findings
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
npx @narrativetrace/cli doctor || true
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**verify:** `npx @narrativetrace/cli doctor --json | node -e "const r=JSON.parse(require('fs').readFileSync(0,'utf8')); if(!Array.isArray(r.findings)||r.findings.length!==12) process.exit(1);"`
|
|
109
|
+
|
|
110
|
+
## 5. Install the skills for next time
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npx @narrativetrace/cli init --dry-run
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**verify:** `npx @narrativetrace/cli init --dry-run --json | node -e "const r=JSON.parse(require('fs').readFileSync(0,'utf8')); if(!Array.isArray(r.actions)||r.actions.length===0||r.exitCode!==0) process.exit(1);"`
|
|
117
|
+
|
|
118
|
+
## Always
|
|
119
|
+
|
|
120
|
+
- Reinstall clean (a frozen-lockfile install) rather than trusting whatever is already in node_modules. (a mismatched peer or stale lockfile is the single most common install failure, and it only surfaces on a clean install)
|
|
121
|
+
|
|
122
|
+
## Never
|
|
123
|
+
|
|
124
|
+
- Never assume a step worked without running its verify. (self-reported success overstates reality — a build claimed green that does not reproduce from clean is not evidence)
|
|
125
|
+
- Never skip the final narrativetrace doctor call. (it is the seam that catches anything these four steps did not — narrativetrace-doctor owns diagnosis from here)
|
|
126
|
+
- Never run `narrativetrace init` without `--dry-run` from this skill. (the prompt's own step 3 human gate applies the plan only after a person has seen the diff — this skill only shows it)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: narrativetrace-doctor
|
|
3
|
+
description: "Diagnoses a NarrativeTrace TypeScript install and configuration. Use when nothing is being traced, traces aren't showing up, the vitest config crashes on load, parameter names render as arg0/arg1, or you are not sure NarrativeTrace is wired up correctly. Checks Node/vitest-peer/sibling-package versions, the /reporters subpath, traceObject option shapes, NARRATIVETRACE_OUTPUT, whether any consumer is attached to a traced proxy, whether redaction is proven in a test, stale approval-trace diffs, and whether the NarrativeTrace agent skills are installed and current. Read-only — makes no changes. Say 'check my narrativetrace setup', 'is narrativetrace broken', or 'why isn't anything being traced' to invoke it."
|
|
4
|
+
when_to_use: "A project already has NarrativeTrace installed and something about it is not working, or an agent wants a pre-flight check before wiring it into new code."
|
|
5
|
+
allowed-tools: pnpm, npx, node
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# narrativetrace-doctor
|
|
9
|
+
|
|
10
|
+
## 1. Run the doctor and read its report
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @narrativetrace/cli doctor || true
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**verify:** `npx @narrativetrace/cli doctor --json | node -e "const r=JSON.parse(require('fs').readFileSync(0,'utf8')); if(!Array.isArray(r.findings)||r.findings.length!==12) process.exit(1);"`
|
|
17
|
+
|
|
18
|
+
**failure:** the CLI's JSON output does not parse, or is missing findings — the CLI crashed instead of reporting a finding. Fix: re-run `npx @narrativetrace/cli doctor --json` directly and read the raw output — a crash here is a doctor bug, never a project finding
|
|
19
|
+
|
|
20
|
+
## 2. Prove redaction in a test
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node -e "console.log('Render a call with a deny-listed parameter name (e.g. password or token) in a test and assert the output contains [REDACTED], and that a neighboring non-sensitive value is still present.')"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**verify:** `npx @narrativetrace/cli doctor --json | node -e "const r=JSON.parse(require('fs').readFileSync(0,'utf8')); const f=r.findings.find(x=>x.id==='trap.redaction-proof'); if(!f||(f.status!=='pass'&&f.status!=='fail')) process.exit(1);"`
|
|
27
|
+
|
|
28
|
+
**failure:** a redaction primitive is imported but never asserted on — trusting redaction by inspection instead of proving it in a test. Fix: render a call with a deny-listed parameter name and assert the output contains "[REDACTED]"
|
|
29
|
+
|
|
30
|
+
## 3. Read the rendered trace before asserting
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
node -e "const fs=require('fs'),path=require('path');function walk(d){return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>{const p=path.join(d,e.name);return e.isDirectory()?walk(p):[p];});}const files=walk('narrativetrace-output').filter(f=>f.endsWith('.md'));if(!files.length){console.error('no rendered .md file found under narrativetrace-output');process.exit(1);}const newest=files.map(f=>[f,fs.statSync(f).mtimeMs]).sort((a,b)=>b[1]-a[1])[0][0];console.log(newest);console.log(fs.readFileSync(newest,'utf8'));"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 4. Approval flow: diff the structural trace, not just values
|
|
37
|
+
|
|
38
|
+
**Flagged:** unstudied — eval cell pending
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node -e "const fs=require('fs'),path=require('path');function walk(d){if(!fs.existsSync(d))return[];return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>{const p=path.join(d,e.name);return e.isDirectory()?walk(p):[p];});}const dir='narrativetrace-output';const received=walk(dir).filter(f=>f.endsWith('.received.nt'));if(received.length){const newest=received.map(f=>[f,fs.statSync(f).mtimeMs]).sort((a,b)=>b[1]-a[1])[0][0];const approved=newest.slice(0,-'.received.nt'.length)+'.approved.nt';console.log('received: '+newest);if(fs.existsSync(approved)){console.log('approved: '+approved);console.log(fs.readFileSync(approved,'utf8'));console.log('--- vs received ---');console.log(fs.readFileSync(newest,'utf8'));}else{console.log('no .approved.nt yet - first approval, review then promote');console.log(fs.readFileSync(newest,'utf8'));}}else{const nt=walk(dir).filter(f=>f.endsWith('.nt'));if(!nt.length){console.error('no structural .nt file found under narrativetrace-output');process.exit(1);}const newest=nt.map(f=>[f,fs.statSync(f).mtimeMs]).sort((a,b)=>b[1]-a[1])[0][0];console.log('no .received.nt pending; newest structural trace: '+newest);console.log(fs.readFileSync(newest,'utf8'));}"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Always
|
|
45
|
+
|
|
46
|
+
- Run the doctor CLI and read its report before making any change. (the tested tooling already computed the finding — re-deriving it by hand risks disagreeing with what ships)
|
|
47
|
+
|
|
48
|
+
## Never
|
|
49
|
+
|
|
50
|
+
- Never have this skill edit, generate, or delete a file. (narrativetrace-doctor is scoped read-only by design — generation of the redaction-proof test itself is a separate, later skill)
|
|
51
|
+
- Never claim a finding passed without having run the doctor CLI in this session. (self-reported success overstates reality — a build claimed green that does not reproduce from clean is not evidence; verify is never 'ask the agent')
|