Skip to content

Commit e91aaed

Browse files
oratisclaude
andauthored
feat(core,ci,docs): built-in skills + effort-bench + release pipeline + behavior parity (#14)
What ships ---------- Built-in skills (15 SKILL.md files in packages/core/skills/) - init / verify / run / code-review / security-review / skill-creator - consolidate-memory / fewer-permission-prompts / update-config / keybindings-help - deepseek-api / loop / schedule / review / pdf Each has frontmatter (name + description) and a short body. The CLI auto-locates this dir via `@deepcode/core/package.json` resolution and passes it to loadSkills as the `builtinDir`. The 15 are baseline stubs — content can grow over time. Effort benchmark (packages/core/scripts/effort-bench.ts) - 3 scenarios × 5 effort tiers = 15 real API calls - Reads creds from DEEPSEEK_API_KEY env OR ~/.deepcode/credentials.json - Writes docs/design/effort-levels-measured.csv next to the design doc - Computes ¥ cost using DeepSeek pricing from docs/design/effort-levels.md §2.4 - Run: pnpm -F @deepcode/core tsx scripts/effort-bench.ts Release pipeline (.github/workflows/release.yml) - Triggered on git tag v* - validate job: typecheck + test + build on Ubuntu Node 22 - publish-cli: npm publish, with --tag <channel> for beta/nightly - build-mac: stubbed (if: false) until M6 ships Electron app — has the full electron-builder + Apple notarization + artifact upload structure ready - github-release: auto release notes from `git log`, prerelease flag for non-stable - Channel/version/mandatory derived from tag: vX.Y.Z (stable), vX.Y.Z-beta.N, vX.Y.Z-nightly.N, vX.Y.Z+security.N BEHAVIOR_PARITY.md (docs/) - Per-feature comparison table vs Claude Code: 30+ slash commands, hook events, hook handler types, modes, memory system, MCP, tools, CLI flags - Each row marked ✅ matches / 🟡 caveats / 🔄 deferred / ⚠️ deliberately differs / 🆕 DeepCode-only REPL plumbing (apps/cli/src/repl.ts) - New `resolveBuiltinSkillsDir()` helper finds packaged skills/ via @deepcode/core/package.json + dirname; loaded into skills registry alongside user/project/plugin layers Package manifest - packages/core/package.json `files` now includes "skills/" so npm install bundles the built-in skills Verified -------- pnpm typecheck → green pnpm test → 308 passed / 8 skipped / 0 failed (unchanged — skills are content) pnpm format:check → conformant Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7dba3c8 commit e91aaed

20 files changed

Lines changed: 743 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write # for GitHub Releases
10+
id-token: write # for npm provenance (if enabled)
11+
12+
jobs:
13+
# ----------------------------------------------------------------------
14+
# Validate
15+
# ----------------------------------------------------------------------
16+
validate:
17+
name: Validate before release
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 15
20+
outputs:
21+
version: ${{ steps.version.outputs.version }}
22+
channel: ${{ steps.version.outputs.channel }}
23+
is_mandatory: ${{ steps.version.outputs.is_mandatory }}
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- uses: pnpm/action-setup@v4
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version: '22'
31+
cache: 'pnpm'
32+
33+
- run: pnpm install --frozen-lockfile
34+
- run: pnpm typecheck
35+
- run: pnpm test
36+
- run: pnpm build
37+
38+
- id: version
39+
name: Parse version + channel from tag
40+
run: |
41+
TAG="${GITHUB_REF#refs/tags/}"
42+
VERSION="${TAG#v}"
43+
CHANNEL="stable"
44+
IS_MANDATORY="false"
45+
if [[ "$VERSION" == *-nightly.* ]]; then CHANNEL="nightly"; fi
46+
if [[ "$VERSION" == *-beta.* ]]; then CHANNEL="beta"; fi
47+
if [[ "$VERSION" == *+security.* ]]; then IS_MANDATORY="true"; fi
48+
echo "version=$VERSION" >> $GITHUB_OUTPUT
49+
echo "channel=$CHANNEL" >> $GITHUB_OUTPUT
50+
echo "is_mandatory=$IS_MANDATORY" >> $GITHUB_OUTPUT
51+
echo "Released as version=$VERSION channel=$CHANNEL mandatory=$IS_MANDATORY"
52+
53+
# ----------------------------------------------------------------------
54+
# Publish CLI to npm
55+
# ----------------------------------------------------------------------
56+
publish-cli:
57+
name: Publish deepcode-cli to npm
58+
needs: validate
59+
runs-on: ubuntu-latest
60+
timeout-minutes: 10
61+
steps:
62+
- uses: actions/checkout@v4
63+
64+
- uses: pnpm/action-setup@v4
65+
- uses: actions/setup-node@v4
66+
with:
67+
node-version: '22'
68+
cache: 'pnpm'
69+
registry-url: https://registry.npmjs.org
70+
71+
- run: pnpm install --frozen-lockfile
72+
- run: pnpm build
73+
74+
- name: Set CLI version from tag
75+
run: |
76+
cd apps/cli
77+
npm version "${{ needs.validate.outputs.version }}" --no-git-tag-version
78+
79+
- name: Publish
80+
run: |
81+
cd apps/cli
82+
# On beta/nightly, publish with --tag <channel> so default `latest`
83+
# stays on stable.
84+
if [ "${{ needs.validate.outputs.channel }}" = "stable" ]; then
85+
pnpm publish --no-git-checks --access public
86+
else
87+
pnpm publish --no-git-checks --access public --tag ${{ needs.validate.outputs.channel }}
88+
fi
89+
env:
90+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
91+
92+
# ----------------------------------------------------------------------
93+
# Build + sign Mac client (.dmg)
94+
# ----------------------------------------------------------------------
95+
build-mac:
96+
name: Build + sign + notarize Mac client
97+
needs: validate
98+
runs-on: macos-14
99+
if: false # Disabled until apps/desktop has actual Electron code (M6)
100+
timeout-minutes: 30
101+
steps:
102+
- uses: actions/checkout@v4
103+
- uses: pnpm/action-setup@v4
104+
- uses: actions/setup-node@v4
105+
with:
106+
node-version: '22'
107+
cache: 'pnpm'
108+
- run: pnpm install --frozen-lockfile
109+
110+
- name: Set version
111+
run: |
112+
cd apps/desktop
113+
npm version "${{ needs.validate.outputs.version }}" --no-git-tag-version
114+
115+
- name: Build (electron-builder)
116+
env:
117+
APPLE_ID: ${{ secrets.APPLE_ID }}
118+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
119+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
120+
CSC_LINK: ${{ secrets.CSC_LINK }}
121+
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
122+
run: |
123+
cd apps/desktop
124+
pnpm electron-builder --mac --arm64 --x64 --publish never
125+
126+
- name: Upload artifacts
127+
uses: actions/upload-artifact@v4
128+
with:
129+
name: mac-release
130+
path: |
131+
apps/desktop/release/*.dmg
132+
apps/desktop/release/latest-mac.yml
133+
134+
# ----------------------------------------------------------------------
135+
# GitHub Release
136+
# ----------------------------------------------------------------------
137+
github-release:
138+
name: Publish GitHub Release
139+
needs: [validate, publish-cli]
140+
runs-on: ubuntu-latest
141+
timeout-minutes: 10
142+
steps:
143+
- uses: actions/checkout@v4
144+
145+
- name: Mark mandatory in latest-mac.yml if applicable
146+
if: needs.validate.outputs.is_mandatory == 'true'
147+
run: |
148+
echo "Note: this release tag has +security.X suffix — Mac client auto-update will be marked mandatory."
149+
150+
- name: Generate release notes from PR labels
151+
id: notes
152+
run: |
153+
# Lightweight: extract section between last tag and HEAD; group by label.
154+
# Real implementation lands in scripts/gen-release-notes.ts (M9-ext).
155+
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
156+
if [ -n "$PREV_TAG" ]; then
157+
RANGE="$PREV_TAG..HEAD"
158+
else
159+
RANGE="HEAD"
160+
fi
161+
{
162+
echo "## Changes"
163+
git log --pretty=format:'- %s' $RANGE
164+
} > release-notes.md
165+
cat release-notes.md
166+
167+
- name: Create GitHub Release
168+
uses: softprops/action-gh-release@v2
169+
with:
170+
name: ${{ needs.validate.outputs.version }}
171+
body_path: release-notes.md
172+
prerelease: ${{ needs.validate.outputs.channel != 'stable' }}
173+
generate_release_notes: false
174+
# files: omitted until M6 mac artifacts exist

apps/cli/src/repl.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,12 @@ export async function startRepl(opts: ReplOpts): Promise<number> {
115115
home: opts.home,
116116
maxBytes: (settings.memoryLoadCapKB ?? 100) * 1024,
117117
});
118+
// Locate built-in skills dir (packaged with @deepcode/core)
119+
const builtinSkillsDir = await resolveBuiltinSkillsDir();
118120
const skills = await loadSkills({
119121
cwd,
120122
home: opts.home,
123+
builtinDir: builtinSkillsDir,
121124
overrides: settings.skillOverrides,
122125
});
123126
const styles = await loadOutputStyles({ cwd, home: opts.home });
@@ -317,3 +320,29 @@ function formatToolInput(input: Record<string, unknown>): string {
317320
function truncate(s: string, n: number): string {
318321
return s.length > n ? s.slice(0, n) + '…' : s;
319322
}
323+
324+
/**
325+
* Find the bundled built-in skills directory.
326+
* In dev: <repo>/packages/core/skills/.
327+
* In published package: packaged inside @deepcode/core/skills/.
328+
* Returns undefined if not found.
329+
*/
330+
async function resolveBuiltinSkillsDir(): Promise<string | undefined> {
331+
const { createRequire } = await import('node:module');
332+
const require_ = createRequire(import.meta.url);
333+
try {
334+
// Resolve any file inside the package, then walk up to find skills/
335+
const corePkg = require_.resolve('@deepcode/core/package.json');
336+
const path = await import('node:path');
337+
const fs = await import('node:fs/promises');
338+
const skillsDir = path.join(path.dirname(corePkg), 'skills');
339+
try {
340+
await fs.access(skillsDir);
341+
return skillsDir;
342+
} catch {
343+
return undefined;
344+
}
345+
} catch {
346+
return undefined;
347+
}
348+
}

0 commit comments

Comments
 (0)