delimit-cli 4.3.3 → 4.4.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +25 -18
  3. package/adapters/codex-security.js +64 -0
  4. package/adapters/codex-skill.js +78 -0
  5. package/adapters/cursor-rules.js +73 -0
  6. package/bin/delimit-cli.js +4 -4
  7. package/bin/delimit-setup.js +23 -0
  8. package/gateway/ai/backends/governance_bridge.py +168 -2
  9. package/gateway/ai/backends/tools_design.py +563 -83
  10. package/gateway/ai/backends/tools_infra.py +11 -4
  11. package/gateway/ai/backends/tools_real.py +3 -1
  12. package/gateway/ai/content_grounding/__init__.py +98 -0
  13. package/gateway/ai/content_grounding/build.py +350 -0
  14. package/gateway/ai/content_grounding/consume.py +280 -0
  15. package/gateway/ai/content_grounding/features.py +218 -0
  16. package/gateway/ai/content_grounding/fixtures/fail/01_missing_evidence.json +9 -0
  17. package/gateway/ai/content_grounding/fixtures/fail/02_unknown_evidence_prefix.json +9 -0
  18. package/gateway/ai/content_grounding/fixtures/fail/03_banned_comparative.json +17 -0
  19. package/gateway/ai/content_grounding/fixtures/fail/04_banned_adoption.json +17 -0
  20. package/gateway/ai/content_grounding/fixtures/fail/05_aggregate_no_numeric.json +17 -0
  21. package/gateway/ai/content_grounding/fixtures/fail/06_unversioned_inference_rule.json +18 -0
  22. package/gateway/ai/content_grounding/fixtures/pass/01_feature_shipped.json +18 -0
  23. package/gateway/ai/content_grounding/fixtures/pass/02_aggregate_claim.json +23 -0
  24. package/gateway/ai/content_grounding/fixtures/pass/03_attestation.json +16 -0
  25. package/gateway/ai/content_grounding/schemas/claim.schema.json +40 -0
  26. package/gateway/ai/content_grounding/schemas/event.schema.json +23 -0
  27. package/gateway/ai/content_grounding/schemas.py +276 -0
  28. package/gateway/ai/content_grounding/telemetry.py +221 -0
  29. package/gateway/ai/governance.py +89 -0
  30. package/gateway/ai/hot_reload.py +148 -7
  31. package/gateway/ai/ledger_manager.py +9 -2
  32. package/gateway/ai/license_core.py +3 -1
  33. package/gateway/ai/mcp_bridge.py +1 -1
  34. package/gateway/ai/reddit_proxy.py +8 -6
  35. package/gateway/ai/server.py +27 -0
  36. package/gateway/ai/supabase_sync.py +47 -7
  37. package/gateway/ai/swarm.py +1 -1
  38. package/gateway/ai/workers/executor.py +1 -1
  39. package/gateway/core/zero_spec/express_extractor.py +1 -1
  40. package/lib/agent.js +3 -3
  41. package/lib/cross-model-hooks.js +1 -1
  42. package/lib/delimit-template.js +5 -0
  43. package/lib/wrap-engine.js +19 -1
  44. package/package.json +1 -1
@@ -0,0 +1,280 @@
1
+ """
2
+ Week 2 consumer API for the grounding layer (LED-1084).
3
+
4
+ Exposes the gate generators (social drafter, blog pipeline, storyline) must
5
+ call before emitting text. Amendments A6+A9+A10 from the adversarial rebuttal
6
+ (/home/delimit/delimit-private/strategy/CONTENT_GROUNDING_REBUTTAL_2026_04.md):
7
+
8
+ A6. Hard bans on unresolved claim categories (comparative, adoption,
9
+ customer, roadmap) apply here at the gate — not just at schema time.
10
+ A9. Deterministic extraction gate: extract candidate claims from output →
11
+ classify → map to allowed_claim_ids OR approved inference rule →
12
+ reject on any unmatched/uncertain claim.
13
+ A10. One-strike kill-switch semantics: callers that detect a slippage
14
+ MUST revert ALL generators to manual-only mode.
15
+
16
+ Week 2 scope:
17
+ - fetch_grounding_bundle(venture, days) → GroundingBundle for a window
18
+ - build_allowed_claim_set(bundle) → frozenset of safe-to-use texts
19
+ - load_feature_whitelist() → shipped-feature list
20
+ - unreleased_feature_detector(text) → True if text claims a feature
21
+ that is NOT in the whitelist
22
+ - score_draft_grounding(text, bundle) → 0.0-1.0 grounding score
23
+ (simple coverage heuristic for
24
+ v1; Week 3 upgrades to
25
+ classifier-based)
26
+
27
+ Not in scope (Week 3+):
28
+ - Paraphrase classifier (we reject non-verbatim for now)
29
+ - Implication detector (hard-ban suffices for now)
30
+ - Comparative claim classifier (ban + whitelist-only for now)
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import json
35
+ import logging
36
+ import os
37
+ import re
38
+ from dataclasses import dataclass, field
39
+ from pathlib import Path
40
+ from typing import Any, Dict, List, Optional
41
+
42
+ from .schemas import Claim, ClaimType, GroundedEvent, Visibility
43
+ from .build import build_grounding_index
44
+
45
+ logger = logging.getLogger("delimit.ai.content_grounding.consume")
46
+
47
+ FEATURES_FILE = Path(os.environ.get(
48
+ "DELIMIT_GROUNDING_FEATURES",
49
+ str(Path.home() / ".delimit" / "content" / "grounding" / "features.json"),
50
+ ))
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # GroundingBundle — what generators receive
55
+ # ---------------------------------------------------------------------------
56
+
57
+ @dataclass
58
+ class GroundingBundle:
59
+ """A time-windowed snapshot generators use to ground their output.
60
+
61
+ Generators MUST NOT emit claims that don't appear (verbatim) in
62
+ `allowed_claim_texts` or that mention features not in `features`.
63
+ """
64
+ venture: str
65
+ built_at: str
66
+ window_days: int
67
+ events: List[GroundedEvent] = field(default_factory=list)
68
+ allowed_claim_texts: frozenset = field(default_factory=frozenset)
69
+ features: frozenset = field(default_factory=frozenset)
70
+
71
+ def public_events(self) -> List[GroundedEvent]:
72
+ return [e for e in self.events if e.visibility == Visibility.PUBLIC]
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # Bundle construction
77
+ # ---------------------------------------------------------------------------
78
+
79
+ def load_feature_whitelist() -> frozenset:
80
+ """Load the shipped-feature whitelist from features.json.
81
+
82
+ If the file is missing, return an empty frozenset. Generators MUST
83
+ fail-closed on empty whitelist — i.e., refuse to name any feature —
84
+ rather than fall through to prompt-level trust.
85
+ """
86
+ if not FEATURES_FILE.exists():
87
+ logger.info(
88
+ "feature whitelist not found at %s — generators will fail-closed on feature claims",
89
+ FEATURES_FILE,
90
+ )
91
+ return frozenset()
92
+ try:
93
+ data = json.loads(FEATURES_FILE.read_text())
94
+ feats = data.get("features") if isinstance(data, dict) else data
95
+ if isinstance(feats, list):
96
+ return frozenset(str(f).strip() for f in feats if f)
97
+ except Exception as e:
98
+ logger.warning("feature whitelist load failed: %s", e)
99
+ return frozenset()
100
+
101
+
102
+ def build_allowed_claim_set(events: List[GroundedEvent]) -> frozenset:
103
+ """Extract the set of exact claim texts a generator may use verbatim.
104
+
105
+ Returns a frozenset so downstream code can do O(1) membership checks.
106
+ """
107
+ texts: List[str] = []
108
+ for ev in events:
109
+ for claim in ev.claims:
110
+ if claim.visibility == Visibility.PUBLIC and not claim.validate():
111
+ texts.append(claim.text.strip())
112
+ return frozenset(t for t in texts if t)
113
+
114
+
115
+ def fetch_grounding_bundle(
116
+ venture: str = "delimit",
117
+ days: int = 7,
118
+ include_internal: bool = False,
119
+ ) -> GroundingBundle:
120
+ """Primary entrypoint generators call.
121
+
122
+ Week 2 default: 7-day window, public-only events. Tighter than the
123
+ 30-day grounding index so generators see recent-and-relevant, not
124
+ the full history.
125
+ """
126
+ idx = build_grounding_index(venture=venture, days=days)
127
+ events = idx.events if include_internal else idx.public_events()
128
+ return GroundingBundle(
129
+ venture=venture,
130
+ built_at=idx.built_at,
131
+ window_days=days,
132
+ events=list(events),
133
+ allowed_claim_texts=build_allowed_claim_set(events),
134
+ features=load_feature_whitelist(),
135
+ )
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # Gate functions (generators call these before emitting)
140
+ # ---------------------------------------------------------------------------
141
+
142
+ # Very common English verbs that often anchor feature claims in social copy.
143
+ # Used as a lightweight trigger for the unreleased-feature scan. Not a full
144
+ # NLP pipeline — just enough to flag "delimit does X" / "the CLI does Y"
145
+ # constructions for review.
146
+ _FEATURE_CLAIM_TRIGGERS = [
147
+ r"\bdelimit\s+\w+s\b", # "delimit detects", "delimit signs"
148
+ r"\b(?:our|the)\s+(?:cli|action|mcp|server)\s+\w+s\b",
149
+ r"\b(?:we|delimit)\s+(?:built|ship|shipped|support|supports|have|has)\b",
150
+ r"\b(?:new|latest)\s+feature\b",
151
+ r"\b(?:supports|offers|provides)\s+[a-z]",
152
+ ]
153
+
154
+ _TRIGGER_RE = re.compile("|".join(_FEATURE_CLAIM_TRIGGERS), re.IGNORECASE)
155
+
156
+
157
+ def unreleased_feature_detector(
158
+ text: str,
159
+ features: Optional[frozenset] = None,
160
+ ) -> Dict[str, Any]:
161
+ """Scan generated text for feature claims NOT in the shipped whitelist.
162
+
163
+ Returns a dict with:
164
+ - status: "clean" | "flagged"
165
+ - triggers: list of regex matches indicating claim-like language
166
+ - unknown_features_mentioned: list of feature-like substrings that
167
+ look specific enough to be a claim but don't match any entry in
168
+ `features`.
169
+
170
+ Generators MUST fail-closed on status == "flagged" when the whitelist
171
+ is loaded (empty whitelist → fail-closed by default).
172
+ """
173
+ feats = features if features is not None else load_feature_whitelist()
174
+ triggers = _TRIGGER_RE.findall(text or "")
175
+
176
+ # Word-level scan for "delimit-sounding" specific feature names.
177
+ # A "specific feature claim" looks like an identifier or a compound
178
+ # noun that doesn't appear in the features list and is adjacent to a
179
+ # trigger phrase.
180
+ unknown_specifics: List[str] = []
181
+ if triggers:
182
+ # Pull noun-like tokens near triggers. Low recall, intentionally
183
+ # conservative. Week 3 upgrade: proper NER.
184
+ sentences = re.split(r"[.!?]\s+", text or "")
185
+ for sentence in sentences:
186
+ if not _TRIGGER_RE.search(sentence):
187
+ continue
188
+ for token in re.findall(r"\b([a-z][a-z0-9-]{3,})\b", sentence.lower()):
189
+ # Only flag tokens that look domain-specific (mixed case markers,
190
+ # hyphens, or longer-than-common-English). For v1 we skip this
191
+ # lookup entirely and rely on the whitelist match at caller.
192
+ if token in feats:
193
+ continue
194
+ # Don't spam: only flag once per token per text.
195
+ if token not in unknown_specifics and len(token) > 8 and "-" in token:
196
+ unknown_specifics.append(token)
197
+
198
+ # If we have no whitelist and triggers fired, flag regardless — the
199
+ # generator has no basis to claim any feature.
200
+ if triggers and not feats:
201
+ return {
202
+ "status": "flagged",
203
+ "reason": "feature-claim triggers present but feature whitelist is empty",
204
+ "triggers": triggers[:5],
205
+ "unknown_features_mentioned": [],
206
+ }
207
+
208
+ if triggers and unknown_specifics:
209
+ return {
210
+ "status": "flagged",
211
+ "reason": f"{len(unknown_specifics)} unknown-feature-looking tokens near claim triggers",
212
+ "triggers": triggers[:5],
213
+ "unknown_features_mentioned": unknown_specifics[:10],
214
+ }
215
+
216
+ return {
217
+ "status": "clean",
218
+ "triggers": triggers[:5],
219
+ "unknown_features_mentioned": [],
220
+ }
221
+
222
+
223
+ def score_draft_grounding(
224
+ text: str,
225
+ bundle: GroundingBundle,
226
+ threshold: float = 0.85,
227
+ ) -> Dict[str, Any]:
228
+ """Score how much of a draft is covered by the allowed-claim set.
229
+
230
+ v1 algorithm (simple):
231
+ - Score = proportion of sentences in `text` that either (a) contain
232
+ at least one verbatim allowed-claim text, or (b) contain no
233
+ specific feature claim at all (safe conversational filler).
234
+ - If sentence contains a trigger AND no allowed-claim match,
235
+ the sentence counts as UNGROUNDED.
236
+
237
+ Returns:
238
+ {
239
+ "score": 0.0-1.0,
240
+ "threshold": threshold,
241
+ "passed": bool,
242
+ "sentence_count": int,
243
+ "ungrounded_sentences": List[str] (first 3 for debug)
244
+ }
245
+
246
+ Week 3 upgrade: proper claim classifier + paraphrase detection.
247
+ """
248
+ if not text or not text.strip():
249
+ return {"score": 1.0, "threshold": threshold, "passed": True, "sentence_count": 0, "ungrounded_sentences": []}
250
+
251
+ sentences = [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()]
252
+ if not sentences:
253
+ return {"score": 1.0, "threshold": threshold, "passed": True, "sentence_count": 0, "ungrounded_sentences": []}
254
+
255
+ ungrounded: List[str] = []
256
+ grounded_count = 0
257
+ allowed = bundle.allowed_claim_texts
258
+
259
+ for sent in sentences:
260
+ has_trigger = bool(_TRIGGER_RE.search(sent))
261
+ if not has_trigger:
262
+ # No specific feature claim being made → safe.
263
+ grounded_count += 1
264
+ continue
265
+ # Claim-like sentence: require a verbatim match in allowed set.
266
+ matched = any(allowed_text in sent for allowed_text in allowed)
267
+ if matched:
268
+ grounded_count += 1
269
+ else:
270
+ ungrounded.append(sent[:120])
271
+
272
+ score = grounded_count / len(sentences)
273
+ return {
274
+ "score": round(score, 3),
275
+ "threshold": threshold,
276
+ "passed": score >= threshold,
277
+ "sentence_count": len(sentences),
278
+ "grounded_count": grounded_count,
279
+ "ungrounded_sentences": ungrounded[:3],
280
+ }
@@ -0,0 +1,218 @@
1
+ """
2
+ Feature whitelist builder for the content grounding layer (LED-1084 Week 2).
3
+
4
+ Builds `~/.delimit/content/grounding/features.json` from two sources of truth:
5
+ 1. MCP tool registry — every `@mcp.tool()` decorator in ai/server.py is
6
+ a shipped capability the drafter may reference.
7
+ 2. CLI subcommands — parsed from `delimit --help` output (bin/delimit-cli.js
8
+ `program.command(...)` entries) when the CLI binary
9
+ is on PATH. Best-effort; we fall back to just the
10
+ MCP tool set if the CLI parse fails.
11
+
12
+ The grounding gate in ai/content_grounding/consume.py calls
13
+ `load_feature_whitelist()` which reads this file. An empty whitelist
14
+ causes the gate to flag ANY feature-claim language, so populating this
15
+ file is a direct prerequisite for meaningful gate scores.
16
+
17
+ Running this builder is idempotent and safe — it overwrites `features.json`
18
+ with the fresh set, keeps the schema stable, and records `built_at` +
19
+ `source_counts` for audit.
20
+
21
+ Usage:
22
+ python -m ai.content_grounding.features build
23
+ # or programmatically:
24
+ from ai.content_grounding.features import build_and_persist_features
25
+ build_and_persist_features()
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import logging
31
+ import os
32
+ import re
33
+ import subprocess
34
+ import sys
35
+ from datetime import datetime, timezone
36
+ from pathlib import Path
37
+ from typing import Any, Dict, List, Optional, Set
38
+
39
+ from .consume import FEATURES_FILE
40
+
41
+ logger = logging.getLogger("delimit.ai.content_grounding.features")
42
+
43
+ GATEWAY_ROOT = Path(os.environ.get("DELIMIT_GATEWAY_REPO", "/home/delimit/delimit-gateway"))
44
+ NPM_BIN = Path(os.environ.get("DELIMIT_CLI_BIN", "/home/delimit/npm-delimit/bin/delimit-cli.js"))
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Source 1 — MCP tool registry (ai/server.py)
49
+ # ---------------------------------------------------------------------------
50
+
51
+ _MCP_TOOL_DEF_RE = re.compile(
52
+ r"@mcp\.tool\(\)\s*\n\s*def\s+(delimit_[a-z0-9_]+)\s*\(",
53
+ re.MULTILINE,
54
+ )
55
+
56
+
57
+ def extract_mcp_tools(server_py: Path) -> List[str]:
58
+ """Extract every `delimit_<name>` tool registered in server.py.
59
+
60
+ Returns sorted unique tool names. These are the shipped MCP tools the
61
+ drafter is allowed to name. A draft that mentions `delimit_wrap` or
62
+ `delimit_trust_page` passes the feature gate; one that mentions
63
+ `delimit_coinbase_integration` fails.
64
+ """
65
+ if not server_py.is_file():
66
+ logger.warning("MCP server source not found: %s", server_py)
67
+ return []
68
+ try:
69
+ text = server_py.read_text(errors="replace")
70
+ except Exception as e:
71
+ logger.warning("could not read MCP server source: %s", e)
72
+ return []
73
+ tools = sorted(set(_MCP_TOOL_DEF_RE.findall(text)))
74
+ return tools
75
+
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Source 2 — CLI subcommands (bin/delimit-cli.js)
79
+ # ---------------------------------------------------------------------------
80
+
81
+ # Commander.js patterns we grep for:
82
+ # program.command("foo")
83
+ # program.command('foo [bar]')
84
+ # .command("foo <arg>")
85
+ # Captures just the subcommand name (first token up to space).
86
+ _CLI_COMMAND_RE = re.compile(
87
+ r"""(?:^|\.)command\(\s*['"]([a-z][a-z0-9_-]*)""",
88
+ re.MULTILINE | re.IGNORECASE,
89
+ )
90
+
91
+
92
+ def extract_cli_commands(cli_path: Path) -> List[str]:
93
+ """Extract CLI subcommand names from bin/delimit-cli.js.
94
+
95
+ Works statically (no subprocess), so it's safe on CI runners without
96
+ node installed. Returns sorted unique command names.
97
+ """
98
+ if not cli_path.is_file():
99
+ logger.warning("delimit CLI source not found: %s", cli_path)
100
+ return []
101
+ try:
102
+ text = cli_path.read_text(errors="replace")
103
+ except Exception as e:
104
+ logger.warning("could not read CLI source: %s", e)
105
+ return []
106
+ commands = set()
107
+ for m in _CLI_COMMAND_RE.finditer(text):
108
+ name = m.group(1).strip().lower()
109
+ # Filter out obvious false matches (method names that happen to start
110
+ # with a word followed by '('). A real commander subcommand won't
111
+ # contain certain tokens.
112
+ if name in {"log", "error", "warn", "info", "on", "off", "then", "catch", "parse"}:
113
+ continue
114
+ commands.add(name)
115
+ return sorted(commands)
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # Build + persist
120
+ # ---------------------------------------------------------------------------
121
+
122
+ def build_feature_set(
123
+ gateway_root: Optional[Path] = None,
124
+ cli_path: Optional[Path] = None,
125
+ ) -> Dict[str, Any]:
126
+ """Aggregate features from both sources into a dict ready to persist.
127
+
128
+ Shape:
129
+ {
130
+ "version": 1,
131
+ "built_at": "2026-04-24T...",
132
+ "features": ["delimit_lint", "delimit_wrap", "init", "scan", ...],
133
+ "source_counts": {"mcp_tools": N, "cli_commands": M},
134
+ "sources": {
135
+ "mcp_tools": [...],
136
+ "cli_commands": [...],
137
+ }
138
+ }
139
+ """
140
+ gw = gateway_root or GATEWAY_ROOT
141
+ cp = cli_path or NPM_BIN
142
+ mcp_tools = extract_mcp_tools(gw / "ai" / "server.py")
143
+ cli_commands = extract_cli_commands(cp)
144
+ features = sorted(set(mcp_tools) | set(cli_commands))
145
+ return {
146
+ "version": 1,
147
+ "built_at": datetime.now(timezone.utc).isoformat(),
148
+ "features": features,
149
+ "source_counts": {
150
+ "mcp_tools": len(mcp_tools),
151
+ "cli_commands": len(cli_commands),
152
+ "total_unique": len(features),
153
+ },
154
+ "sources": {
155
+ "mcp_tools": mcp_tools,
156
+ "cli_commands": cli_commands,
157
+ },
158
+ }
159
+
160
+
161
+ def build_and_persist_features(
162
+ out_path: Optional[Path] = None,
163
+ gateway_root: Optional[Path] = None,
164
+ cli_path: Optional[Path] = None,
165
+ ) -> Path:
166
+ """Build the feature set and write it to features.json.
167
+
168
+ Returns the path written. Overwrites any previous file.
169
+ """
170
+ target = Path(out_path) if out_path else FEATURES_FILE
171
+ target.parent.mkdir(parents=True, exist_ok=True)
172
+ data = build_feature_set(gateway_root=gateway_root, cli_path=cli_path)
173
+ target.write_text(json.dumps(data, indent=2))
174
+ logger.info(
175
+ "wrote features.json: %d mcp_tools + %d cli_commands → %d unique → %s",
176
+ data["source_counts"]["mcp_tools"],
177
+ data["source_counts"]["cli_commands"],
178
+ data["source_counts"]["total_unique"],
179
+ target,
180
+ )
181
+ return target
182
+
183
+
184
+ # ---------------------------------------------------------------------------
185
+ # CLI entry
186
+ # ---------------------------------------------------------------------------
187
+
188
+ def _main() -> int:
189
+ if len(sys.argv) < 2 or sys.argv[1] not in ("build", "show"):
190
+ print(
191
+ "usage: python -m ai.content_grounding.features build | show\n"
192
+ " build rebuild features.json from MCP + CLI sources\n"
193
+ " show print current features.json stats",
194
+ file=sys.stderr,
195
+ )
196
+ return 2
197
+ if sys.argv[1] == "build":
198
+ out = build_and_persist_features()
199
+ data = json.loads(out.read_text())
200
+ print(f"wrote {out}")
201
+ print(f" mcp_tools: {data['source_counts']['mcp_tools']}")
202
+ print(f" cli_commands: {data['source_counts']['cli_commands']}")
203
+ print(f" total unique: {data['source_counts']['total_unique']}")
204
+ return 0
205
+ if sys.argv[1] == "show":
206
+ if not FEATURES_FILE.exists():
207
+ print(f"no features.json at {FEATURES_FILE}. run 'build' first.", file=sys.stderr)
208
+ return 1
209
+ data = json.loads(FEATURES_FILE.read_text())
210
+ print(f"features.json built_at={data.get('built_at')}")
211
+ print(f" total: {data['source_counts']['total_unique']}")
212
+ print(f" sample: {data['features'][:8]} ...")
213
+ return 0
214
+ return 2
215
+
216
+
217
+ if __name__ == "__main__":
218
+ sys.exit(_main())
@@ -0,0 +1,9 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-999",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": [],
7
+ "claims": [],
8
+ "visibility": "internal"
9
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-888",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["jira:TICKET-123"],
7
+ "claims": [],
8
+ "visibility": "internal"
9
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-777",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-777"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-LED-777-compare",
10
+ "type": "comparative",
11
+ "text": "Delimit is faster than openapi-diff in every benchmark.",
12
+ "evidence_refs": ["LED-777"],
13
+ "visibility": "public"
14
+ }
15
+ ],
16
+ "visibility": "public"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-666",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-666"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-LED-666-adoption",
10
+ "type": "adoption",
11
+ "text": "Used in production by 12 open-source projects.",
12
+ "evidence_refs": ["LED-666"],
13
+ "visibility": "public"
14
+ }
15
+ ],
16
+ "visibility": "public"
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-555",
3
+ "type": "release",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-555"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-LED-555-tests",
10
+ "type": "aggregate",
11
+ "text": "Many tests pass.",
12
+ "evidence_refs": ["LED-555"],
13
+ "visibility": "public"
14
+ }
15
+ ],
16
+ "visibility": "public"
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-444",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-444"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-LED-444-inferred",
10
+ "type": "feature",
11
+ "text": "Delimit handles all the edge cases.",
12
+ "evidence_refs": ["LED-444"],
13
+ "visibility": "internal",
14
+ "inference_rule": "generalization"
15
+ }
16
+ ],
17
+ "visibility": "internal"
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-1010",
3
+ "type": "feature_shipped",
4
+ "date": "2026-04-24T00:00:00Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-1010", "git:f6597185b72d"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-LED-1010-title",
10
+ "type": "feature",
11
+ "text": "Tailwind-aware extract_tokens emits framework default spacing, typography, and breakpoint scales.",
12
+ "evidence_refs": ["LED-1010", "git:f6597185b72d"],
13
+ "visibility": "public"
14
+ }
15
+ ],
16
+ "visibility": "internal",
17
+ "source": "ledger:feature"
18
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "event_id": "evt-ledger-LED-release-v4.3.4",
3
+ "type": "release",
4
+ "date": "2026-04-24T01:39:29Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["LED-1079", "git:67578dc54e77"],
7
+ "claims": [
8
+ {
9
+ "claim_id": "CLM-release-v4.3.4-test-count",
10
+ "type": "aggregate",
11
+ "text": "165 tests pass on delimit-cli v4.3.4.",
12
+ "evidence_refs": ["git:67578dc54e77"],
13
+ "visibility": "public",
14
+ "numeric_evidence": {
15
+ "value": 165,
16
+ "unit": "tests",
17
+ "commit_sha": "67578dc54e77b7dfbed0f6a9ce18baa4ae695c91"
18
+ }
19
+ }
20
+ ],
21
+ "visibility": "public",
22
+ "source": "ledger:release"
23
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "event_id": "evt-att-att_a05050eb8e13277e",
3
+ "type": "attestation",
4
+ "date": "2026-04-23T23:52:51.823Z",
5
+ "venture": "delimit",
6
+ "evidence_refs": ["attest:att_a05050eb8e13277e", "git:ff0c729fda7b"],
7
+ "claims": [],
8
+ "visibility": "public",
9
+ "source": "attestation",
10
+ "raw": {
11
+ "attestation_id": "att_a05050eb8e13277e",
12
+ "kind": "merge_attestation",
13
+ "wrapped_exit": 0,
14
+ "signature_alg": "HMAC-SHA256"
15
+ }
16
+ }