@nclamvn/vibecode-cli 1.0.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.
@@ -0,0 +1,222 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Start Command
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import path from 'path';
8
+ import {
9
+ workspaceExists,
10
+ loadState,
11
+ getProjectName
12
+ } from '../core/workspace.js';
13
+ import {
14
+ createSession,
15
+ getCurrentSessionId,
16
+ getCurrentSessionPath,
17
+ createIntake,
18
+ createBlueprint,
19
+ createContract,
20
+ getSessionFilesStatus
21
+ } from '../core/session.js';
22
+ import { getCurrentState, transitionTo } from '../core/state-machine.js';
23
+ import { STATES } from '../config/constants.js';
24
+ import {
25
+ printBox,
26
+ printError,
27
+ printSuccess,
28
+ printProgress,
29
+ printStatus,
30
+ printNextStep
31
+ } from '../ui/output.js';
32
+ import { printWelcome, printDivider } from '../ui/branding.js';
33
+ import { askSimpleDescription, askNextAction, confirmAction } from '../ui/prompts.js';
34
+
35
+ export async function startCommand(options = {}) {
36
+ try {
37
+ // Check workspace
38
+ if (!await workspaceExists()) {
39
+ printError('No Vibecode workspace found.');
40
+ console.log('Run `vibecode init` first.');
41
+ process.exit(1);
42
+ }
43
+
44
+ // Load current state
45
+ const currentState = await getCurrentState();
46
+ const projectName = await getProjectName();
47
+ let sessionId = await getCurrentSessionId();
48
+
49
+ // Print welcome
50
+ printWelcome();
51
+ printDivider();
52
+ printStatus(projectName, currentState, sessionId);
53
+ printProgress(currentState);
54
+ printDivider();
55
+
56
+ // Handle based on state
57
+ switch (currentState) {
58
+ case STATES.INIT:
59
+ await handleInit(projectName);
60
+ break;
61
+
62
+ case STATES.INTAKE_CAPTURED:
63
+ await handleIntakeCaptured(projectName);
64
+ break;
65
+
66
+ case STATES.BLUEPRINT_DRAFTED:
67
+ await handleBlueprintDrafted(projectName);
68
+ break;
69
+
70
+ case STATES.CONTRACT_DRAFTED:
71
+ await handleContractDrafted(projectName);
72
+ break;
73
+
74
+ case STATES.CONTRACT_LOCKED:
75
+ await handleContractLocked(projectName);
76
+ break;
77
+
78
+ default:
79
+ console.log(chalk.yellow(`State ${currentState} not fully handled in MVP.`));
80
+ console.log('This will be implemented in Phase B.');
81
+ }
82
+
83
+ } catch (error) {
84
+ printError(error.message);
85
+ process.exit(1);
86
+ }
87
+ }
88
+
89
+ async function handleInit(projectName) {
90
+ console.log();
91
+ console.log(chalk.cyan('🏗️ Bắt đầu dự án mới!\n'));
92
+
93
+ // Get project description
94
+ const description = await askSimpleDescription();
95
+
96
+ if (!description || description.trim().length < 10) {
97
+ printError('Mô tả quá ngắn. Vui lòng thử lại.');
98
+ return;
99
+ }
100
+
101
+ const spinner = ora('Creating session...').start();
102
+
103
+ // Create session
104
+ const sessionId = await createSession(projectName);
105
+ spinner.text = 'Capturing intake...';
106
+
107
+ // Create intake
108
+ await createIntake(projectName, description, sessionId);
109
+
110
+ // Transition state
111
+ await transitionTo(STATES.INTAKE_CAPTURED, 'intake_captured');
112
+
113
+ spinner.succeed('Intake captured!');
114
+
115
+ const sessionPath = await getCurrentSessionPath();
116
+ console.log();
117
+ printSuccess(`Intake saved to: ${chalk.gray(path.join(sessionPath, 'intake.md'))}`);
118
+ printNextStep('Review intake.md then run `vibecode start` to continue');
119
+ }
120
+
121
+ async function handleIntakeCaptured(projectName) {
122
+ console.log();
123
+ console.log(chalk.cyan('📋 Intake đã được capture. Sẵn sàng tạo Blueprint.\n'));
124
+
125
+ const action = await askNextAction(STATES.INTAKE_CAPTURED);
126
+
127
+ if (action === 'blueprint') {
128
+ const spinner = ora('Creating blueprint...').start();
129
+ const sessionId = await getCurrentSessionId();
130
+
131
+ await createBlueprint(projectName, sessionId);
132
+ await transitionTo(STATES.BLUEPRINT_DRAFTED, 'blueprint_created');
133
+
134
+ spinner.succeed('Blueprint created!');
135
+
136
+ const sessionPath = await getCurrentSessionPath();
137
+ printSuccess(`Blueprint saved to: ${chalk.gray(path.join(sessionPath, 'blueprint.md'))}`);
138
+ printNextStep('Edit blueprint.md then run `vibecode start` to continue');
139
+
140
+ } else if (action === 'view_intake') {
141
+ const sessionPath = await getCurrentSessionPath();
142
+ console.log(chalk.gray(`\nIntake file: ${path.join(sessionPath, 'intake.md')}`));
143
+
144
+ } else {
145
+ console.log('Goodbye!');
146
+ }
147
+ }
148
+
149
+ async function handleBlueprintDrafted(projectName) {
150
+ console.log();
151
+ console.log(chalk.cyan('📘 Blueprint đã sẵn sàng. Tạo Contract?\n'));
152
+
153
+ const action = await askNextAction(STATES.BLUEPRINT_DRAFTED);
154
+
155
+ if (action === 'contract') {
156
+ const spinner = ora('Creating contract...').start();
157
+ const sessionId = await getCurrentSessionId();
158
+
159
+ await createContract(projectName, sessionId);
160
+ await transitionTo(STATES.CONTRACT_DRAFTED, 'contract_created');
161
+
162
+ spinner.succeed('Contract created!');
163
+
164
+ const sessionPath = await getCurrentSessionPath();
165
+ printSuccess(`Contract saved to: ${chalk.gray(path.join(sessionPath, 'contract.md'))}`);
166
+ printNextStep('Edit contract.md then run `vibecode lock` to finalize');
167
+
168
+ } else if (action === 'edit_blueprint') {
169
+ const sessionPath = await getCurrentSessionPath();
170
+ console.log(chalk.gray(`\nBlueprint file: ${path.join(sessionPath, 'blueprint.md')}`));
171
+
172
+ } else {
173
+ console.log('Goodbye!');
174
+ }
175
+ }
176
+
177
+ async function handleContractDrafted(projectName) {
178
+ console.log();
179
+ console.log(chalk.cyan('📜 Contract đã được tạo. Sẵn sàng lock?\n'));
180
+
181
+ const sessionPath = await getCurrentSessionPath();
182
+ console.log(chalk.gray(`Contract file: ${path.join(sessionPath, 'contract.md')}`));
183
+ console.log();
184
+
185
+ const action = await askNextAction(STATES.CONTRACT_DRAFTED);
186
+
187
+ if (action === 'lock') {
188
+ console.log(chalk.yellow('\nRun `vibecode lock` to lock the contract.'));
189
+
190
+ } else if (action === 'edit_contract') {
191
+ console.log(chalk.gray(`\nEdit: ${path.join(sessionPath, 'contract.md')}`));
192
+
193
+ } else {
194
+ console.log('Goodbye!');
195
+ }
196
+ }
197
+
198
+ async function handleContractLocked(projectName) {
199
+ console.log();
200
+
201
+ const content = `🎉 Contract is LOCKED!
202
+
203
+ Your project is ready for build.
204
+
205
+ In Phase B, you will be able to:
206
+ • Run \`vibecode build\` to start building
207
+ • Run \`vibecode review\` for QA checks
208
+
209
+ For now, you can:
210
+ • Transfer the contract to Claude Code
211
+ • Use Vibecode Kit prompts for building`;
212
+
213
+ printBox(content, { borderColor: 'green' });
214
+
215
+ const sessionPath = await getCurrentSessionPath();
216
+ console.log();
217
+ console.log(chalk.cyan('📁 Your files:'));
218
+ console.log(chalk.gray(` ${sessionPath}/`));
219
+ console.log(chalk.gray(' ├── intake.md'));
220
+ console.log(chalk.gray(' ├── blueprint.md'));
221
+ console.log(chalk.gray(' └── contract.md 🔒'));
222
+ }
@@ -0,0 +1,80 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Status Command
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import chalk from 'chalk';
6
+ import {
7
+ workspaceExists,
8
+ loadState,
9
+ getProjectName
10
+ } from '../core/workspace.js';
11
+ import { getCurrentSessionId, getSessionFilesStatus } from '../core/session.js';
12
+ import { getSpecHash } from '../core/contract.js';
13
+ import {
14
+ printBox,
15
+ printError,
16
+ printProgress,
17
+ printStatus,
18
+ printNextStep
19
+ } from '../ui/output.js';
20
+ import { STATES } from '../config/constants.js';
21
+
22
+ export async function statusCommand(options = {}) {
23
+ try {
24
+ // Check workspace exists
25
+ if (!await workspaceExists()) {
26
+ printError('No Vibecode workspace found.');
27
+ console.log('Run `vibecode init` first.');
28
+ process.exit(1);
29
+ }
30
+
31
+ // Load data
32
+ const stateData = await loadState();
33
+ const projectName = await getProjectName();
34
+ const sessionId = await getCurrentSessionId();
35
+ const specHash = await getSpecHash();
36
+ const filesStatus = await getSessionFilesStatus();
37
+
38
+ // Output
39
+ if (options.json) {
40
+ console.log(JSON.stringify({
41
+ project: projectName,
42
+ state: stateData.current_state,
43
+ session: sessionId,
44
+ specHash: specHash,
45
+ files: filesStatus
46
+ }, null, 2));
47
+ return;
48
+ }
49
+
50
+ // Visual output
51
+ console.log();
52
+ printStatus(projectName, stateData.current_state, sessionId, specHash);
53
+ printProgress(stateData.current_state);
54
+
55
+ // Files status
56
+ if (filesStatus) {
57
+ console.log();
58
+ console.log(chalk.cyan('📁 Session Files:'));
59
+ console.log(chalk.gray(` ├── intake.md ${filesStatus.intake ? '✅' : '⬜'}`));
60
+ console.log(chalk.gray(` ├── blueprint.md ${filesStatus.blueprint ? '✅' : '⬜'}`));
61
+ console.log(chalk.gray(` └── contract.md ${filesStatus.contract ? (specHash ? '🔒' : '📝') : '⬜'}`));
62
+ }
63
+
64
+ // Next step hint
65
+ const hints = {
66
+ [STATES.INIT]: 'Run `vibecode start` to capture requirements',
67
+ [STATES.INTAKE_CAPTURED]: 'Create blueprint for your project',
68
+ [STATES.BLUEPRINT_DRAFTED]: 'Create contract with deliverables',
69
+ [STATES.CONTRACT_DRAFTED]: 'Run `vibecode lock` to finalize contract',
70
+ [STATES.CONTRACT_LOCKED]: 'Contract locked! Ready for build (Phase B)',
71
+ };
72
+
73
+ printNextStep(hints[stateData.current_state] || 'Continue with your workflow');
74
+ console.log();
75
+
76
+ } catch (error) {
77
+ printError(error.message);
78
+ process.exit(1);
79
+ }
80
+ }
@@ -0,0 +1,89 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Constants & Configuration
3
+ // Spec Hash: 0fe43335f5a325e3279a079ce616c052
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ export const VERSION = '1.0.0';
7
+ export const SPEC_HASH = '0fe43335f5a325e3279a079ce616c052';
8
+
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // State Machine
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+
13
+ export const STATES = {
14
+ INIT: 'INIT',
15
+ INTAKE_CAPTURED: 'INTAKE_CAPTURED',
16
+ BLUEPRINT_DRAFTED: 'BLUEPRINT_DRAFTED',
17
+ CONTRACT_DRAFTED: 'CONTRACT_DRAFTED',
18
+ CONTRACT_LOCKED: 'CONTRACT_LOCKED',
19
+ BUILD_IN_PROGRESS: 'BUILD_IN_PROGRESS',
20
+ BUILD_DONE: 'BUILD_DONE',
21
+ REVIEW_PASSED: 'REVIEW_PASSED',
22
+ REVIEW_FAILED: 'REVIEW_FAILED'
23
+ };
24
+
25
+ export const TRANSITIONS = {
26
+ [STATES.INIT]: [STATES.INTAKE_CAPTURED],
27
+ [STATES.INTAKE_CAPTURED]: [STATES.BLUEPRINT_DRAFTED],
28
+ [STATES.BLUEPRINT_DRAFTED]: [STATES.CONTRACT_DRAFTED, STATES.INTAKE_CAPTURED],
29
+ [STATES.CONTRACT_DRAFTED]: [STATES.CONTRACT_LOCKED, STATES.BLUEPRINT_DRAFTED],
30
+ [STATES.CONTRACT_LOCKED]: [STATES.BUILD_IN_PROGRESS],
31
+ [STATES.BUILD_IN_PROGRESS]: [STATES.BUILD_DONE],
32
+ [STATES.BUILD_DONE]: [STATES.REVIEW_PASSED, STATES.REVIEW_FAILED],
33
+ [STATES.REVIEW_PASSED]: [],
34
+ [STATES.REVIEW_FAILED]: [STATES.BUILD_IN_PROGRESS, STATES.CONTRACT_DRAFTED]
35
+ };
36
+
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ // Progress Display Mapping
39
+ // ─────────────────────────────────────────────────────────────────────────────
40
+
41
+ export const PROGRESS_MAP = {
42
+ [STATES.INIT]: { intake: '🔄', blueprint: '⬜', contract: '⬜', build: '⬜', ship: '⬜' },
43
+ [STATES.INTAKE_CAPTURED]: { intake: '✅', blueprint: '🔄', contract: '⬜', build: '⬜', ship: '⬜' },
44
+ [STATES.BLUEPRINT_DRAFTED]: { intake: '✅', blueprint: '✅', contract: '🔄', build: '⬜', ship: '⬜' },
45
+ [STATES.CONTRACT_DRAFTED]: { intake: '✅', blueprint: '✅', contract: '🔄', build: '⬜', ship: '⬜' },
46
+ [STATES.CONTRACT_LOCKED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '🔄', ship: '⬜' },
47
+ [STATES.BUILD_IN_PROGRESS]: { intake: '✅', blueprint: '✅', contract: '✅', build: '🔄', ship: '⬜' },
48
+ [STATES.BUILD_DONE]: { intake: '✅', blueprint: '✅', contract: '✅', build: '✅', ship: '🔄' },
49
+ [STATES.REVIEW_PASSED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '✅', ship: '✅' },
50
+ [STATES.REVIEW_FAILED]: { intake: '✅', blueprint: '✅', contract: '✅', build: '⚠️', ship: '⬜' }
51
+ };
52
+
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // File & Folder Names
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+
57
+ export const WORKSPACE_DIR = '.vibecode';
58
+ export const CONFIG_FILE = 'vibecode.yaml';
59
+ export const STATE_FILE = 'state.json';
60
+ export const SESSIONS_DIR = 'sessions';
61
+ export const LIBRARY_DIR = 'library';
62
+ export const LOGS_DIR = 'logs';
63
+ export const AUDIT_LOG = 'audit.log';
64
+
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // Contract Validation
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+
69
+ export const CONTRACT_REQUIRED_SECTIONS = [
70
+ { key: 'goal', name: 'Goal', pattern: /##\s*🎯\s*Goal/i },
71
+ { key: 'inscope', name: 'In-Scope', pattern: /##\s*✅\s*In-Scope/i },
72
+ { key: 'outscope', name: 'Out-of-Scope', pattern: /##\s*❌\s*Out-of-Scope/i },
73
+ { key: 'deliverables', name: 'Deliverables', pattern: /##\s*📦\s*Deliverables/i },
74
+ { key: 'acceptance', name: 'Acceptance Criteria', pattern: /##\s*✔️\s*Acceptance Criteria/i }
75
+ ];
76
+
77
+ // ─────────────────────────────────────────────────────────────────────────────
78
+ // UI Colors
79
+ // ─────────────────────────────────────────────────────────────────────────────
80
+
81
+ export const COLORS = {
82
+ primary: 'cyan',
83
+ success: 'green',
84
+ warning: 'yellow',
85
+ error: 'red',
86
+ muted: 'gray',
87
+ highlight: 'magenta',
88
+ info: 'blue'
89
+ };
@@ -0,0 +1,194 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Template Generators
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ /**
6
+ * Get intake template with values filled
7
+ */
8
+ export function getIntakeTemplate(projectName, description, sessionId) {
9
+ const timestamp = new Date().toISOString();
10
+
11
+ return `# 📥 INTAKE: ${projectName}
12
+
13
+ ## Captured: ${timestamp}
14
+ ## Session: ${sessionId}
15
+
16
+ ---
17
+
18
+ ## 🎯 Mô tả dự án
19
+
20
+ ${description}
21
+
22
+ ---
23
+
24
+ ## 📋 Parsed Information
25
+
26
+ ### Project Type
27
+ [To be detected by AI]
28
+
29
+ ### Key Requirements
30
+ - [To be extracted from description]
31
+
32
+ ### Target Users
33
+ [To be identified]
34
+
35
+ ### Constraints
36
+ [To be noted]
37
+
38
+ ---
39
+
40
+ ## ✅ Status
41
+
42
+ - [x] Intake captured
43
+ - [ ] Ready for Blueprint
44
+
45
+ ---
46
+
47
+ *Generated by Vibecode CLI v1.0*
48
+ `;
49
+ }
50
+
51
+ /**
52
+ * Get blueprint template
53
+ */
54
+ export function getBlueprintTemplate(projectName, sessionId) {
55
+ const timestamp = new Date().toISOString();
56
+
57
+ return `# 📘 BLUEPRINT: ${projectName}
58
+
59
+ ## Session: ${sessionId}
60
+ ## Created: ${timestamp}
61
+
62
+ ---
63
+
64
+ ## 🎯 Project Overview
65
+
66
+ | Field | Value |
67
+ |-------|-------|
68
+ | Type | [Fill in] |
69
+ | Name | ${projectName} |
70
+ | Goal | [Fill in] |
71
+
72
+ ---
73
+
74
+ ## 📐 Architecture
75
+
76
+ [Describe your architecture here]
77
+
78
+ ---
79
+
80
+ ## 🎨 Design System
81
+
82
+ ### Colors
83
+ - Primary: #3B82F6
84
+ - Secondary: #10B981
85
+ - Accent: #F59E0B
86
+
87
+ ### Typography
88
+ - Headings: Inter
89
+ - Body: Inter
90
+
91
+ ---
92
+
93
+ ## 💻 Tech Stack
94
+
95
+ - Framework: Next.js 14
96
+ - Styling: Tailwind CSS
97
+ - Database: [If needed]
98
+ - Auth: [If needed]
99
+
100
+ ---
101
+
102
+ ## 📁 File Structure
103
+
104
+ \`\`\`
105
+ src/
106
+ ├── app/
107
+ ├── components/
108
+ ├── lib/
109
+ └── styles/
110
+ \`\`\`
111
+
112
+ ---
113
+
114
+ ## ✅ Checkpoint
115
+
116
+ - [ ] Structure approved
117
+ - [ ] Design approved
118
+ - [ ] Tech stack approved
119
+
120
+ ---
121
+
122
+ *Generated by Vibecode CLI v1.0*
123
+ `;
124
+ }
125
+
126
+ /**
127
+ * Get contract template
128
+ */
129
+ export function getContractTemplate(projectName, sessionId) {
130
+ const timestamp = new Date().toISOString();
131
+
132
+ return `# 📜 CONTRACT: ${projectName}
133
+
134
+ ## Session: ${sessionId}
135
+ ## Created: ${timestamp}
136
+ ## Status: DRAFT
137
+ ## Spec Hash: [hash when locked]
138
+
139
+ ---
140
+
141
+ ## 🎯 Goal
142
+
143
+ [Define your clear, specific goal here]
144
+
145
+ ---
146
+
147
+ ## ✅ In-Scope
148
+
149
+ - [ ] [Deliverable 1]
150
+ - [ ] [Deliverable 2]
151
+ - [ ] [Deliverable 3]
152
+
153
+ ---
154
+
155
+ ## ❌ Out-of-Scope
156
+
157
+ - [What is NOT included]
158
+ - [Explicitly excluded items]
159
+
160
+ ---
161
+
162
+ ## 📦 Deliverables
163
+
164
+ | # | Item | Description | Status |
165
+ |---|------|-------------|--------|
166
+ | 1 | [Item] | [Description] | ⬜ |
167
+
168
+ ---
169
+
170
+ ## ✔️ Acceptance Criteria
171
+
172
+ - [ ] [Criterion 1]
173
+ - [ ] [Criterion 2]
174
+
175
+ ---
176
+
177
+ ## ⚠️ Risks & Mitigations
178
+
179
+ | Risk | Mitigation |
180
+ |------|------------|
181
+ | [Risk] | [Mitigation] |
182
+
183
+ ---
184
+
185
+ ## 🔙 Rollback Plan
186
+
187
+ [Define rollback strategy]
188
+
189
+ ---
190
+
191
+ *Generated by Vibecode CLI v1.0*
192
+ *Lock this contract with \`vibecode lock\` when ready*
193
+ `;
194
+ }
@@ -0,0 +1,103 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Contract Validation & Locking
3
+ // ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ import { CONTRACT_REQUIRED_SECTIONS } from '../config/constants.js';
6
+ import { readSessionFile, writeSessionFile, sessionFileExists } from './session.js';
7
+ import { loadState, saveState } from './workspace.js';
8
+ import { generateSpecHash } from '../utils/hash.js';
9
+ import { logAudit } from './state-machine.js';
10
+
11
+ /**
12
+ * Validate contract content
13
+ */
14
+ export function validateContract(content) {
15
+ const errors = [];
16
+ const warnings = [];
17
+
18
+ for (const section of CONTRACT_REQUIRED_SECTIONS) {
19
+ if (!section.pattern.test(content)) {
20
+ errors.push(`Missing required section: ${section.name}`);
21
+ } else {
22
+ // Check if section has content (not just header)
23
+ const sectionMatch = content.match(new RegExp(`${section.pattern.source}[\\s\\S]*?(?=##|$)`, 'i'));
24
+ if (sectionMatch) {
25
+ const sectionContent = sectionMatch[0].replace(section.pattern, '').trim();
26
+ if (sectionContent.length < 10) {
27
+ warnings.push(`Section "${section.name}" appears to be empty or too short`);
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ return {
34
+ valid: errors.length === 0,
35
+ errors,
36
+ warnings
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Lock contract and generate spec hash
42
+ */
43
+ export async function lockContract() {
44
+ // Check contract exists
45
+ if (!await sessionFileExists('contract.md')) {
46
+ throw new Error('Contract file not found. Create contract first.');
47
+ }
48
+
49
+ // Read contract
50
+ const content = await readSessionFile('contract.md');
51
+
52
+ // Validate
53
+ const validation = validateContract(content);
54
+ if (!validation.valid) {
55
+ return {
56
+ success: false,
57
+ errors: validation.errors,
58
+ warnings: validation.warnings
59
+ };
60
+ }
61
+
62
+ // Generate spec hash
63
+ const timestamp = new Date().toISOString();
64
+ const specHash = generateSpecHash(content, timestamp);
65
+
66
+ // Update contract with spec hash and locked status
67
+ const updatedContent = content
68
+ .replace(/## Status: DRAFT/i, '## Status: LOCKED')
69
+ .replace(/## Spec Hash: \[hash when locked\]/i, `## Spec Hash: ${specHash}`);
70
+
71
+ await writeSessionFile('contract.md', updatedContent);
72
+
73
+ // Update state
74
+ const stateData = await loadState();
75
+ stateData.spec_hash = specHash;
76
+ await saveState(stateData);
77
+
78
+ // Log
79
+ await logAudit(`Contract locked with spec hash: ${specHash}`);
80
+
81
+ return {
82
+ success: true,
83
+ specHash,
84
+ timestamp,
85
+ warnings: validation.warnings
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Get current spec hash
91
+ */
92
+ export async function getSpecHash() {
93
+ const stateData = await loadState();
94
+ return stateData.spec_hash;
95
+ }
96
+
97
+ /**
98
+ * Check if contract is locked
99
+ */
100
+ export async function isContractLocked() {
101
+ const specHash = await getSpecHash();
102
+ return specHash !== null;
103
+ }