@elevasis/sdk 1.7.0 → 1.8.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/dist/cli.cjs +523 -4560
- package/dist/index.d.ts +72 -16
- package/package.json +2 -2
- package/reference/claude-config/hooks/post-edit-validate.mjs +0 -11
- package/reference/claude-config/hooks/scaffold-registry-reminder.mjs +188 -0
- package/reference/claude-config/logs/pre-edit-vibe-gate.log +17 -0
- package/reference/claude-config/logs/scaffold-registry-reminder.log +17 -0
- package/reference/claude-config/rules/active-change-index.md +80 -0
- package/reference/claude-config/rules/agent-start-here.md +254 -0
- package/reference/claude-config/rules/deployment.md +0 -1
- package/reference/claude-config/rules/observability.md +2 -2
- package/reference/claude-config/rules/operations.md +64 -0
- package/reference/claude-config/rules/organization-model.md +44 -0
- package/reference/claude-config/rules/organization-os.md +2 -4
- package/reference/claude-config/rules/task-tracking.md +4 -4
- package/reference/claude-config/rules/ui.md +202 -0
- package/reference/claude-config/rules/vibe.md +0 -8
- package/reference/claude-config/settings.json +4 -11
- package/reference/claude-config/skills/configure/SKILL.md +0 -2
- package/reference/claude-config/skills/configure/operations/features.md +1 -3
- package/reference/claude-config/skills/deploy/SKILL.md +3 -13
- package/reference/claude-config/skills/dsp/SKILL.md +2 -2
- package/reference/claude-config/skills/elevasis/SKILL.md +0 -4
- package/reference/claude-config/skills/explore/SKILL.md +5 -5
- package/reference/claude-config/skills/project/SKILL.md +1 -1
- package/reference/claude-config/skills/save/SKILL.md +5 -19
- package/reference/claude-config/skills/setup/SKILL.md +32 -16
- package/reference/claude-config/skills/status/SKILL.md +2 -3
- package/reference/claude-config/skills/submit-request/SKILL.md +1 -1
- package/reference/deployment/command-center.mdx +0 -17
- package/reference/framework/project-structure.mdx +1 -5
- package/reference/packages/ui/src/hooks/README.md +1 -2
- package/reference/scaffold/operations/propagation-pipeline.md +10 -11
- package/reference/scaffold/operations/scaffold-maintenance.md +1 -4
- package/reference/scaffold/recipes/add-a-resource.md +3 -12
- package/reference/scaffold/reference/contracts.md +1 -1
- package/reference/claude-config/hooks/__tests__/pre-edit-vibe-gate.test.mjs +0 -169
- package/reference/claude-config/hooks/pre-edit-vibe-gate.mjs +0 -128
- package/reference/claude-config/rules/docs.md +0 -26
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pre-edit-vibe-gate.test.mjs
|
|
3
|
-
// Tests for the pre-edit-vibe-gate hook.
|
|
4
|
-
// Run with: node --test .claude/hooks/__tests__/pre-edit-vibe-gate.test.mjs
|
|
5
|
-
//
|
|
6
|
-
// Tests the exported `isProtectedPath` helper directly (pure logic, no stdin/stdout
|
|
7
|
-
// mocking needed). Integration cases simulate the full hook by spawning it as a
|
|
8
|
-
// child process.
|
|
9
|
-
|
|
10
|
-
import { describe, it } from 'node:test'
|
|
11
|
-
import assert from 'node:assert/strict'
|
|
12
|
-
import { spawnSync } from 'node:child_process'
|
|
13
|
-
import { resolve, dirname } from 'node:path'
|
|
14
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
15
|
-
|
|
16
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
17
|
-
const HOOK_PATH = resolve(__dirname, '..', 'pre-edit-vibe-gate.mjs')
|
|
18
|
-
|
|
19
|
-
// Import the exported helper for unit-level tests.
|
|
20
|
-
// Using a dynamic import so the module's top-level await (stdin read) doesn't
|
|
21
|
-
// execute — the hook guards that behind the stdin loop which only runs when
|
|
22
|
-
// there is actual input; unit tests call the exported function directly.
|
|
23
|
-
// On Windows, absolute paths must be converted to file:// URLs for ESM imports.
|
|
24
|
-
const { isProtectedPath } = await import(pathToFileURL(HOOK_PATH).href)
|
|
25
|
-
|
|
26
|
-
// Template root = 3 levels up from __tests__/
|
|
27
|
-
const TEMPLATE_ROOT = resolve(__dirname, '..', '..', '..')
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Helper: run the hook as a subprocess with a synthetic PreToolUse event.
|
|
31
|
-
// filePath may be relative (to template root) or already absolute.
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
function runHook(toolName, filePath, env = {}) {
|
|
34
|
-
// Claude always sends absolute paths to hooks. Resolve relative paths
|
|
35
|
-
// against the template root so toRelative() inside the hook works correctly.
|
|
36
|
-
const absFilePath =
|
|
37
|
-
filePath.startsWith('/') || /^[A-Za-z]:/.test(filePath) ? filePath : resolve(TEMPLATE_ROOT, filePath)
|
|
38
|
-
|
|
39
|
-
const event = JSON.stringify({
|
|
40
|
-
tool_name: toolName,
|
|
41
|
-
tool_input: { file_path: absFilePath }
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
return spawnSync(process.execPath, [HOOK_PATH], {
|
|
45
|
-
input: event,
|
|
46
|
-
encoding: 'utf-8',
|
|
47
|
-
env: {
|
|
48
|
-
...process.env,
|
|
49
|
-
// Override AFTER spreading so this always wins over any ambient value
|
|
50
|
-
CLAUDE_PROJECT_DIR: TEMPLATE_ROOT,
|
|
51
|
-
...env
|
|
52
|
-
},
|
|
53
|
-
timeout: 5000
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Unit tests — isProtectedPath (pure function, no process spawning)
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
describe('isProtectedPath — exact protected files', () => {
|
|
62
|
-
it('blocks organization-model.ts', () => {
|
|
63
|
-
assert.equal(isProtectedPath('foundations/config/organization-model.ts'), true)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('blocks organization-model.examples.ts', () => {
|
|
67
|
-
assert.equal(isProtectedPath('foundations/config/organization-model.examples.ts'), true)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('blocks organization-model.override.ts', () => {
|
|
71
|
-
assert.equal(isProtectedPath('foundations/config/organization-model.override.ts'), true)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
describe('isProtectedPath — extensions glob', () => {
|
|
76
|
-
it('blocks a .ts file directly under extensions/', () => {
|
|
77
|
-
assert.equal(isProtectedPath('foundations/config/extensions/deal-ecom.ts'), true)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('blocks a .ts file in a subdirectory under extensions/', () => {
|
|
81
|
-
assert.equal(isProtectedPath('foundations/config/extensions/crm/deal-ecom.ts'), true)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('blocks the discriminated-union index.ts under extensions/', () => {
|
|
85
|
-
assert.equal(isProtectedPath('foundations/config/extensions/index.ts'), true)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('does NOT block a non-.ts file under extensions/', () => {
|
|
89
|
-
assert.equal(isProtectedPath('foundations/config/extensions/README.md'), false)
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
describe('isProtectedPath — unrelated paths always pass', () => {
|
|
94
|
-
it('allows ui/ files', () => {
|
|
95
|
-
assert.equal(isProtectedPath('ui/src/routes/__root.tsx'), false)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
it('allows operations/ files', () => {
|
|
99
|
-
assert.equal(isProtectedPath('operations/src/index.ts'), false)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('allows foundations/types (not config)', () => {
|
|
103
|
-
assert.equal(isProtectedPath('foundations/types/entities.ts'), false)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
it('does NOT block a partial-match path that is not under foundations/config', () => {
|
|
107
|
-
// "organization-model.ts" without the full prefix should not be blocked
|
|
108
|
-
assert.equal(isProtectedPath('src/organization-model.ts'), false)
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
describe('isProtectedPath — partial path / edge cases', () => {
|
|
113
|
-
it('does not block foundations/config/ itself (no .ts suffix)', () => {
|
|
114
|
-
// The directory path should not be blocked
|
|
115
|
-
assert.equal(isProtectedPath('foundations/config/'), false)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('strips leading ./ before matching', () => {
|
|
119
|
-
assert.equal(isProtectedPath('./foundations/config/organization-model.ts'), true)
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// Integration tests — spawn the hook with synthetic events
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
describe('hook subprocess — blocked without VIBE_APPROVED', () => {
|
|
128
|
-
it('exits 2 for Write to organization-model.ts', () => {
|
|
129
|
-
const result = runHook('Write', 'foundations/config/organization-model.ts')
|
|
130
|
-
assert.equal(result.status, 2, `expected exit 2, got ${result.status}`)
|
|
131
|
-
assert.ok(result.stderr.includes('BLOCKED'), 'expected BLOCKED in stderr')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('exits 2 for Edit to extensions/deal-ecom.ts', () => {
|
|
135
|
-
const result = runHook('Edit', 'foundations/config/extensions/deal-ecom.ts')
|
|
136
|
-
assert.equal(result.status, 2)
|
|
137
|
-
assert.ok(result.stderr.includes('BLOCKED'))
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('exits 2 for MultiEdit to organization-model.override.ts', () => {
|
|
141
|
-
const result = runHook('MultiEdit', 'foundations/config/organization-model.override.ts')
|
|
142
|
-
assert.equal(result.status, 2)
|
|
143
|
-
assert.ok(result.stderr.includes('BLOCKED'))
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('hook subprocess — passes with VIBE_APPROVED=1', () => {
|
|
148
|
-
it('exits 0 for Write to organization-model.ts when VIBE_APPROVED=1', () => {
|
|
149
|
-
const result = runHook('Write', 'foundations/config/organization-model.ts', { VIBE_APPROVED: '1' })
|
|
150
|
-
assert.equal(result.status, 0, `expected exit 0, got ${result.status}`)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('exits 0 for Edit to extensions/deal-ecom.ts when VIBE_APPROVED=1', () => {
|
|
154
|
-
const result = runHook('Edit', 'foundations/config/extensions/deal-ecom.ts', { VIBE_APPROVED: '1' })
|
|
155
|
-
assert.equal(result.status, 0)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe('hook subprocess — unrelated path always passes', () => {
|
|
160
|
-
it('exits 0 for Write to ui/src/routes/__root.tsx without VIBE_APPROVED', () => {
|
|
161
|
-
const result = runHook('Write', 'ui/src/routes/__root.tsx')
|
|
162
|
-
assert.equal(result.status, 0)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('exits 0 for Edit to operations/src/index.ts without VIBE_APPROVED', () => {
|
|
166
|
-
const result = runHook('Edit', 'operations/src/index.ts')
|
|
167
|
-
assert.equal(result.status, 0)
|
|
168
|
-
})
|
|
169
|
-
})
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pre-edit-vibe-gate.mjs
|
|
3
|
-
// PreToolUse backstop — blocks raw writes to protected foundations/config paths
|
|
4
|
-
// unless the VIBE_APPROVED=1 trust marker is present.
|
|
5
|
-
//
|
|
6
|
-
// Protected paths (relative to project root):
|
|
7
|
-
// foundations/config/organization-model.ts
|
|
8
|
-
// foundations/config/organization-model.examples.ts
|
|
9
|
-
// foundations/config/organization-model.override.ts
|
|
10
|
-
// foundations/config/extensions/**/*.ts
|
|
11
|
-
//
|
|
12
|
-
// To allow a write: set VIBE_APPROVED=1 in the environment before invoking the
|
|
13
|
-
// agent, or run /configure which sets the marker automatically after user
|
|
14
|
-
// confirmation.
|
|
15
|
-
|
|
16
|
-
import { appendFileSync, mkdirSync } from 'node:fs'
|
|
17
|
-
import { normalize, resolve, relative, sep } from 'node:path'
|
|
18
|
-
import { fileURLToPath } from 'node:url'
|
|
19
|
-
|
|
20
|
-
const ROOT = normalize(process.env.CLAUDE_PROJECT_DIR ?? process.cwd())
|
|
21
|
-
const LOG_DIR = ROOT + sep + '.claude' + sep + 'logs'
|
|
22
|
-
const LOG_FILE = LOG_DIR + sep + 'pre-edit-vibe-gate.log'
|
|
23
|
-
|
|
24
|
-
function log(msg) {
|
|
25
|
-
try {
|
|
26
|
-
mkdirSync(LOG_DIR, { recursive: true })
|
|
27
|
-
appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`)
|
|
28
|
-
} catch {
|
|
29
|
-
// Never crash on log failure
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Normalise an incoming file path (may be absolute or project-relative) to a
|
|
35
|
-
* forward-slash relative path from ROOT for consistent matching.
|
|
36
|
-
*/
|
|
37
|
-
function toRelative(filePath) {
|
|
38
|
-
const abs = normalize(resolve(filePath))
|
|
39
|
-
// relative() returns OS-separator paths — normalise to forward slashes
|
|
40
|
-
return relative(ROOT, abs).replace(/\\/g, '/')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Returns true if `rel` (forward-slash relative path from root) matches one of
|
|
45
|
-
* the protected path patterns. No external deps — uses simple string checks.
|
|
46
|
-
*
|
|
47
|
-
* Protected patterns:
|
|
48
|
-
* foundations/config/organization-model.ts (exact)
|
|
49
|
-
* foundations/config/organization-model.examples.ts (exact)
|
|
50
|
-
* foundations/config/organization-model.override.ts (exact)
|
|
51
|
-
* foundations/config/extensions/**\/*.ts (glob — any .ts under extensions/)
|
|
52
|
-
*/
|
|
53
|
-
export function isProtectedPath(rel) {
|
|
54
|
-
// Normalise away any leading ./
|
|
55
|
-
const r = rel.replace(/^\.\//, '')
|
|
56
|
-
|
|
57
|
-
// Exact protected files
|
|
58
|
-
const EXACT = [
|
|
59
|
-
'foundations/config/organization-model.ts',
|
|
60
|
-
'foundations/config/organization-model.examples.ts',
|
|
61
|
-
'foundations/config/organization-model.override.ts'
|
|
62
|
-
]
|
|
63
|
-
if (EXACT.includes(r)) return true
|
|
64
|
-
|
|
65
|
-
// Glob: foundations/config/extensions/**/*.ts
|
|
66
|
-
// Must start with the prefix, end with .ts, and have at least one path segment
|
|
67
|
-
// after the extensions/ directory.
|
|
68
|
-
const EXT_PREFIX = 'foundations/config/extensions/'
|
|
69
|
-
if (r.startsWith(EXT_PREFIX) && r.endsWith('.ts')) {
|
|
70
|
-
// Ensure there is at least one filename after the prefix (not just the dir itself)
|
|
71
|
-
const remainder = r.slice(EXT_PREFIX.length)
|
|
72
|
-
if (remainder.length > 3) return true // at least "x.ts"
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const BLOCK_MESSAGE =
|
|
79
|
-
'BLOCKED: This file is protected by the vibe gate.\n' +
|
|
80
|
-
'WHY: Direct edits to foundations/config/organization-model.ts and\n' +
|
|
81
|
-
' foundations/config/extensions/*.ts bypass the ambient vibe ceremony\n' +
|
|
82
|
-
' (confirm + /configure write + pnpm check-types).\n' +
|
|
83
|
-
'FIX: Run /configure to codify changes with proper ceremony (sets VIBE_APPROVED=1\n' +
|
|
84
|
-
' automatically), or set VIBE_APPROVED=1 explicitly if you are a power user\n' +
|
|
85
|
-
' who has already completed the ceremony manually.'
|
|
86
|
-
|
|
87
|
-
// Only run the stdin-reading main loop when this file is the entry point,
|
|
88
|
-
// not when it is imported as a module (e.g., by tests).
|
|
89
|
-
const isMain =
|
|
90
|
-
process.argv[1] != null && normalize(fileURLToPath(import.meta.url)) === normalize(resolve(process.argv[1]))
|
|
91
|
-
|
|
92
|
-
if (isMain) {
|
|
93
|
-
try {
|
|
94
|
-
const chunks = []
|
|
95
|
-
for await (const chunk of process.stdin) chunks.push(chunk)
|
|
96
|
-
const input = JSON.parse(Buffer.concat(chunks).toString())
|
|
97
|
-
|
|
98
|
-
const toolName = input.tool_name ?? ''
|
|
99
|
-
const filePath = input.tool_input?.file_path ?? ''
|
|
100
|
-
|
|
101
|
-
// Only gate Write, Edit, MultiEdit
|
|
102
|
-
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
103
|
-
log(`SKIP tool=${toolName} (not a write tool)`)
|
|
104
|
-
process.exit(0)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!filePath) {
|
|
108
|
-
log(`SKIP tool=${toolName} no file_path in input`)
|
|
109
|
-
process.exit(0)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const rel = toRelative(filePath)
|
|
113
|
-
const protected_ = isProtectedPath(rel)
|
|
114
|
-
const approved = process.env.VIBE_APPROVED === '1'
|
|
115
|
-
const decision = protected_ && !approved ? 'BLOCK' : 'ALLOW'
|
|
116
|
-
|
|
117
|
-
log(`tool=${toolName} path=${rel} protected=${protected_} VIBE_APPROVED=${approved} decision=${decision}`)
|
|
118
|
-
|
|
119
|
-
if (decision === 'BLOCK') {
|
|
120
|
-
process.stderr.write(BLOCK_MESSAGE)
|
|
121
|
-
process.exit(2)
|
|
122
|
-
}
|
|
123
|
-
} catch (err) {
|
|
124
|
-
log(`ERROR: ${err?.message ?? String(err)}`)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
process.exit(0)
|
|
128
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Documentation conventions -- structure, frontmatter, /save integration, auto-generated files
|
|
3
|
-
paths:
|
|
4
|
-
- docs/**
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Documentation
|
|
8
|
-
|
|
9
|
-
## Safety Invariants
|
|
10
|
-
|
|
11
|
-
- `docs/index.md` and `docs/resources.md` are auto-generated by the SDK CLI -- never edit manually
|
|
12
|
-
- Regenerate: `pnpm exec elevasis-sdk generate-docs` / `pnpm exec elevasis-sdk generate-resources`
|
|
13
|
-
- Validate: `pnpm exec elevasis-sdk validate-docs` (or `pnpm check:autogenerated-docs`)
|
|
14
|
-
- Every `.md` file in `docs/` MUST have `title` and `description` frontmatter -- docs without it are skipped by the index generator
|
|
15
|
-
- In-progress docs additionally require `status: planned | in-progress | complete`
|
|
16
|
-
|
|
17
|
-
## Structure
|
|
18
|
-
|
|
19
|
-
- All docs are Markdown (`.md`), not MDX -- external projects don't use Fumadocs
|
|
20
|
-
- UI features → `docs/ui/`, platform features → `docs/operations/`, knowledge → `docs/knowledge/`
|
|
21
|
-
- New subdirectories under `docs/` are automatically discovered by the index generator
|
|
22
|
-
|
|
23
|
-
## Detailed Reference
|
|
24
|
-
|
|
25
|
-
- `docs/index.md` -- auto-generated navigation hub (full doc map)
|
|
26
|
-
- `docs/agent-start-here.md` -- canonical agent entrypoint and task-routing guidance
|