@nurix/etna 0.15.6 → 0.15.8

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.
Files changed (88) hide show
  1. package/bin/etna.js +64 -13
  2. package/data/harness-index.json +7 -5
  3. package/data/harnesses/code/CLAUDE.md +3 -7
  4. package/data/harnesses/code/agents/harness-manager.md +4 -4
  5. package/data/harnesses/code/agents/maintenance.md +1 -1
  6. package/data/harnesses/code/harness.json +4 -3
  7. package/data/harnesses/code/hooks/hooks.json +3 -17
  8. package/data/harnesses/code/hooks/prompt-recall.js +157 -0
  9. package/data/harnesses/code/hooks/session-reflect.js +283 -0
  10. package/data/harnesses/code/skills/distiller/SKILL.md +62 -60
  11. package/data/harnesses/code/skills/distiller/references/docs-mode.md +21 -21
  12. package/data/harnesses/code/skills/distiller/references/method.md +41 -41
  13. package/data/harnesses/code/skills/documentation/references/templates.md +20 -27
  14. package/data/harnesses/code-fable/CLAUDE.md +138 -0
  15. package/data/harnesses/code-fable/agents/brainstorm.md +35 -0
  16. package/data/harnesses/code-fable/agents/change-review.md +249 -0
  17. package/data/harnesses/code-fable/agents/harness-manager.md +193 -0
  18. package/data/harnesses/code-fable/agents/maintenance.md +123 -0
  19. package/data/harnesses/code-fable/agents/performance-review.md +218 -0
  20. package/data/harnesses/code-fable/agents/spec-mode.md +56 -0
  21. package/data/harnesses/code-fable/harness.json +7 -0
  22. package/data/harnesses/code-fable/hooks/hooks.json +27 -0
  23. package/data/harnesses/code-fable/hooks/lib/redact.js +37 -0
  24. package/data/harnesses/code-fable/hooks/prompt-recall.js +157 -0
  25. package/data/harnesses/code-fable/hooks/session-reflect.js +283 -0
  26. package/data/harnesses/code-fable/rules/general.md +96 -0
  27. package/data/harnesses/code-fable/rules/nomenclature.md +34 -0
  28. package/data/harnesses/code-fable/skills/boundary/SKILL.md +262 -0
  29. package/data/harnesses/code-fable/skills/boundary/references/boundary-taxonomy.md +734 -0
  30. package/data/harnesses/code-fable/skills/brainstorm/README.md +136 -0
  31. package/data/harnesses/code-fable/skills/brainstorm/SKILL.md +135 -0
  32. package/data/harnesses/code-fable/skills/brainstorm/harvest.md +26 -0
  33. package/data/harnesses/code-fable/skills/brainstorm/templates/backlog.md +39 -0
  34. package/data/harnesses/code-fable/skills/brainstorm/templates/build.md +37 -0
  35. package/data/harnesses/code-fable/skills/brainstorm/templates/decisions.md +29 -0
  36. package/data/harnesses/code-fable/skills/brainstorm/templates/doctrine.md +31 -0
  37. package/data/harnesses/code-fable/skills/brainstorm/templates/domain-model.md +25 -0
  38. package/data/harnesses/code-fable/skills/brainstorm/templates/glossary.md +19 -0
  39. package/data/harnesses/code-fable/skills/brainstorm/templates/milestones.md +43 -0
  40. package/data/harnesses/code-fable/skills/brainstorm/templates/non-goals.md +21 -0
  41. package/data/harnesses/code-fable/skills/brainstorm/templates/plan-doc.md +61 -0
  42. package/data/harnesses/code-fable/skills/brainstorm/templates/questions.md +35 -0
  43. package/data/harnesses/code-fable/skills/brainstorm/templates/references.md +28 -0
  44. package/data/harnesses/code-fable/skills/brainstorm/templates/rejected.md +29 -0
  45. package/data/harnesses/code-fable/skills/brainstorm/templates/state.md +8 -0
  46. package/data/harnesses/code-fable/skills/brainstorm/templates/tech.md +23 -0
  47. package/data/harnesses/code-fable/skills/brainstorm/templates/thesis.md +21 -0
  48. package/data/harnesses/code-fable/skills/distiller/SKILL.md +219 -0
  49. package/data/harnesses/code-fable/skills/distiller/references/docs-mode.md +526 -0
  50. package/data/harnesses/code-fable/skills/distiller/references/method.md +166 -0
  51. package/data/harnesses/code-fable/skills/documentation/SKILL.md +127 -0
  52. package/data/harnesses/code-fable/skills/documentation/references/examples.md +193 -0
  53. package/data/harnesses/code-fable/skills/documentation/references/style-guide.md +288 -0
  54. package/data/harnesses/code-fable/skills/documentation/references/templates.md +942 -0
  55. package/data/harnesses/code-fable/skills/workflow-mapper/SKILL.md +430 -0
  56. package/data/harnesses/code-fable/skills/workflow-mapper/references/patterns.md +214 -0
  57. package/data/harnesses/discover/CLAUDE.md +180 -0
  58. package/data/harnesses/{re → discover}/agents/api.md +3 -3
  59. package/data/harnesses/{re → discover}/agents/codebase.md +3 -3
  60. package/data/harnesses/{re → discover}/agents/community.md +3 -3
  61. package/data/harnesses/{re → discover}/agents/deployed-client-bundle.md +21 -15
  62. package/data/harnesses/{re → discover}/agents/distribution-artifacts.md +13 -12
  63. package/data/harnesses/{re → discover}/agents/docs.md +3 -3
  64. package/data/harnesses/{re → discover}/agents/infra-backend-fingerprint.md +3 -3
  65. package/data/harnesses/{re → discover}/agents/packages.md +3 -3
  66. package/data/harnesses/{re → discover}/agents/session.md +29 -11
  67. package/data/harnesses/{re → discover}/agents/website.md +3 -3
  68. package/data/harnesses/{re → discover}/agents/wire-capture.md +14 -10
  69. package/data/harnesses/discover/harness.json +7 -0
  70. package/data/harnesses/{re/rules/re-discovery.md → discover/rules/discovery.md} +86 -17
  71. package/data/harnesses/{re/rules/re-evaluation.md → discover/rules/evaluation.md} +40 -4
  72. package/data/harnesses/discover/rules/ingestion.md +551 -0
  73. package/data/harnesses/discover/rules/self-correction.md +177 -0
  74. package/data/harnesses/discover/rules/tradecraft.md +532 -0
  75. package/data/harnesses/hr/CLAUDE.md +91 -0
  76. package/data/harnesses/hr/harness.json +7 -0
  77. package/data/harnesses/hr/skills/interview-eval/SKILL.md +118 -0
  78. package/data/harnesses/hr/skills/interview-eval/TEMPLATE.md +124 -0
  79. package/data/harnesses/hr/skills/interview-eval/extract_session.py +418 -0
  80. package/data/harnesses/hr/skills/interview-eval/rubric.md +219 -0
  81. package/data/harnesses/kb-agi/harness.json +1 -0
  82. package/package.json +1 -1
  83. package/data/harnesses/code/hooks/post-commit.js +0 -136
  84. package/data/harnesses/code/hooks/post-tool-failure.js +0 -136
  85. package/data/harnesses/code/hooks/session-stop.js +0 -207
  86. package/data/harnesses/re/CLAUDE.md +0 -146
  87. package/data/harnesses/re/harness.json +0 -6
  88. package/data/harnesses/re/rules/re-ingestion.md +0 -327
@@ -1,207 +0,0 @@
1
- // harness/code/hooks/session-stop.js — SessionEnd hook: append one cheap session-summary ledger entry.
2
-
3
- "use strict";
4
-
5
- const fs = require("fs");
6
- const path = require("path");
7
- const os = require("os");
8
- const { execFileSync } = require("child_process");
9
- const { redact } = require("./lib/redact.js");
10
-
11
- // Read all of stdin synchronously; returns "" if nothing is piped.
12
- function readStdin() {
13
- try {
14
- return fs.readFileSync(0, "utf8");
15
- } catch (e) {
16
- return "";
17
- }
18
- }
19
-
20
- // Resolve the project root: CLAUDE_PROJECT_DIR env, else payload cwd, else process cwd.
21
- function resolveProjectDir(payload) {
22
- return process.env.CLAUDE_PROJECT_DIR || (payload && payload.cwd) || process.cwd();
23
- }
24
-
25
- // Run a git command in projectDir; returns trimmed stdout or fallback on any error.
26
- function git(projectDir, args, fallback) {
27
- try {
28
- return execFileSync("git", ["-C", projectDir, ...args], {
29
- encoding: "utf8",
30
- stdio: ["ignore", "pipe", "ignore"],
31
- }).trim() || fallback;
32
- } catch (e) {
33
- return fallback;
34
- }
35
- }
36
-
37
- // Slug a raw actor name into kebab-case ASCII.
38
- function slugActor(name) {
39
- return String(name || "")
40
- .toLowerCase()
41
- .replace(/[^a-z0-9]+/g, "-")
42
- .replace(/^-+|-+$/g, "");
43
- }
44
-
45
- // Determine the actor: git user.name (slugged) → $USER → "agent".
46
- function resolveActor(projectDir) {
47
- const fromGit = slugActor(git(projectDir, ["config", "user.name"], ""));
48
- if (fromGit) return fromGit;
49
- const fromEnv = slugActor(process.env.USER || "");
50
- if (fromEnv) return fromEnv;
51
- return "agent";
52
- }
53
-
54
- // Truncate a single line to n chars with an ellipsis marker.
55
- function truncate(text, n) {
56
- const clean = String(text || "").replace(/\s+/g, " ").trim();
57
- return clean.length > n ? clean.slice(0, n) + "…" : clean;
58
- }
59
-
60
- // Parse the JSONL transcript into a cheap mechanical summary.
61
- function summarizeTranscript(transcriptPath) {
62
- const summary = { firstPrompt: "", assistantTurns: 0, files: new Set(), lastAssistant: "", sawCommit: false };
63
- try {
64
- if (!transcriptPath || !fs.existsSync(transcriptPath)) return summary;
65
- const lines = fs.readFileSync(transcriptPath, "utf8").split("\n");
66
- for (const line of lines) {
67
- if (!line.trim()) continue;
68
- let row;
69
- try {
70
- row = JSON.parse(line);
71
- } catch (e) {
72
- continue;
73
- }
74
- const msg = row && row.message ? row.message : row;
75
- const role = (msg && msg.role) || row.type;
76
- if (role === "user" && !summary.firstPrompt) {
77
- summary.firstPrompt = extractText(msg);
78
- }
79
- if (role === "assistant") {
80
- summary.assistantTurns += 1;
81
- const text = extractText(msg);
82
- if (text) summary.lastAssistant = text;
83
- collectToolUse(msg, summary);
84
- }
85
- }
86
- } catch (e) {
87
- // Best-effort summary; ignore parse issues.
88
- }
89
- return summary;
90
- }
91
-
92
- // Extract plain text from a message whose content is a string or content-block array.
93
- function extractText(msg) {
94
- if (!msg || !msg.content) return "";
95
- if (typeof msg.content === "string") return msg.content;
96
- if (Array.isArray(msg.content)) {
97
- return msg.content
98
- .filter((b) => b && b.type === "text" && typeof b.text === "string")
99
- .map((b) => b.text)
100
- .join(" ");
101
- }
102
- return "";
103
- }
104
-
105
- // Scan an assistant message's tool_use blocks for edited files and git commits.
106
- function collectToolUse(msg, summary) {
107
- if (!msg || !Array.isArray(msg.content)) return;
108
- for (const block of msg.content) {
109
- if (!block || block.type !== "tool_use") continue;
110
- const name = block.name || "";
111
- const input = block.input || {};
112
- if (/^(Edit|Write|MultiEdit|NotebookEdit)$/.test(name) && input.file_path) {
113
- summary.files.add(input.file_path);
114
- }
115
- if (name === "Bash" && typeof input.command === "string" && /\bgit\s+commit\b/.test(input.command)) {
116
- summary.sawCommit = true;
117
- }
118
- }
119
- }
120
-
121
- // Compute the ledger day-file path for a given actor and date.
122
- function ledgerPath(projectDir, date, actor) {
123
- const dir = path.join(projectDir, "docs", "memory", "ledger");
124
- return { dir, file: path.join(dir, `${date}--${actor}.md`) };
125
- }
126
-
127
- // Build the day-file header block.
128
- function header(date, actor) {
129
- return `# Ledger — ${date} — ${actor}\nAppend-only event ledger. Raw input to distillation — see docs/memory/.\n`;
130
- }
131
-
132
- // Skip if the day-file's last entry was written by this session within the current minute.
133
- function isDuplicate(file, sessionId, hhmm) {
134
- try {
135
- if (!sessionId || !fs.existsSync(file)) return false;
136
- const text = fs.readFileSync(file, "utf8");
137
- const idx = text.lastIndexOf("\n## ");
138
- const tail = idx >= 0 ? text.slice(idx) : text;
139
- return tail.includes(`session ${sessionId}`) && tail.includes(`## ${hhmm} `);
140
- } catch (e) {
141
- return false;
142
- }
143
- }
144
-
145
- // Main: assemble and append one session-summary entry, never blocking.
146
- function main() {
147
- const raw = readStdin();
148
- let payload = {};
149
- try {
150
- payload = raw ? JSON.parse(raw) : {};
151
- } catch (e) {
152
- payload = {};
153
- }
154
-
155
- const projectDir = resolveProjectDir(payload);
156
- const actor = resolveActor(projectDir);
157
- const repo = path.basename(projectDir);
158
- const branch = git(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"], "-");
159
-
160
- const now = new Date();
161
- const pad = (n) => String(n).padStart(2, "0");
162
- const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
163
- const hhmm = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
164
-
165
- const summary = summarizeTranscript(payload.transcript_path);
166
- const sessionId = payload.session_id || "";
167
-
168
- // Nothing meaningful to record (garbage/empty payload, unreadable transcript) → silent no-op.
169
- if (summary.assistantTurns === 0 && !summary.firstPrompt) return;
170
-
171
- const { dir, file } = ledgerPath(projectDir, date, actor);
172
- if (isDuplicate(file, sessionId, hhmm)) return;
173
-
174
- const type = summary.sawCommit ? "decision" : summary.assistantTurns > 0 ? "action" : "observation";
175
- const fileCount = summary.files.size;
176
- const whatBits = [];
177
- if (summary.firstPrompt) whatBits.push(`task "${truncate(redact(summary.firstPrompt), 100)}"`);
178
- whatBits.push(`${summary.assistantTurns} turns`);
179
- if (fileCount) whatBits.push(`${fileCount} file${fileCount === 1 ? "" : "s"} edited`);
180
- const what = whatBits.join(", ") || "session ended";
181
-
182
- const lines = [`## ${hhmm} · ${actor} · ${repo}@${branch}`, `- type: ${type}`, `- what: ${redact(what)}`];
183
- if (summary.lastAssistant) {
184
- lines.push(`- why: ${truncate(redact(summary.lastAssistant), 100)}`);
185
- }
186
- const refs = [];
187
- if (sessionId) refs.push(`session ${sessionId}`);
188
- if (fileCount) refs.push(`${fileCount} files`);
189
- if (refs.length) lines.push(`- refs: ${redact(refs.join(" · "))}`);
190
-
191
- const exists = fs.existsSync(file);
192
- fs.mkdirSync(dir, { recursive: true });
193
- const block = (exists ? "\n" : header(date, actor) + "\n") + lines.join("\n") + "\n";
194
- fs.appendFileSync(file, block, "utf8");
195
- }
196
-
197
- try {
198
- main();
199
- } catch (e) {
200
- try {
201
- process.stderr.write(`session-stop hook: ${e && e.message ? e.message : e}\n`);
202
- } catch (e2) {
203
- // Nothing more we can do.
204
- }
205
- } finally {
206
- process.exit(0);
207
- }
@@ -1,146 +0,0 @@
1
- # Product Reverse-Engineering — Orchestrator
2
-
3
- This is the **policy** half of the `re` harness. The plugin ships the capabilities (11
4
- dimension-dumper agents + 3 `re-*` rules, one per pipeline phase); this file drives them. Together they reverse-engineer a
5
- product across every surface it exposes and dump a structured, provenance-tracked corpus into
6
- `research/<target>/`.
7
-
8
- A run is four modes, in order: **Discovery → Ingestion → Evaluation → Self-correction.** The main
9
- session drives all four — Ingestion and Evaluation fan out parallel sub-agents, and a sub-agent
10
- cannot spawn further sub-agents.
11
-
12
- ---
13
-
14
- ## Access is a capability vector (the front gate)
15
-
16
- A target's access is four orthogonal axes, each `{value, confidence, evidence}` — and the vector
17
- decides which dimensions are reachable and which extraction method each uses:
18
-
19
- | Axis | Values | Unlocks | Case |
20
- |------|--------|---------|------|
21
- | **source** | full · partial · none | codebase, packages | open-source repo |
22
- | **runtime** | reachable · gated · none | deployed-client-bundle, api, infra-backend-fingerprint, wire-capture | deployed, no source |
23
- | **auth** | have-login · none | session | logged-in SaaS |
24
- | **presence** | rich · thin · none | docs, website, community | — |
25
-
26
- Targets light several axes at once. The full model + grade→method mapping is in
27
- `.claude/rules/re-discovery.md`.
28
-
29
- ---
30
-
31
- ## The eleven dimensions → agents
32
-
33
- | Dimension / agent | | Dimension / agent |
34
- |---|--|---|
35
- | `codebase` | | `session` ⚠ |
36
- | `docs` | | `deployed-client-bundle` |
37
- | `packages` | | `infra-backend-fingerprint` |
38
- | `api` (+MCP) | | `wire-capture` ⚠ |
39
- | `website` | | `distribution-artifacts` |
40
- | `community` | | |
41
-
42
- Agent name = dimension name. ⚠ = gated (needs user authorization before dispatch). The catalog —
43
- detection signals + strategy + per-dimension artifacts — is in `.claude/rules/re-discovery.md`.
44
-
45
- ---
46
-
47
- ## Mode 1 — Discovery
48
-
49
- Cheap, read-only. Scope the target, grade its access, detect which dimensions it exposes.
50
-
51
- 1. **Scope-feasibility gate** — run the `.claude/rules/re-discovery.md` Part A (scope gate) algorithm: resolve
52
- identity → ambiguity → ethics → suite-size → primitive → reachability. If the verdict isn't
53
- `accept`, **halt** (present the narrowing/redirect prompt, or refuse). Write
54
- `research/<target>/00-scope-verdict.md`.
55
- 2. **Grade the access vector** (the four axes, with evidence) per `re-discovery` (Part B).
56
- 3. **Probe each of the 11 dimensions** (cheap signals only); mark ✅ available / ⚠️ partial /
57
- ❌ absent with evidence. An absent dimension is a finding.
58
- 4. Write `research/<target>/00-recon-plan.md` — frontmatter carries `scope:` + the `access_grade`
59
- vector; the body is the dimension-availability table + the per-dimension dump plan (warm entry
60
- points) + the gating/ethics flags.
61
-
62
- ---
63
-
64
- ## Mode 2 — Ingestion
65
-
66
- Dispatch one dumper per available dimension; dump exhaustively before context is lost. Every dumper
67
- writes `research/<target>/dimensions/<dim>/_summary.md` (with the **provenance frontmatter** —
68
- method · confidence · completeness · gaps) + `raw/`, obeying `.claude/rules/re-ingestion.md`.
69
-
70
- - **Run independents in parallel** — dispatch `docs`, `packages`, `api`, `website`, `community`,
71
- `deployed-client-bundle`, `infra-backend-fingerprint`, `distribution-artifacts` together
72
- (`subagent_type='<dimension>'`, one message, multiple Agent calls). Seed each dumper with **warm
73
- entry points (URLs / repos / hosts), not claimed facts** — any fact carried from Discovery (a price,
74
- a tier name, a version) is tagged `verify:may-be-stale` so the dumper re-derives it instead of
75
- anchoring on a possibly-stale value. Only one dimension may drive the browser at a time (see
76
- `re-ingestion` §7 rule 9): the parallel dumpers use WebFetch/curl, leaving Chrome to `session`.
77
- - **Codebase fan-out** — dispatch `codebase`; it clones + maps + returns a per-package work-list;
78
- then the main session fans out one reader per substantial package.
79
- - **Gated dimensions** — `session` and `wire-capture` (rung ≥2) need explicit user authorization
80
- first (see `re-ingestion` §7). `session` follows `.claude/rules/re-ingestion.md` §9. When `session`
81
- is authorized, **dispatch the non-browser dumpers in the background (`run_in_background`) and drive
82
- the gated browser session in the main session concurrently** — they don't contend (Chrome belongs to
83
- the main session; the dumpers use WebFetch/curl), so the whole of Ingestion runs in parallel.
84
- - **Shared seam** — bundle/session/wire each contribute to the canonical `raw/api-path-catalog.md`
85
- with a `source:` column; don't triplicate.
86
-
87
- Validate each return (`_summary.md` + provenance present). A `blocked`/`partial` return is recorded,
88
- not blindly re-run.
89
-
90
- ---
91
-
92
- ## Mode 3 — Evaluation
93
-
94
- Reconcile the dumps into four cross-dimension rollups under `research/<target>/evaluation/`, plus
95
- the run `README.md`, **weighting every claim by how it was obtained**. Follow
96
- `.claude/rules/re-evaluation.md`: read each dimension's provenance frontmatter first; anchor +
97
- provenance-tag every claim `(api: raw/endpoint-catalog.md · openapi-verbatim · high)`; weight
98
- (max-of-sources, promote one band only on ≥2 *independent* dimensions, never average); present
99
- high→fact, low/single-source→tentative, conflict→flagged. The four rollups: `technology-architecture`,
100
- `product-features`, `data-model-api-surface`, `competitive-positioning`.
101
-
102
- ---
103
-
104
- ## Mode 4 — Self-correction (the harness improves itself, never silently)
105
-
106
- Read the finished run (scope verdict, plan, every dimension `_summary.md`, the rollups) and detect
107
- where the harness under-delivered (method ≠ planned, missing artifacts, low completeness, blocked
108
- where predicted available). Write **one** file — `research/<target>/04-self-correction-proposal.md`
109
- — an ordered table of proposed fixes, each routed to its target file (`re-discovery` /
110
- `re-ingestion` / `re-evaluation` / an agent), with severity, an
111
- additive/structural flag, and a `research/<target>/…` evidence anchor.
112
-
113
- **Approval gate:** present the table and STOP — nothing is applied. The user accepts / rejects /
114
- defers per item. Then the **main session** (not a sub-agent) applies accepted items, flips each row
115
- to APPLIED, and logs the change. Promote only what the *next, different* target would also hit;
116
- prefer additive edits; structural edits are flagged, never auto-applied.
117
-
118
- ---
119
-
120
- ## Ethics (non-negotiable)
121
-
122
- For spec-writing, integration planning, and competitive analysis — **not** credential theft,
123
- rate-limit bypass, or scraping. Full rules: `.claude/rules/re-ingestion.md` §7.
124
-
125
- - **Never enter credentials.** The user logs in; `session` and `wire-capture` rung 3 are opt-in.
126
- - **Redact every secret before writing `raw/`.** Cookies/tokens/JWT-signatures/API-keys never hit disk.
127
- - **wire-capture** climbs the 3-rung ladder (browser-tap → HAR → mitmproxy); rung 3 is user-run,
128
- first-party traffic only. Wireshark is not used (TLS ciphertext).
129
- - **Confirm before any state change or cost.** **No destructive actions, no exfiltration.** Respect TOS/robots.
130
-
131
- ---
132
-
133
- ## Output contract
134
-
135
- ```
136
- research/<target>/
137
- ├── 00-scope-verdict.md ← Discovery: accept/narrow/redirect/refuse
138
- ├── 00-recon-plan.md ← Discovery: access_grade + scope + dimension plan
139
- ├── dimensions/<dim>/{_summary.md, raw/} ← Ingestion (session & wire-capture also: captures/, curls.md)
140
- ├── evaluation/{technology-architecture, product-features, data-model-api-surface, competitive-positioning}.md
141
- ├── README.md ← Evaluation: run story + headline findings
142
- └── 04-self-correction-proposal.md ← Self-correction: proposed harness fixes (approval-gated)
143
- ```
144
-
145
- Cloned repos live in `source/<target>/`. Provenance is the "self-aware" layer: each dump records
146
- *how* it was obtained; Evaluation weights claims by it. Detailed shapes live in the `re-*` rules.
@@ -1,6 +0,0 @@
1
- {
2
- "name": "re",
3
- "version": "0.1.2",
4
- "epoch": null,
5
- "digest": "29a864bc5245"
6
- }