🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@graph8/devex

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@graph8/devex - npm Package Compare versions

Comparing version
1.0.4
to
1.1.0
+1
dist/commands/validate.d.ts
export declare function validate(specPath: string): Promise<void>;
// src/commands/validate.ts
import { readFileSync, existsSync } from 'node:fs';
import { log } from '../utils/logger.js';
/** Required sections per PRD 6.3 */
const REQUIRED_SECTIONS = [
{ name: 'Goal', pattern: /^##\s+Goal/m },
{ name: 'Constraints / Invariants', pattern: /^##\s+(Constraints|Invariants|Constraints\s*\/\s*Invariants)/m },
{ name: 'Acceptance Criteria', pattern: /^##\s+Acceptance\s+Criteria/m },
{ name: 'Tasks', pattern: /^##\s+Tasks/m },
{ name: 'Test / Verification Evidence', pattern: /^##\s+(Test|Verification|Test\s*\/\s*Verification)/m },
];
/** Check if a section has content beyond just the heading */
function sectionHasContent(content, sectionPattern) {
const match = content.match(sectionPattern);
if (!match)
return false;
const startIndex = match.index + match[0].length;
const nextSectionMatch = content.slice(startIndex).match(/^##\s+/m);
const endIndex = nextSectionMatch
? startIndex + nextSectionMatch.index
: content.length;
const sectionContent = content.slice(startIndex, endIndex).trim();
// Check for placeholder text (common template markers)
const isPlaceholder = /^<.*>$|^\[.*\]$|^<!--.*-->$/m.test(sectionContent) &&
sectionContent.split('\n').filter(l => l.trim() && !l.startsWith('<!--')).length <= 1;
return sectionContent.length > 0 && !isPlaceholder;
}
/** Check that Tasks section has verification items */
function tasksHaveVerification(content) {
const tasksMatch = content.match(/^##\s+Tasks/m);
if (!tasksMatch)
return false;
const startIndex = tasksMatch.index + tasksMatch[0].length;
const nextSectionMatch = content.slice(startIndex).match(/^##\s+/m);
const endIndex = nextSectionMatch
? startIndex + nextSectionMatch.index
: content.length;
const tasksContent = content.slice(startIndex, endIndex);
// Check for task items (- [ ] pattern)
const taskLines = tasksContent.match(/^-\s+\[[ x]\]/gm);
if (!taskLines || taskLines.length === 0)
return false;
// Check for verification markers (Verify:, verification, etc.)
const hasVerifyMarkers = /verify:|verification:|evidence:/im.test(tasksContent);
return hasVerifyMarkers;
}
export async function validate(specPath) {
log.header('g8 validate — Spec Structure Check');
// Check file exists
if (!existsSync(specPath)) {
log.error(`File not found: ${specPath}`);
process.exit(1);
}
// Check it's a markdown file
if (!specPath.endsWith('.md')) {
log.error('Spec file must be a markdown file (.md)');
process.exit(1);
}
log.info(`Validating: ${specPath}`);
const content = readFileSync(specPath, 'utf-8');
const results = [];
// Check each required section
for (const section of REQUIRED_SECTIONS) {
const hasSection = section.pattern.test(content);
const hasContent = hasSection && sectionHasContent(content, section.pattern);
results.push({
section: section.name,
passed: hasContent,
details: !hasSection
? 'Section missing'
: (!hasContent ? 'Section empty or placeholder only' : undefined),
});
if (hasContent) {
log.success(`Found: ${section.name}`);
}
else if (hasSection) {
log.error(`Empty: ${section.name}`);
}
else {
log.error(`Missing: ${section.name}`);
}
}
// Additional check: Tasks have verification
log.header('Task Verification Check');
const tasksValid = tasksHaveVerification(content);
results.push({
section: 'Tasks with verification',
passed: tasksValid,
details: tasksValid ? undefined : 'Tasks must include "Verify:" for each item',
});
if (tasksValid) {
log.success('Tasks include verification methods');
}
else {
log.error('Tasks missing verification methods (add "Verify:" to each task)');
}
// Summary
const passed = results.filter(r => r.passed).length;
const total = results.length;
const allPassed = passed === total;
log.header('Results');
console.log(`
Checks passed: ${passed}/${total}
Status: ${allPassed ? 'VALID' : 'INVALID'}
`);
if (!allPassed) {
log.header('Required Fixes');
results
.filter(r => !r.passed)
.forEach(r => {
log.step(`- ${r.section}: ${r.details || 'Add content'}`);
});
console.log(`
To fix, ensure your spec includes all required sections:
- Goal (what and why)
- Constraints / Invariants (boundaries)
- Acceptance Criteria (observable behaviors)
- Tasks with "Verify:" for each
- Test / Verification Evidence (proof plan)
Template: specs/plan.template.md
`);
process.exit(1);
}
log.success('Spec is valid and ready for implementation');
process.exit(0);
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

# Feature: <name>
<!--
Copy this template to specs/<feature-name>/plan.md
Fill in all sections before implementation
Run: npx @graph8/devex validate specs/<feature-name>/plan.md
-->
## Goal
<One paragraph describing what this achieves and why. Be specific about the problem being solved.>
## Constraints / Invariants
<!-- What must remain true. Boundaries that cannot be crossed. -->
- <Constraint 1>
- <Constraint 2>
## Acceptance Criteria
<!-- Observable behaviors that prove the feature works. Each should be testable. -->
- [ ] <Observable behavior 1>
- [ ] <Observable behavior 2>
- [ ] <Edge case handling>
## Tasks
<!-- Break down into implementable units. Each task must have a verification method. -->
- [ ] **Task 1:** <description>
- Verify: <how to confirm this task is complete>
- [ ] **Task 2:** <description>
- Verify: <how to confirm this task is complete>
- [ ] **Task 3:** <description>
- Verify: <how to confirm this task is complete>
## Non-Goals
<!-- What this explicitly does NOT do. Prevents scope creep. -->
- <What is out of scope 1>
- <What is out of scope 2>
## Test / Verification Evidence
<!-- How correctness will be proven. Commands to run, expected outputs. -->
- <Test command 1 and expected result>
- <Test command 2 and expected result>
---
## Intent Diff (Optional)
<!-- Only include if this spec amends a previous decision or changes existing behavior. -->
| Before | After | Rationale |
|--------|-------|-----------|
| <old behavior> | <new behavior> | <why the change> |
---
## Checklist
- [ ] Spec approved by reviewer
- [ ] Implementation complete
- [ ] All tasks verified
- [ ] Tests passing
- [ ] No unrelated changes
- [ ] Ready for archive (move to specs/_archive/ when merged)
+68
-2

@@ -66,2 +66,28 @@ // src/commands/doctor.ts

}
// Check skills directory
log.header('Cursor Skills');
const skillsDir = join(repoRoot, '.cursor', 'rules', 'skills');
const hasSkillsDir = existsSync(skillsDir);
checks.push({
name: '.cursor/rules/skills/ directory',
passed: hasSkillsDir,
});
if (hasSkillsDir) {
log.success('.cursor/rules/skills/ directory exists');
// Check each skill file
for (const file of STANDARD_FILES.skills) {
const filePath = join(repoRoot, file);
const exists = fileExists(filePath);
checks.push({ name: file, passed: exists });
if (exists) {
log.success(`Found: ${file}`);
}
else {
log.error(`Missing: ${file}`);
}
}
}
else {
log.error('.cursor/rules/skills/ directory missing');
}
// Check for legacy .cursorrules

@@ -105,6 +131,16 @@ const legacyCursorRules = join(repoRoot, '.cursorrules');

}
// Count spec directories
// Check plan template
const templatePath = join(repoRoot, 'specs', 'plan.template.md');
const hasTemplate = fileExists(templatePath);
checks.push({ name: 'specs/plan.template.md', passed: hasTemplate });
if (hasTemplate) {
log.success('Found: specs/plan.template.md');
}
else {
log.error('Missing: specs/plan.template.md');
}
// Count spec directories (look for plan.md or spec.md)
const specDirs = readdirSync(specsDir).filter(f => {
const fullPath = join(specsDir, f);
return existsSync(join(fullPath, 'spec.md'));
return existsSync(join(fullPath, 'plan.md')) || existsSync(join(fullPath, 'spec.md'));
});

@@ -118,2 +154,32 @@ if (specDirs.length > 0) {

}
// Test harness detection (informational only)
log.header('Test Harness (Informational)');
const testIndicators = [
{ name: 'Jest', files: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'] },
{ name: 'Vitest', files: ['vitest.config.js', 'vitest.config.ts', 'vitest.config.mts'] },
{ name: 'Mocha', files: ['.mocharc.js', '.mocharc.json', '.mocharc.yaml'] },
{ name: 'Pytest', files: ['pytest.ini', 'pyproject.toml', 'setup.cfg'] },
{ name: 'Go test', files: ['go.mod'] },
];
let testHarnessFound = false;
for (const indicator of testIndicators) {
for (const file of indicator.files) {
if (existsSync(join(repoRoot, file))) {
log.info(`Detected: ${indicator.name} (${file})`);
testHarnessFound = true;
break;
}
}
}
// Check for test directories
const testDirs = ['test', 'tests', '__tests__', 'spec'];
for (const dir of testDirs) {
if (existsSync(join(repoRoot, dir))) {
log.info(`Test directory found: ${dir}/`);
testHarnessFound = true;
}
}
if (!testHarnessFound) {
log.info('No test harness detected (not required for governance)');
}
// Calculate and display level

@@ -120,0 +186,0 @@ const level = calculateLevel(checks);

+20
-7

@@ -5,2 +5,3 @@ #!/usr/bin/env node

import { doctor } from './commands/doctor.js';
import { validate } from './commands/validate.js';
const args = process.argv.slice(2);

@@ -13,11 +14,13 @@ const command = args[0];

Usage:
npx @graph8/devex init [--force] Bootstrap governance + Cursor rules
npx @graph8/devex doctor Check repo DevEx health
npx @graph8/devex --help Show this help message
npx @graph8/devex --version Show version
npx @graph8/devex init [--force] Bootstrap governance + Cursor rules
npx @graph8/devex doctor Check repo DevEx health
npx @graph8/devex validate <path> Validate spec structure
npx @graph8/devex --help Show this help message
npx @graph8/devex --version Show version
Examples:
npx @graph8/devex init Install governance files (safe mode)
npx @graph8/devex init --force Overwrite existing standard files
npx @graph8/devex doctor Check for missing governance files
npx @graph8/devex init Install governance files (safe mode)
npx @graph8/devex init --force Overwrite existing standard files
npx @graph8/devex doctor Check for missing governance files
npx @graph8/devex validate specs/foo/plan.md Validate a spec file
`;

@@ -42,2 +45,12 @@ async function main() {

break;
case 'validate': {
const specPath = flags[0];
if (!specPath) {
console.error('Error: validate requires a spec path');
console.log('Usage: npx @graph8/devex validate <path-to-spec.md>');
process.exit(1);
}
await validate(specPath);
break;
}
default:

@@ -44,0 +57,0 @@ console.error(`Unknown command: ${command}`);

@@ -16,2 +16,3 @@ /** Path to the template directory (relative to compiled dist/) */

cursorRules: string[];
skills: string[];
governance: string[];

@@ -18,0 +19,0 @@ specs: string[];

@@ -71,2 +71,7 @@ // src/utils/files.ts

],
skills: [
'.cursor/rules/skills/spec-author.mdc',
'.cursor/rules/skills/scoped-implementer.mdc',
'.cursor/rules/skills/proof-gate.mdc',
],
governance: [

@@ -80,2 +85,3 @@ 'manifesto.md',

'specs/backlog.md',
'specs/plan.template.md',
],

@@ -85,4 +91,5 @@ };

...STANDARD_FILES.cursorRules,
...STANDARD_FILES.skills,
...STANDARD_FILES.governance,
...STANDARD_FILES.specs,
];
{
"name": "@graph8/devex",
"version": "1.0.4",
"version": "1.1.0",
"description": "DevEx bootstrap CLI for spec-driven development with Cursor",

@@ -5,0 +5,0 @@ "type": "module",

@@ -21,3 +21,10 @@ # AGENTS.md

## Cursor Skills
Skills live in `.cursor/rules/skills/`:
- `spec-author.mdc` — Compress intent into structured specs
- `scoped-implementer.mdc` — Implement exactly what the spec defines
- `proof-gate.mdc` — Require verification evidence before completion
## Tip
For large changes, prefer a spec first. Use `specs/<feature>/spec.md`.
For large changes, prefer a spec first. Copy `specs/plan.template.md` to `specs/<feature>/plan.md`.
Validate with: `npx @graph8/devex validate specs/<feature>/plan.md`

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet