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.
Files changed (3) hide show
  1. package/README.md +23 -0
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kspec",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Spec-driven development workflow for Kiro CLI",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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 };