Skip to content

Commit ef80755

Browse files
authored
release: v0.3.2 (dev -> main) (#35)
* New branch (#33) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * Feature/bench scorecard ci windows fixes (#34) * chore: add pending project files * refactor(ingest): centralize ingestion via parser/resolver/store layers * docs: document layered ingest architecture * test(perf): add qmd benchmark harness and non-blocking CI regression check * perf(bench): add ingest hotpath benchmark and record qmd optimization * perf(ingest): batch session writes and add stable benchmark tooling * Add benchmark scorecard to CI summary and sticky PR comment * Fix bench import path and temporarily disable design-contract workflow * CI: checkout qmd submodule in perf bench workflow * Fix Windows path handling in ingest session discovery * CI: run full test matrix only on merge branches * CI: auto-create draft prerelease on successful dev CI * CI: auto-template and title for dev to main PRs
1 parent f8628e4 commit ef80755

52 files changed

Lines changed: 5876 additions & 666 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
name: design-contracts
3+
description: Enforces smriti's three design contracts (observability, dry-run, versioning) when writing or modifying CLI command handlers or JSON output.
4+
---
5+
6+
# smriti Design Contract Guardrails
7+
8+
This skill activates whenever you are **adding or modifying a CLI command**,
9+
**changing JSON output**, **touching telemetry/logging code**, or **altering
10+
config defaults** in the smriti project.
11+
12+
---
13+
14+
## Contract 1 — Dry Run
15+
16+
### Mutating commands MUST support `--dry-run`
17+
18+
The following commands write to disk, the database, or the network. Every one of
19+
them **must** honour `--dry-run`:
20+
21+
| Command | Expected guard pattern |
22+
| ------------ | ----------------------------------------------------------------------------- |
23+
| `ingest` | `const dryRun = hasFlag(args, "--dry-run");` then no DB/file writes when true |
24+
| `embed` | same |
25+
| `categorize` | same |
26+
| `tag` | same |
27+
| `share` | same |
28+
| `sync` | same |
29+
| `context` | already implemented — keep it |
30+
31+
When `--dry-run` is active:
32+
33+
- `stdout` must describe **what would happen** (e.g. `Would ingest N sessions`).
34+
- `stderr` must note what was skipped (`No changes were made (--dry-run)`).
35+
- Exit code follows normal success/error rules — dry-run is NOT an error.
36+
- If `--json` is also set, the output envelope must include
37+
`"meta": { "dry_run": true }`.
38+
39+
### Read-only commands MUST reject `--dry-run`
40+
41+
These commands never mutate state. If they receive `--dry-run`, they must print
42+
a usage error and `process.exit(1)`:
43+
44+
`search`, `recall`, `list`, `status`, `show`, `compare`, `projects`, `team`,
45+
`categories`
46+
47+
---
48+
49+
## Contract 2 — Observability / Telemetry
50+
51+
### Never log user content
52+
53+
The following are **forbidden** in any `console.log`, `console.error`, or
54+
log/audit output:
55+
56+
- Message content (`.content`, `.text`, `.body`)
57+
- Query strings passed by the user
58+
- Memory text or embedding data
59+
- File paths provided by the user (as opposed to system-derived paths)
60+
61+
✅ OK to log: command name, exit code, duration, session IDs, counts, smriti
62+
version.
63+
64+
### Telemetry default must be OFF
65+
66+
- `SMRITI_TELEMETRY` must default to `0`/`false`/`"off"` — never `1`.
67+
- Telemetry calls must be guarded: `if (telemetryEnabled) { ... }`.
68+
- Any new telemetry signal must be added to `smriti telemetry sample` output.
69+
70+
---
71+
72+
## Contract 3 — JSON & CLI Versioning
73+
74+
### JSON output is a hard contract
75+
76+
The standard output envelope is:
77+
78+
```json
79+
{ "ok": true, "data": { ... }, "meta": { ... } }
80+
```
81+
82+
Rules:
83+
84+
- **Never remove a field** from `data` or `meta` — add `@deprecated` in a
85+
comment instead.
86+
- **Never rename a field**.
87+
- **Never change a field's type** (e.g. string → number).
88+
- New fields in `data` or `meta` must be **optional**.
89+
- If you must replace a field: add the new one AND keep the old one with a
90+
`_deprecated: true` sibling or comment.
91+
92+
### CLI interface stability
93+
94+
Once a command or flag has shipped:
95+
96+
- **Command names**: frozen.
97+
- **Flag names**: frozen. You may add aliases (e.g. `--dry-run``-n`) but not
98+
rename.
99+
- **Positional argument order**: frozen.
100+
- **Deprecated flags**: must keep working, must emit a `stderr` warning.
101+
102+
---
103+
104+
## Pre-Submission Checklist
105+
106+
Before finishing any edit that touches `src/index.ts` or a command handler:
107+
108+
- [ ] If command is mutating → `--dry-run` is supported and guarded
109+
- [ ] If command is read-only → `--dry-run` is rejected with a usage error
110+
- [ ] No user-supplied content appears in `console.log`/`console.error`
111+
- [ ] If JSON output changed → only fields were **added**, not
112+
removed/renamed/retyped
113+
- [ ] If a new flag was added → it does not conflict with any existing flag name
114+
- [ ] Telemetry default remains off in `config.ts`
115+
116+
If any item fails, fix it before proceeding.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## Release Summary
2+
- Version: `v<package.json.version>`
3+
- Source: `dev`
4+
- Target: `main`
5+
- Scope: promote validated changes from `dev` to `main`
6+
7+
## Changes Included
8+
<!-- AUTO-GENERATED:COMMITS -->
9+
- _Auto-filled by workflow from PR commits._
10+
<!-- /AUTO-GENERATED:COMMITS -->
11+
12+
## Validation
13+
- [ ] CI passed on `dev`
14+
- [ ] Perf bench reviewed (if relevant)
15+
- [ ] Breaking changes documented
16+
- [ ] Release notes verified
17+
18+
## Notes
19+
- Replace or extend this section with any release-specific context.

.github/workflows/ci.yml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,31 @@ on:
77
branches: [main, dev]
88

99
jobs:
10-
test:
10+
test-pr:
11+
if: github.event_name == 'pull_request'
12+
name: Test (ubuntu-latest)
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
with:
19+
submodules: recursive
20+
21+
- name: Setup Bun
22+
uses: oven-sh/setup-bun@v2
23+
with:
24+
bun-version: latest
25+
26+
- name: Install dependencies
27+
run: bun install
28+
29+
- name: Run tests
30+
# Fast PR validation on Linux only.
31+
run: bun test test/
32+
33+
test-merge:
34+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
1135
name: Test (${{ matrix.os }})
1236
runs-on: ${{ matrix.os }}
1337
strategy:
@@ -30,7 +54,5 @@ jobs:
3054
run: bun install
3155

3256
- name: Run tests
33-
# We only run tests in the smriti/test directory.
34-
# qmd/ tests are skipped here as they are part of the backbone submodule
35-
# and may have heavy dependencies (like local LLMs) that the runner lacks.
57+
# Full cross-platform test matrix for merge branches.
3658
run: bun test test/
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Dev Draft Release
2+
3+
on:
4+
workflow_run:
5+
workflows: ["CI"]
6+
types: [completed]
7+
8+
jobs:
9+
draft-release:
10+
name: Create/Update Dev Draft Release
11+
if: >
12+
github.event.workflow_run.conclusion == 'success' &&
13+
github.event.workflow_run.event == 'push' &&
14+
github.event.workflow_run.head_branch == 'dev'
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: write
18+
19+
steps:
20+
- name: Checkout dev commit
21+
uses: actions/checkout@v4
22+
with:
23+
ref: ${{ github.event.workflow_run.head_sha }}
24+
fetch-depth: 0
25+
submodules: recursive
26+
27+
- name: Compute dev tag
28+
id: tag
29+
run: |
30+
BASE_VERSION=$(node -p "require('./package.json').version")
31+
DEV_SUFFIX="dev.${{ github.event.workflow_run.run_number }}"
32+
DEV_TAG="v${BASE_VERSION}-${DEV_SUFFIX}"
33+
echo "base_version=${BASE_VERSION}" >> "$GITHUB_OUTPUT"
34+
echo "dev_tag=${DEV_TAG}" >> "$GITHUB_OUTPUT"
35+
36+
- name: Remove previous dev draft releases
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
const releases = await github.paginate(github.rest.repos.listReleases, {
41+
owner: context.repo.owner,
42+
repo: context.repo.repo,
43+
per_page: 100,
44+
});
45+
46+
for (const release of releases) {
47+
const isDevTag = /-dev\.\d+$/.test(release.tag_name || "");
48+
if (isDevTag && release.draft) {
49+
await github.rest.repos.deleteRelease({
50+
owner: context.repo.owner,
51+
repo: context.repo.repo,
52+
release_id: release.id,
53+
});
54+
}
55+
}
56+
57+
- name: Remove previous dev tags
58+
env:
59+
GH_TOKEN: ${{ github.token }}
60+
run: |
61+
for tag in $(git tag --list 'v*-dev.*'); do
62+
git push origin ":refs/tags/${tag}" || true
63+
done
64+
65+
- name: Create dev draft prerelease
66+
uses: softprops/action-gh-release@v2
67+
with:
68+
tag_name: ${{ steps.tag.outputs.dev_tag }}
69+
target_commitish: ${{ github.event.workflow_run.head_sha }}
70+
name: Dev Draft ${{ steps.tag.outputs.dev_tag }}
71+
generate_release_notes: true
72+
draft: true
73+
prerelease: true
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Dev->Main PR Autofill
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize]
6+
branches: [main]
7+
8+
jobs:
9+
autofill:
10+
if: github.event.pull_request.head.ref == 'dev' && github.event.pull_request.base.ref == 'main'
11+
runs-on: ubuntu-latest
12+
permissions:
13+
pull-requests: write
14+
contents: read
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Auto-set title and body
21+
uses: actions/github-script@v7
22+
with:
23+
script: |
24+
const fs = require("fs");
25+
const path = ".github/PULL_REQUEST_TEMPLATE/dev-to-main.md";
26+
const template = fs.readFileSync(path, "utf8");
27+
28+
const { owner, repo } = context.repo;
29+
const pull_number = context.payload.pull_request.number;
30+
31+
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
32+
const version = pkg.version || "0.0.0";
33+
const title = `release: v${version} (dev -> main)`;
34+
35+
const commits = await github.paginate(github.rest.pulls.listCommits, {
36+
owner,
37+
repo,
38+
pull_number,
39+
per_page: 100,
40+
});
41+
const commitLines = commits.map((c) => `- ${c.commit.message.split("\n")[0]} (${c.sha.slice(0, 7)})`);
42+
const commitsText = commitLines.length ? commitLines.join("\n") : "- No commits found.";
43+
44+
const body = template
45+
.replace("`v<package.json.version>`", `v${version}`)
46+
.replace(
47+
/<!-- AUTO-GENERATED:COMMITS -->[\s\S]*?<!-- \/AUTO-GENERATED:COMMITS -->/m,
48+
`<!-- AUTO-GENERATED:COMMITS -->\n${commitsText}\n<!-- /AUTO-GENERATED:COMMITS -->`
49+
);
50+
51+
await github.rest.pulls.update({
52+
owner,
53+
repo,
54+
pull_number,
55+
title,
56+
body,
57+
});

0 commit comments

Comments
 (0)