delimit-cli 4.1.53 → 4.3.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/CHANGELOG.md +26 -0
- package/README.md +34 -3
- package/bin/delimit-cli.js +150 -2
- package/bin/delimit-setup.js +22 -7
- package/gateway/ai/agent_dispatch.py +79 -0
- package/gateway/ai/daily_digest.py +386 -0
- package/gateway/ai/ledger_manager.py +32 -0
- package/gateway/ai/license_core.py +2 -0
- package/gateway/ai/notify.py +17 -11
- package/gateway/ai/reddit_proxy.py +28 -9
- package/gateway/ai/sensing/__init__.py +35 -0
- package/gateway/ai/sensing/schema.py +107 -0
- package/gateway/ai/sensing/signal_store.py +348 -0
- package/gateway/ai/server.py +419 -6
- package/gateway/ai/supabase_sync.py +308 -0
- package/gateway/ai/work_order.py +216 -0
- package/gateway/ai/workers/__init__.py +32 -0
- package/gateway/ai/workers/base.py +154 -0
- package/gateway/ai/workers/executor.py +861 -0
- package/gateway/ai/workers/outreach_drafter.py +161 -0
- package/gateway/ai/workers/pr_drafter.py +148 -0
- package/lib/ai-sbom-engine.js +154 -0
- package/lib/trust-page-engine.js +179 -0
- package/lib/wrap-engine.js +431 -0
- package/package.json +14 -1
- package/adapters/codex-security.js +0 -64
- package/adapters/codex-skill.js +0 -78
- package/adapters/cursor-rules.js +0 -73
- package/gateway/ai/continuity.py +0 -462
- package/gateway/ai/inbox_daemon_runner.py +0 -217
- package/gateway/ai/loop_engine.py +0 -1303
- package/gateway/ai/social_cache.py +0 -341
- package/gateway/ai/social_daemon.py +0 -483
- package/gateway/ai/tweet_corpus_schema.sql +0 -76
- package/scripts/crosspost_devto.py +0 -304
- package/scripts/demo-v420-clean.sh +0 -267
- package/scripts/demo-v420-deliberation.sh +0 -217
- package/scripts/demo-v420.sh +0 -55
- package/scripts/sync-gateway.sh +0 -112
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Cross-post markdown articles to Dev.to.
|
|
4
|
-
|
|
5
|
-
Reads articles from a content directory, publishes or updates them
|
|
6
|
-
on Dev.to via the Forem API. Idempotent — tracks published article
|
|
7
|
-
IDs in a local manifest to support updates.
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
# Publish all articles (dry run):
|
|
11
|
-
python scripts/crosspost_devto.py --dir /home/delimit/delimit-private/blog --dry-run
|
|
12
|
-
|
|
13
|
-
# Publish all articles:
|
|
14
|
-
DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --dir /home/delimit/delimit-private/blog
|
|
15
|
-
|
|
16
|
-
# Publish a single article:
|
|
17
|
-
DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --file /home/delimit/delimit-private/blog/01-catch-breaking-api-changes.md
|
|
18
|
-
|
|
19
|
-
# List published articles:
|
|
20
|
-
DEV_TO_API_KEY=your_key python scripts/crosspost_devto.py --list
|
|
21
|
-
|
|
22
|
-
Requires:
|
|
23
|
-
DEV_TO_API_KEY environment variable (get from https://dev.to/settings/extensions)
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
import argparse
|
|
27
|
-
import json
|
|
28
|
-
import os
|
|
29
|
-
import re
|
|
30
|
-
import sys
|
|
31
|
-
from pathlib import Path
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
import requests
|
|
35
|
-
except ImportError:
|
|
36
|
-
print("ERROR: 'requests' package required. Install with: pip install requests")
|
|
37
|
-
sys.exit(1)
|
|
38
|
-
|
|
39
|
-
API_BASE = "https://dev.to/api"
|
|
40
|
-
MANIFEST_FILE = ".devto_manifest.json"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def get_api_key():
|
|
44
|
-
key = os.environ.get("DEV_TO_API_KEY")
|
|
45
|
-
if not key:
|
|
46
|
-
print("ERROR: DEV_TO_API_KEY environment variable not set.")
|
|
47
|
-
print("Get your key from: https://dev.to/settings/extensions")
|
|
48
|
-
sys.exit(1)
|
|
49
|
-
return key
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def load_manifest(content_dir):
|
|
53
|
-
"""Load the manifest that maps filenames to Dev.to article IDs."""
|
|
54
|
-
manifest_path = Path(content_dir) / MANIFEST_FILE
|
|
55
|
-
if manifest_path.exists():
|
|
56
|
-
with open(manifest_path) as f:
|
|
57
|
-
return json.load(f)
|
|
58
|
-
return {}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def save_manifest(content_dir, manifest):
|
|
62
|
-
"""Save the manifest after publishing."""
|
|
63
|
-
manifest_path = Path(content_dir) / MANIFEST_FILE
|
|
64
|
-
with open(manifest_path, "w") as f:
|
|
65
|
-
json.dump(manifest, f, indent=2)
|
|
66
|
-
print(f" Manifest saved: {manifest_path}")
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def parse_frontmatter(filepath):
|
|
70
|
-
"""Parse YAML-ish frontmatter from a markdown file.
|
|
71
|
-
|
|
72
|
-
Returns (frontmatter_dict, body_markdown).
|
|
73
|
-
"""
|
|
74
|
-
text = Path(filepath).read_text(encoding="utf-8")
|
|
75
|
-
|
|
76
|
-
# Match frontmatter block
|
|
77
|
-
match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)", text, re.DOTALL)
|
|
78
|
-
if not match:
|
|
79
|
-
print(f" WARNING: No frontmatter found in {filepath}")
|
|
80
|
-
return {}, text
|
|
81
|
-
|
|
82
|
-
fm_text = match.group(1)
|
|
83
|
-
body = match.group(2).strip()
|
|
84
|
-
|
|
85
|
-
# Simple key: value parser (handles quoted and unquoted values)
|
|
86
|
-
fm = {}
|
|
87
|
-
for line in fm_text.strip().split("\n"):
|
|
88
|
-
line = line.strip()
|
|
89
|
-
if not line or ":" not in line:
|
|
90
|
-
continue
|
|
91
|
-
key, _, value = line.partition(":")
|
|
92
|
-
key = key.strip()
|
|
93
|
-
value = value.strip().strip('"').strip("'")
|
|
94
|
-
# Parse boolean
|
|
95
|
-
if value.lower() == "true":
|
|
96
|
-
value = True
|
|
97
|
-
elif value.lower() == "false":
|
|
98
|
-
value = False
|
|
99
|
-
fm[key] = value
|
|
100
|
-
|
|
101
|
-
return fm, body
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def build_article_payload(filepath, publish=False):
|
|
105
|
-
"""Build the Dev.to article creation/update payload."""
|
|
106
|
-
fm, body = parse_frontmatter(filepath)
|
|
107
|
-
|
|
108
|
-
if not fm.get("title"):
|
|
109
|
-
print(f" ERROR: Article {filepath} has no title in frontmatter.")
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
# Parse tags from comma-separated string
|
|
113
|
-
tags = []
|
|
114
|
-
if fm.get("tags"):
|
|
115
|
-
if isinstance(fm["tags"], str):
|
|
116
|
-
tags = [t.strip() for t in fm["tags"].split(",")]
|
|
117
|
-
else:
|
|
118
|
-
tags = fm["tags"]
|
|
119
|
-
# Dev.to allows max 4 tags
|
|
120
|
-
tags = tags[:4]
|
|
121
|
-
|
|
122
|
-
payload = {
|
|
123
|
-
"article": {
|
|
124
|
-
"title": fm["title"],
|
|
125
|
-
"body_markdown": body,
|
|
126
|
-
"published": publish,
|
|
127
|
-
"tags": tags,
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if fm.get("description"):
|
|
132
|
-
payload["article"]["description"] = fm["description"]
|
|
133
|
-
if fm.get("canonical_url"):
|
|
134
|
-
payload["article"]["canonical_url"] = fm["canonical_url"]
|
|
135
|
-
if fm.get("cover_image"):
|
|
136
|
-
payload["article"]["cover_image"] = fm["cover_image"]
|
|
137
|
-
if fm.get("series"):
|
|
138
|
-
payload["article"]["series"] = fm["series"]
|
|
139
|
-
|
|
140
|
-
return payload
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def create_article(api_key, payload):
|
|
144
|
-
"""Create a new article on Dev.to."""
|
|
145
|
-
resp = requests.post(
|
|
146
|
-
f"{API_BASE}/articles",
|
|
147
|
-
headers={
|
|
148
|
-
"api-key": api_key,
|
|
149
|
-
"Content-Type": "application/json",
|
|
150
|
-
},
|
|
151
|
-
json=payload,
|
|
152
|
-
timeout=30,
|
|
153
|
-
)
|
|
154
|
-
resp.raise_for_status()
|
|
155
|
-
return resp.json()
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def update_article(api_key, article_id, payload):
|
|
159
|
-
"""Update an existing article on Dev.to."""
|
|
160
|
-
resp = requests.put(
|
|
161
|
-
f"{API_BASE}/articles/{article_id}",
|
|
162
|
-
headers={
|
|
163
|
-
"api-key": api_key,
|
|
164
|
-
"Content-Type": "application/json",
|
|
165
|
-
},
|
|
166
|
-
json=payload,
|
|
167
|
-
timeout=30,
|
|
168
|
-
)
|
|
169
|
-
resp.raise_for_status()
|
|
170
|
-
return resp.json()
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def list_articles(api_key):
|
|
174
|
-
"""List the authenticated user's articles."""
|
|
175
|
-
resp = requests.get(
|
|
176
|
-
f"{API_BASE}/articles/me/all",
|
|
177
|
-
headers={"api-key": api_key},
|
|
178
|
-
timeout=30,
|
|
179
|
-
)
|
|
180
|
-
resp.raise_for_status()
|
|
181
|
-
return resp.json()
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def publish_file(api_key, filepath, manifest, content_dir, dry_run=False, publish=False):
|
|
185
|
-
"""Publish or update a single article."""
|
|
186
|
-
filename = Path(filepath).name
|
|
187
|
-
print(f"\nProcessing: {filename}")
|
|
188
|
-
|
|
189
|
-
payload = build_article_payload(filepath, publish=publish)
|
|
190
|
-
if not payload:
|
|
191
|
-
return False
|
|
192
|
-
|
|
193
|
-
title = payload["article"]["title"]
|
|
194
|
-
existing_id = manifest.get(filename, {}).get("id")
|
|
195
|
-
|
|
196
|
-
if dry_run:
|
|
197
|
-
action = "UPDATE" if existing_id else "CREATE"
|
|
198
|
-
status = "published" if publish else "draft"
|
|
199
|
-
print(f" [DRY RUN] Would {action} ({status}): {title}")
|
|
200
|
-
if existing_id:
|
|
201
|
-
print(f" [DRY RUN] Existing article ID: {existing_id}")
|
|
202
|
-
print(f" [DRY RUN] Tags: {payload['article'].get('tags', [])}")
|
|
203
|
-
return True
|
|
204
|
-
|
|
205
|
-
try:
|
|
206
|
-
if existing_id:
|
|
207
|
-
print(f" Updating article {existing_id}: {title}")
|
|
208
|
-
result = update_article(api_key, existing_id, payload)
|
|
209
|
-
else:
|
|
210
|
-
print(f" Creating article: {title}")
|
|
211
|
-
result = create_article(api_key, payload)
|
|
212
|
-
|
|
213
|
-
article_id = result["id"]
|
|
214
|
-
article_url = result.get("url", f"https://dev.to/delimit_ai/{result.get('slug', '')}")
|
|
215
|
-
|
|
216
|
-
manifest[filename] = {
|
|
217
|
-
"id": article_id,
|
|
218
|
-
"url": article_url,
|
|
219
|
-
"title": title,
|
|
220
|
-
}
|
|
221
|
-
save_manifest(content_dir, manifest)
|
|
222
|
-
|
|
223
|
-
status = "PUBLISHED" if publish else "DRAFT"
|
|
224
|
-
print(f" {status}: {article_url}")
|
|
225
|
-
return True
|
|
226
|
-
|
|
227
|
-
except requests.exceptions.HTTPError as e:
|
|
228
|
-
print(f" ERROR: {e}")
|
|
229
|
-
if e.response is not None:
|
|
230
|
-
print(f" Response: {e.response.text[:500]}")
|
|
231
|
-
return False
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
def main():
|
|
235
|
-
parser = argparse.ArgumentParser(description="Cross-post articles to Dev.to")
|
|
236
|
-
parser.add_argument("--dir", help="Directory containing markdown articles")
|
|
237
|
-
parser.add_argument("--file", help="Single markdown file to publish")
|
|
238
|
-
parser.add_argument("--list", action="store_true", help="List published articles")
|
|
239
|
-
parser.add_argument("--dry-run", action="store_true", help="Preview without publishing")
|
|
240
|
-
parser.add_argument("--publish", action="store_true",
|
|
241
|
-
help="Publish articles (default: save as draft)")
|
|
242
|
-
args = parser.parse_args()
|
|
243
|
-
|
|
244
|
-
if args.list:
|
|
245
|
-
api_key = get_api_key()
|
|
246
|
-
articles = list_articles(api_key)
|
|
247
|
-
if not articles:
|
|
248
|
-
print("No articles found.")
|
|
249
|
-
return
|
|
250
|
-
print(f"Found {len(articles)} articles:\n")
|
|
251
|
-
for a in articles:
|
|
252
|
-
status = "PUBLISHED" if a.get("published") else "DRAFT"
|
|
253
|
-
print(f" [{status}] {a['title']}")
|
|
254
|
-
print(f" URL: {a.get('url', 'N/A')}")
|
|
255
|
-
print(f" ID: {a['id']}")
|
|
256
|
-
print()
|
|
257
|
-
return
|
|
258
|
-
|
|
259
|
-
if not args.dir and not args.file:
|
|
260
|
-
parser.print_help()
|
|
261
|
-
print("\nError: specify --dir or --file")
|
|
262
|
-
sys.exit(1)
|
|
263
|
-
|
|
264
|
-
api_key = None
|
|
265
|
-
if not args.dry_run:
|
|
266
|
-
api_key = get_api_key()
|
|
267
|
-
|
|
268
|
-
content_dir = args.dir or str(Path(args.file).parent)
|
|
269
|
-
manifest = load_manifest(content_dir)
|
|
270
|
-
|
|
271
|
-
files = []
|
|
272
|
-
if args.file:
|
|
273
|
-
files = [args.file]
|
|
274
|
-
else:
|
|
275
|
-
files = sorted(
|
|
276
|
-
str(p) for p in Path(args.dir).glob("*.md")
|
|
277
|
-
if not p.name.startswith(".")
|
|
278
|
-
and p.name not in ("README.md", "DEVTO_SETUP.md")
|
|
279
|
-
and not p.name.isupper() # skip ALL-CAPS docs like DEVTO_SETUP.md
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
if not files:
|
|
283
|
-
print(f"No markdown files found in {args.dir}")
|
|
284
|
-
sys.exit(1)
|
|
285
|
-
|
|
286
|
-
print(f"Found {len(files)} article(s) to process")
|
|
287
|
-
if args.dry_run:
|
|
288
|
-
print("MODE: dry run (no API calls)")
|
|
289
|
-
elif args.publish:
|
|
290
|
-
print("MODE: publish (articles will be live)")
|
|
291
|
-
else:
|
|
292
|
-
print("MODE: draft (articles saved as drafts on Dev.to)")
|
|
293
|
-
|
|
294
|
-
success = 0
|
|
295
|
-
for filepath in files:
|
|
296
|
-
if publish_file(api_key, filepath, manifest, content_dir,
|
|
297
|
-
dry_run=args.dry_run, publish=args.publish):
|
|
298
|
-
success += 1
|
|
299
|
-
|
|
300
|
-
print(f"\nDone: {success}/{len(files)} articles processed successfully.")
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if __name__ == "__main__":
|
|
304
|
-
main()
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# v4.20 Clean Demo — realistic mock data, no personal info
|
|
3
|
-
# Uses a temp HOME so nothing leaks
|
|
4
|
-
|
|
5
|
-
set -e
|
|
6
|
-
|
|
7
|
-
export HOME=/tmp/delimit-demo-home
|
|
8
|
-
export DELIMIT_MODEL=cli
|
|
9
|
-
rm -rf "$HOME" 2>/dev/null
|
|
10
|
-
mkdir -p "$HOME/.delimit/memory" "$HOME/.delimit/evidence" "$HOME/.delimit/ledger" "$HOME/.delimit/sessions" "$HOME/.delimit/server/ai"
|
|
11
|
-
|
|
12
|
-
CLI="node /home/delimit/npm-delimit/bin/delimit-cli.js"
|
|
13
|
-
|
|
14
|
-
# ── Seed memories (12 realistic entries) ─────────────────────────────
|
|
15
|
-
$CLI remember "API uses JWT with 15-minute expiry, refresh tokens last 7 days" --tag jwt 2>/dev/null || true
|
|
16
|
-
$CLI remember "PostgreSQL is primary DB, Redis for sessions only" --tag postgres --tag redis 2>/dev/null || true
|
|
17
|
-
$CLI remember "Never modify the payments service on Fridays — incident 2024-11" --tag payments 2>/dev/null || true
|
|
18
|
-
$CLI remember "GraphQL gateway handles auth, REST endpoints are internal only" --tag graphql --tag auth 2>/dev/null || true
|
|
19
|
-
$CLI remember "Docker images must be under 500MB, scanned by Trivy before push" --tag docker --tag security 2>/dev/null || true
|
|
20
|
-
$CLI remember "Staging deploys to us-east-1, production is multi-region" --tag aws --tag deploy 2>/dev/null || true
|
|
21
|
-
$CLI remember "OpenAPI spec is source of truth — SDK types generated from it weekly" --tag openapi 2>/dev/null || true
|
|
22
|
-
$CLI remember "Rate limiting: 100 req/min for free tier, 1000 for pro" --tag api 2>/dev/null || true
|
|
23
|
-
$CLI remember "Migrated from REST to GraphQL for mobile clients in Q3" --tag graphql 2>/dev/null || true
|
|
24
|
-
$CLI remember "Sentry alerting threshold: P50 > 200ms triggers page" --tag sentry --tag monitoring 2>/dev/null || true
|
|
25
|
-
$CLI remember "E2E tests run against staging before every prod deploy" --tag testing --tag ci 2>/dev/null || true
|
|
26
|
-
$CLI remember "Architecture decision: chose event sourcing for audit trail" --tag architecture 2>/dev/null || true
|
|
27
|
-
|
|
28
|
-
# ── Seed models config ───────────────────────────────────────────────
|
|
29
|
-
cat > "$HOME/.delimit/models.json" << 'MODELS'
|
|
30
|
-
{
|
|
31
|
-
"claude": { "api_key": "sk-ant-demo", "enabled": true },
|
|
32
|
-
"gemini": { "api_key": "AIza-demo", "enabled": true },
|
|
33
|
-
"codex": { "api_key": "sk-demo", "enabled": true }
|
|
34
|
-
}
|
|
35
|
-
MODELS
|
|
36
|
-
|
|
37
|
-
# ── Seed MCP config ──────────────────────────────────────────────────
|
|
38
|
-
cat > "$HOME/.mcp.json" << 'MCP'
|
|
39
|
-
{
|
|
40
|
-
"mcpServers": {
|
|
41
|
-
"delimit": {
|
|
42
|
-
"command": "python3",
|
|
43
|
-
"args": ["server.py"]
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
MCP
|
|
48
|
-
|
|
49
|
-
# ── Seed server.py stub (for doctor check) ───────────────────────────
|
|
50
|
-
cat > "$HOME/.delimit/server/ai/server.py" << 'SRV'
|
|
51
|
-
# Delimit MCP Server stub
|
|
52
|
-
@mcp.tool
|
|
53
|
-
def delimit_lint(): pass
|
|
54
|
-
@mcp.tool
|
|
55
|
-
def delimit_diff(): pass
|
|
56
|
-
@mcp.tool
|
|
57
|
-
def delimit_scan(): pass
|
|
58
|
-
SRV
|
|
59
|
-
|
|
60
|
-
# ── Seed license ─────────────────────────────────────────────────────
|
|
61
|
-
cat > "$HOME/.delimit/license.json" << 'LIC'
|
|
62
|
-
{"tier": "Pro", "status": "active", "email": "team@acme.dev"}
|
|
63
|
-
LIC
|
|
64
|
-
|
|
65
|
-
# ── Seed ledger items ────────────────────────────────────────────────
|
|
66
|
-
cat > "$HOME/.delimit/ledger/ops.jsonl" << 'LEDGER'
|
|
67
|
-
{"id":"LED-001","title":"Add rate limiting to /users endpoint","type":"feat","priority":"P1","status":"open","created_at":"2026-04-01T10:00:00Z"}
|
|
68
|
-
{"id":"LED-002","title":"Fix pagination bug in /orders","type":"fix","priority":"P0","status":"in_progress","created_at":"2026-04-02T14:00:00Z"}
|
|
69
|
-
{"id":"LED-003","title":"Migrate auth to OAuth 2.1","type":"feat","priority":"P1","status":"open","created_at":"2026-04-03T09:00:00Z"}
|
|
70
|
-
{"id":"LED-004","title":"Add OpenTelemetry tracing","type":"feat","priority":"P2","status":"open","created_at":"2026-04-03T11:00:00Z"}
|
|
71
|
-
{"id":"LED-005","title":"Update SDK types from spec","type":"task","priority":"P1","status":"done","created_at":"2026-04-04T08:00:00Z"}
|
|
72
|
-
{"id":"LED-006","title":"Security audit: dependency scan","type":"task","priority":"P0","status":"open","created_at":"2026-04-04T16:00:00Z"}
|
|
73
|
-
{"id":"LED-007","title":"Deprecate v1 webhook format","type":"task","priority":"P2","status":"open","created_at":"2026-04-05T10:00:00Z"}
|
|
74
|
-
LEDGER
|
|
75
|
-
|
|
76
|
-
# ── Seed evidence records ────────────────────────────────────────────
|
|
77
|
-
for i in $(seq 1 8); do
|
|
78
|
-
cat >> "$HOME/.delimit/evidence/events.jsonl" << EVIDENCE
|
|
79
|
-
{"type":"evidence_collected","timestamp":"2026-04-0${i}T12:00:00Z","project":"/projects/acme-api","checks_passed":true}
|
|
80
|
-
EVIDENCE
|
|
81
|
-
done
|
|
82
|
-
|
|
83
|
-
# ── Seed session ─────────────────────────────────────────────────────
|
|
84
|
-
cat > "$HOME/.delimit/sessions/session_20260405_100000.json" << 'SESS'
|
|
85
|
-
{"summary":"Rate limiting implementation + SDK type regeneration","description":"Shipped rate limiting for free tier, regenerated SDK types from latest spec","created_at":"2026-04-05T10:00:00Z"}
|
|
86
|
-
SESS
|
|
87
|
-
|
|
88
|
-
# ── Create demo project ──────────────────────────────────────────────
|
|
89
|
-
DEMO_DIR=/tmp/delimit-demo-project
|
|
90
|
-
rm -rf "$DEMO_DIR"
|
|
91
|
-
mkdir -p "$DEMO_DIR/.delimit" "$DEMO_DIR/.git/hooks" "$DEMO_DIR/.github/workflows"
|
|
92
|
-
cd "$DEMO_DIR"
|
|
93
|
-
git init -q .
|
|
94
|
-
git config user.email "dev@acme.dev"
|
|
95
|
-
git config user.name "Acme Dev"
|
|
96
|
-
|
|
97
|
-
cat > openapi.yaml << 'SPEC'
|
|
98
|
-
openapi: "3.0.0"
|
|
99
|
-
info:
|
|
100
|
-
title: Acme API
|
|
101
|
-
version: 2.1.0
|
|
102
|
-
description: The Acme platform API — users, orders, payments, webhooks
|
|
103
|
-
paths:
|
|
104
|
-
/users:
|
|
105
|
-
get:
|
|
106
|
-
operationId: listUsers
|
|
107
|
-
summary: List all users with pagination
|
|
108
|
-
parameters:
|
|
109
|
-
- name: limit
|
|
110
|
-
in: query
|
|
111
|
-
schema:
|
|
112
|
-
type: integer
|
|
113
|
-
default: 20
|
|
114
|
-
- name: offset
|
|
115
|
-
in: query
|
|
116
|
-
schema:
|
|
117
|
-
type: integer
|
|
118
|
-
default: 0
|
|
119
|
-
responses:
|
|
120
|
-
"200":
|
|
121
|
-
description: Paginated user list
|
|
122
|
-
post:
|
|
123
|
-
operationId: createUser
|
|
124
|
-
summary: Create a new user
|
|
125
|
-
requestBody:
|
|
126
|
-
required: true
|
|
127
|
-
content:
|
|
128
|
-
application/json:
|
|
129
|
-
schema:
|
|
130
|
-
type: object
|
|
131
|
-
required: [email, name]
|
|
132
|
-
properties:
|
|
133
|
-
email:
|
|
134
|
-
type: string
|
|
135
|
-
format: email
|
|
136
|
-
name:
|
|
137
|
-
type: string
|
|
138
|
-
responses:
|
|
139
|
-
"201":
|
|
140
|
-
description: User created
|
|
141
|
-
/users/{id}:
|
|
142
|
-
get:
|
|
143
|
-
operationId: getUser
|
|
144
|
-
summary: Get user by ID
|
|
145
|
-
parameters:
|
|
146
|
-
- name: id
|
|
147
|
-
in: path
|
|
148
|
-
required: true
|
|
149
|
-
schema:
|
|
150
|
-
type: string
|
|
151
|
-
format: uuid
|
|
152
|
-
responses:
|
|
153
|
-
"200":
|
|
154
|
-
description: User details
|
|
155
|
-
"404":
|
|
156
|
-
description: Not found
|
|
157
|
-
/orders:
|
|
158
|
-
get:
|
|
159
|
-
operationId: listOrders
|
|
160
|
-
summary: List orders
|
|
161
|
-
responses:
|
|
162
|
-
"200":
|
|
163
|
-
description: Order list
|
|
164
|
-
/payments/webhook:
|
|
165
|
-
post:
|
|
166
|
-
operationId: handlePaymentWebhook
|
|
167
|
-
summary: Stripe webhook endpoint
|
|
168
|
-
responses:
|
|
169
|
-
"200":
|
|
170
|
-
description: Webhook processed
|
|
171
|
-
SPEC
|
|
172
|
-
|
|
173
|
-
cat > .delimit/policies.yml << 'POL'
|
|
174
|
-
name: acme-governance
|
|
175
|
-
preset: default
|
|
176
|
-
enforcement_mode: enforce
|
|
177
|
-
rules:
|
|
178
|
-
no-breaking-changes:
|
|
179
|
-
severity: error
|
|
180
|
-
description: Block removal of endpoints or required fields
|
|
181
|
-
no-unversioned-changes:
|
|
182
|
-
severity: error
|
|
183
|
-
description: Require version bump for breaking changes
|
|
184
|
-
require-descriptions:
|
|
185
|
-
severity: warn
|
|
186
|
-
description: All endpoints must have descriptions
|
|
187
|
-
require-operation-ids:
|
|
188
|
-
severity: warn
|
|
189
|
-
description: All operations need unique IDs
|
|
190
|
-
max-response-time:
|
|
191
|
-
severity: warn
|
|
192
|
-
threshold: 500ms
|
|
193
|
-
require-auth:
|
|
194
|
-
severity: error
|
|
195
|
-
description: All non-public endpoints require authentication
|
|
196
|
-
POL
|
|
197
|
-
|
|
198
|
-
echo "# delimit-governance-hook" > .git/hooks/pre-commit
|
|
199
|
-
chmod +x .git/hooks/pre-commit
|
|
200
|
-
|
|
201
|
-
cat > .github/workflows/api-governance.yml << 'WF'
|
|
202
|
-
name: API Governance
|
|
203
|
-
on: [pull_request]
|
|
204
|
-
jobs:
|
|
205
|
-
governance:
|
|
206
|
-
runs-on: ubuntu-latest
|
|
207
|
-
steps:
|
|
208
|
-
- uses: actions/checkout@v4
|
|
209
|
-
- uses: delimit-ai/delimit-action@v1
|
|
210
|
-
with:
|
|
211
|
-
spec: openapi.yaml
|
|
212
|
-
WF
|
|
213
|
-
|
|
214
|
-
git add -A
|
|
215
|
-
git commit -q -m "initial: Acme API v2.1.0 with governance"
|
|
216
|
-
|
|
217
|
-
# ── Simulated typing ─────────────────────────────────────────────────
|
|
218
|
-
type_cmd() {
|
|
219
|
-
echo ""
|
|
220
|
-
echo -n "$ "
|
|
221
|
-
for ((i=0; i<${#1}; i++)); do
|
|
222
|
-
echo -n "${1:$i:1}"
|
|
223
|
-
sleep 0.04
|
|
224
|
-
done
|
|
225
|
-
echo ""
|
|
226
|
-
sleep 0.3
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
clear
|
|
230
|
-
echo ""
|
|
231
|
-
echo " Delimit v4.20 — The Highest State of AI Governance"
|
|
232
|
-
echo ""
|
|
233
|
-
sleep 2
|
|
234
|
-
|
|
235
|
-
# 1. Doctor — full health check
|
|
236
|
-
type_cmd "delimit doctor"
|
|
237
|
-
$CLI doctor 2>/dev/null
|
|
238
|
-
sleep 4
|
|
239
|
-
|
|
240
|
-
# 2. Status — the visual dashboard
|
|
241
|
-
type_cmd "delimit status"
|
|
242
|
-
$CLI status 2>/dev/null
|
|
243
|
-
sleep 5
|
|
244
|
-
|
|
245
|
-
# 3. Simulate — dry run before commit
|
|
246
|
-
type_cmd "delimit simulate"
|
|
247
|
-
$CLI simulate 2>/dev/null
|
|
248
|
-
sleep 4
|
|
249
|
-
|
|
250
|
-
# 4. Remember — cross-model memory
|
|
251
|
-
type_cmd "delimit remember 'webhook v1 deprecated, migrate by Q3'"
|
|
252
|
-
$CLI remember 'webhook v1 deprecated, migrate by Q3' 2>/dev/null
|
|
253
|
-
sleep 2
|
|
254
|
-
|
|
255
|
-
# 5. Recall — verify it persists
|
|
256
|
-
type_cmd "delimit recall webhook"
|
|
257
|
-
$CLI recall webhook 2>/dev/null
|
|
258
|
-
sleep 3
|
|
259
|
-
|
|
260
|
-
echo ""
|
|
261
|
-
echo " npm i -g delimit-cli"
|
|
262
|
-
echo " github.com/delimit-ai/delimit-mcp-server"
|
|
263
|
-
echo ""
|
|
264
|
-
sleep 3
|
|
265
|
-
|
|
266
|
-
# Cleanup
|
|
267
|
-
rm -rf "$DEMO_DIR" /tmp/delimit-demo-home
|