@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.
Files changed (39) hide show
  1. package/dist/cli.cjs +523 -4560
  2. package/dist/index.d.ts +72 -16
  3. package/package.json +2 -2
  4. package/reference/claude-config/hooks/post-edit-validate.mjs +0 -11
  5. package/reference/claude-config/hooks/scaffold-registry-reminder.mjs +188 -0
  6. package/reference/claude-config/logs/pre-edit-vibe-gate.log +17 -0
  7. package/reference/claude-config/logs/scaffold-registry-reminder.log +17 -0
  8. package/reference/claude-config/rules/active-change-index.md +80 -0
  9. package/reference/claude-config/rules/agent-start-here.md +254 -0
  10. package/reference/claude-config/rules/deployment.md +0 -1
  11. package/reference/claude-config/rules/observability.md +2 -2
  12. package/reference/claude-config/rules/operations.md +64 -0
  13. package/reference/claude-config/rules/organization-model.md +44 -0
  14. package/reference/claude-config/rules/organization-os.md +2 -4
  15. package/reference/claude-config/rules/task-tracking.md +4 -4
  16. package/reference/claude-config/rules/ui.md +202 -0
  17. package/reference/claude-config/rules/vibe.md +0 -8
  18. package/reference/claude-config/settings.json +4 -11
  19. package/reference/claude-config/skills/configure/SKILL.md +0 -2
  20. package/reference/claude-config/skills/configure/operations/features.md +1 -3
  21. package/reference/claude-config/skills/deploy/SKILL.md +3 -13
  22. package/reference/claude-config/skills/dsp/SKILL.md +2 -2
  23. package/reference/claude-config/skills/elevasis/SKILL.md +0 -4
  24. package/reference/claude-config/skills/explore/SKILL.md +5 -5
  25. package/reference/claude-config/skills/project/SKILL.md +1 -1
  26. package/reference/claude-config/skills/save/SKILL.md +5 -19
  27. package/reference/claude-config/skills/setup/SKILL.md +32 -16
  28. package/reference/claude-config/skills/status/SKILL.md +2 -3
  29. package/reference/claude-config/skills/submit-request/SKILL.md +1 -1
  30. package/reference/deployment/command-center.mdx +0 -17
  31. package/reference/framework/project-structure.mdx +1 -5
  32. package/reference/packages/ui/src/hooks/README.md +1 -2
  33. package/reference/scaffold/operations/propagation-pipeline.md +10 -11
  34. package/reference/scaffold/operations/scaffold-maintenance.md +1 -4
  35. package/reference/scaffold/recipes/add-a-resource.md +3 -12
  36. package/reference/scaffold/reference/contracts.md +1 -1
  37. package/reference/claude-config/hooks/__tests__/pre-edit-vibe-gate.test.mjs +0 -169
  38. package/reference/claude-config/hooks/pre-edit-vibe-gate.mjs +0 -128
  39. 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