kspec 1.0.29 → 1.0.30
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/README.md +23 -0
- package/package.json +1 -1
- package/src/index.js +107 -1
package/README.md
CHANGED
|
@@ -107,6 +107,29 @@ init → analyse → spec → verify-spec → tasks → verify-tasks → build
|
|
|
107
107
|
| `kspec jira-subtasks` | Create Jira subtasks from tasks.md |
|
|
108
108
|
| `kspec jira-subtasks PROJ-123` | Create subtasks under specific issue |
|
|
109
109
|
|
|
110
|
+
## Contracts (Beta)
|
|
111
|
+
|
|
112
|
+
Enforce structured outputs and non-negotiable checks in your spec. This prevents context loss and regression by ensuring specific files and patterns exist before verification proceeds.
|
|
113
|
+
|
|
114
|
+
Add a `## Contract` section to your `spec.md`:
|
|
115
|
+
|
|
116
|
+
```markdown
|
|
117
|
+
## Contract
|
|
118
|
+
|
|
119
|
+
\`\`\`json
|
|
120
|
+
{
|
|
121
|
+
"output_files": ["package.json", "src/index.js"],
|
|
122
|
+
"checks": [
|
|
123
|
+
{ "type": "contains", "file": "package.json", "text": "\"name\": \"my-app\"" }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
\`\`\`
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`kspec verify` will automatically validate these rules.
|
|
130
|
+
|
|
131
|
+
See [Contracts Documentation](docs/contracts.md) for full details.
|
|
132
|
+
|
|
110
133
|
## Context Management
|
|
111
134
|
|
|
112
135
|
kspec maintains context that survives AI context compression:
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -665,6 +665,7 @@ WORKFLOW (do this autonomously):
|
|
|
665
665
|
- Constraints
|
|
666
666
|
- High-level design
|
|
667
667
|
- Acceptance criteria
|
|
668
|
+
- Contract (JSON block with output_files and checks)
|
|
668
669
|
4. Create spec-lite.md (CRITICAL - under 500 words):
|
|
669
670
|
- Concise version for context retention after compression
|
|
670
671
|
- Key requirements only
|
|
@@ -759,6 +760,8 @@ When all tasks complete: Run \`/agent swap kspec-verify\` or \`kspec verify\` to
|
|
|
759
760
|
prompt: `You are the kspec verifier.
|
|
760
761
|
|
|
761
762
|
FIRST: Read .kspec/CONTEXT.md for current spec and progress.
|
|
763
|
+
If contract validation results are provided, verify they match your observations.
|
|
764
|
+
|
|
762
765
|
|
|
763
766
|
Based on what you're asked to verify:
|
|
764
767
|
|
|
@@ -870,6 +873,95 @@ IMPORTANT:
|
|
|
870
873
|
}
|
|
871
874
|
};
|
|
872
875
|
|
|
876
|
+
function validateContract(folder) {
|
|
877
|
+
const specFile = path.join(folder, 'spec.md');
|
|
878
|
+
if (!fs.existsSync(specFile)) return { success: true, checks: [], errors: [] };
|
|
879
|
+
|
|
880
|
+
const content = fs.readFileSync(specFile, 'utf8');
|
|
881
|
+
// Extract JSON block from ## Contract section
|
|
882
|
+
const match = content.match(/## Contract\s+```json\n([\s\S]*?)\n```/);
|
|
883
|
+
|
|
884
|
+
if (!match) return { success: true, checks: [], errors: [] };
|
|
885
|
+
|
|
886
|
+
let contract;
|
|
887
|
+
try {
|
|
888
|
+
// Strip comments to allow user annotations
|
|
889
|
+
const jsonStr = match[1].replace(/\/\/.*$/gm, '');
|
|
890
|
+
contract = JSON.parse(jsonStr);
|
|
891
|
+
} catch (e) {
|
|
892
|
+
return { success: false, checks: [], errors: ['Invalid JSON in Contract section'] };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const errors = [];
|
|
896
|
+
const checks = [];
|
|
897
|
+
|
|
898
|
+
// Check API Schema
|
|
899
|
+
if (contract.api_schema) {
|
|
900
|
+
if (contract.api_schema.file) {
|
|
901
|
+
if (!fs.existsSync(contract.api_schema.file)) {
|
|
902
|
+
errors.push(`Missing API Schema file: ${contract.api_schema.file}`);
|
|
903
|
+
} else {
|
|
904
|
+
checks.push(`API Schema exists: ${contract.api_schema.file}`);
|
|
905
|
+
// Basic parse check if JSON/YAML
|
|
906
|
+
if (contract.api_schema.file.endsWith('.json')) {
|
|
907
|
+
try {
|
|
908
|
+
JSON.parse(fs.readFileSync(contract.api_schema.file, 'utf8'));
|
|
909
|
+
checks.push(`API Schema is valid JSON`);
|
|
910
|
+
} catch {
|
|
911
|
+
errors.push(`API Schema is invalid JSON`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Check output files
|
|
919
|
+
if (contract.output_files) {
|
|
920
|
+
for (const file of contract.output_files) {
|
|
921
|
+
if (!fs.existsSync(file)) {
|
|
922
|
+
errors.push(`Missing required file: ${file}`);
|
|
923
|
+
} else {
|
|
924
|
+
checks.push(`File exists: ${file}`);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Check custom checks
|
|
930
|
+
if (contract.checks) {
|
|
931
|
+
for (const check of contract.checks) {
|
|
932
|
+
if (!check.file || !check.type || !check.text) continue;
|
|
933
|
+
|
|
934
|
+
const filePath = check.file;
|
|
935
|
+
if (!fs.existsSync(filePath)) {
|
|
936
|
+
errors.push(`Check failed: File not found ${filePath}`);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
941
|
+
|
|
942
|
+
if (check.type === 'contains') {
|
|
943
|
+
if (!fileContent.includes(check.text)) {
|
|
944
|
+
errors.push(`Check failed: ${filePath} should contain "${check.text}"`);
|
|
945
|
+
} else {
|
|
946
|
+
checks.push(`Content match: ${filePath} contains text`);
|
|
947
|
+
}
|
|
948
|
+
} else if (check.type === 'not_contains') {
|
|
949
|
+
if (fileContent.includes(check.text)) {
|
|
950
|
+
errors.push(`Check failed: ${filePath} should NOT contain "${check.text}"`);
|
|
951
|
+
} else {
|
|
952
|
+
checks.push(`Content check: ${filePath} does not contain text`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
return {
|
|
959
|
+
success: errors.length === 0,
|
|
960
|
+
errors,
|
|
961
|
+
checks
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
873
965
|
// Commands
|
|
874
966
|
const commands = {
|
|
875
967
|
async init() {
|
|
@@ -1287,8 +1379,21 @@ NEVER delete .kiro or .kspec folders.`, 'kspec-build');
|
|
|
1287
1379
|
log(`Verifying implementation: ${folder}`);
|
|
1288
1380
|
if (stats) log(`Tasks: ${stats.done}/${stats.total}`);
|
|
1289
1381
|
|
|
1382
|
+
// Verify contract if exists
|
|
1383
|
+
console.log('Validating contract...');
|
|
1384
|
+
const contractResult = validateContract(folder);
|
|
1385
|
+
if (!contractResult.success) {
|
|
1386
|
+
console.log('\n❌ Contract checks failed:');
|
|
1387
|
+
contractResult.errors.forEach(e => console.log(` - ${e}`));
|
|
1388
|
+
} else if (contractResult.checks.length > 0) {
|
|
1389
|
+
console.log('✅ Contract checks passed');
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1290
1392
|
await chat(`Verify implementation for ${folder}:
|
|
1291
1393
|
|
|
1394
|
+
CONTRACT VALIDATION RESULTS:
|
|
1395
|
+
${JSON.stringify(contractResult, null, 2)}
|
|
1396
|
+
|
|
1292
1397
|
1. Read spec.md - list all requirements
|
|
1293
1398
|
2. Read tasks.md - check all marked [x]
|
|
1294
1399
|
3. Check codebase - does implementation match spec?
|
|
@@ -1299,6 +1404,7 @@ Report:
|
|
|
1299
1404
|
- Requirements: X/Y implemented
|
|
1300
1405
|
- Tasks: X/Y completed
|
|
1301
1406
|
- Tests: PASS/FAIL
|
|
1407
|
+
- Contract: ${contractResult.success ? 'PASS' : 'FAIL'}
|
|
1302
1408
|
- Gaps: [list any]`, 'kspec-verify');
|
|
1303
1409
|
|
|
1304
1410
|
console.log('\nIf verification passed:');
|
|
@@ -1602,4 +1708,4 @@ async function run(args) {
|
|
|
1602
1708
|
}
|
|
1603
1709
|
}
|
|
1604
1710
|
|
|
1605
|
-
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug, isSpecStale };
|
|
1711
|
+
module.exports = { run, commands, loadConfig, detectCli, requireCli, agentTemplates, getTaskStats, refreshContext, getCurrentSpec, getCurrentTask, checkForUpdates, compareVersions, hasAtlassianMcp, getMcpConfig, slugify, generateSlug, isSpecStale, validateContract };
|