@aikdna/kdna-studio-cli 0.6.4 → 0.6.5

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.
@@ -852,6 +852,18 @@ function cmdImport(args) {
852
852
  return;
853
853
  }
854
854
 
855
+ // Detect binary content: try reading first 4KB as utf8; if it contains
856
+ // null bytes or too many non-printable chars, it's likely binary.
857
+ const buf = fs.readFileSync(filePath);
858
+ if (buf.length === 0) { console.warn(`Empty file: ${name}`); skipped++; return; }
859
+ const sample = buf.subarray(0, Math.min(buf.length, 4096));
860
+ const nulls = sample.filter(b => b === 0).length;
861
+ const nonPrintable = sample.filter(b => b !== 0 && (b < 0x20 || b === 0x7F) && b !== 0x0A && b !== 0x0D && b !== 0x09).length;
862
+ if (nulls > 0 || nonPrintable > sample.length * 0.1) {
863
+ console.warn(`Binary or non-text file skipped: ${name}`);
864
+ skipped++;
865
+ return;
866
+ }
855
867
  let content;
856
868
  try { content = fs.readFileSync(filePath, 'utf8'); } catch { console.warn(`Cannot read: ${name}`); skipped++; return; }
857
869
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-studio-cli",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
4
4
  "description": "Official KDNA Studio command-line authoring entry for creating, reviewing, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -15,7 +15,9 @@
15
15
  "scripts": {
16
16
  "lint": "node --check bin/kdna-studio.js && node --check tests/cli.test.js",
17
17
  "test": "node --test tests/*.test.js",
18
- "test:all": "npm run lint && npm test"
18
+ "test:all": "npm run lint && npm test",
19
+ "release:check": "node scripts/release-check.js",
20
+ "prepublishOnly": "npm test && npm run release:check"
19
21
  },
20
22
  "keywords": [
21
23
  "kdna",
@@ -46,4 +48,4 @@
46
48
  "devDependencies": {
47
49
  "@aikdna/kdna-core": "^0.12.5"
48
50
  }
49
- }
51
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * e2e-export-completeness.test.js
3
+ * Verifies all card types survive add → approve → export → validate → load.
4
+ * Catches regressions like: stances hardcoded to [], pattern cards dropped during compile.
5
+ */
6
+ const { test } = require('node:test');
7
+ const assert = require('node:assert/strict');
8
+ const { spawnSync } = require('node:child_process');
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+ const os = require('node:os');
12
+
13
+ const studioBin = path.join(__dirname, '..', 'bin', 'kdna-studio.js');
14
+
15
+ function run(args, opts = {}) {
16
+ return spawnSync(process.execPath, [studioBin, ...args], {
17
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
18
+ env: { ...process.env, ...opts.env },
19
+ });
20
+ }
21
+
22
+ function runKdna(args) {
23
+ return spawnSync('kdna', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
24
+ }
25
+
26
+ function tmpDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-e2e-export-')); }
27
+
28
+ function addCard(projectDir, type, fields) {
29
+ const args = ['card', 'add', projectDir, type];
30
+ for (const [k, v] of Object.entries(fields)) args.push('--field', `${k}=${v}`);
31
+ return run(args);
32
+ }
33
+
34
+ test('e2e: pattern → approve → export → patterns in payload', () => {
35
+ const tmp = tmpDir();
36
+ try {
37
+ const pDir = path.join(tmp, 'p');
38
+ run(['create', pDir, '--name', '@test/e2e-pattern']);
39
+ addCard(pDir, 'axiom', {
40
+ one_sentence: 'AX', full_statement: 'A complete and testable explanation for the agent.',
41
+ why: 'Without this axiom the agent would fail.',
42
+ applies_when: '["x"]', does_not_apply_when: '["y"]', failure_risk: 'g',
43
+ confidence: 'high', evidence_type: 'practice',
44
+ });
45
+ addCard(pDir, 'pattern', {
46
+ type: 'test_pattern', name: 'Test Pattern', one_sentence: 'A test pattern.',
47
+ what_it_looks_like: 'It looks like X.', how_to_fix: 'Fix by doing Y.', failure_risk: 'low',
48
+ });
49
+ const approveR = run(['card', 'approve', pDir, '--all', '--by', 'me', '--statement', 'ok']);
50
+ assert.equal(approveR.status, 0, approveR.stderr);
51
+ const exportR = run(['export', pDir, '--format', 'v1', '--out', path.join(tmp, 'out.kdna')]);
52
+ assert.equal(exportR.status, 0, exportR.stderr);
53
+ runKdna(['unpack', path.join(tmp, 'out.kdna'), path.join(tmp, 'unpacked')]);
54
+ const payload = JSON.parse(fs.readFileSync(path.join(tmp, 'unpacked', 'payload.kdnab'), 'utf8'));
55
+ const patternCards = (payload.patterns || []).filter(p => p.type === 'test_pattern');
56
+ assert.ok(patternCards.length > 0, `Expected >0 pattern cards in payload patterns, got ${patternCards.length}`);
57
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
58
+ });
59
+
60
+ test('e2e: stances → approve → export → stances in payload', () => {
61
+ const tmp = tmpDir();
62
+ try {
63
+ const pDir = path.join(tmp, 'p');
64
+ run(['create', pDir, '--name', '@test/e2e-stance']);
65
+ addCard(pDir, 'axiom', {
66
+ one_sentence: 'AX', full_statement: 'A complete and testable explanation for the agent.',
67
+ why: 'Without this axiom the agent would fail.',
68
+ applies_when: '["x"]', does_not_apply_when: '["y"]', failure_risk: 'g',
69
+ confidence: 'high', evidence_type: 'practice',
70
+ });
71
+ addCard(pDir, 'stance', { statement: 'Stance one: Always diagnose before prescribing.' });
72
+ addCard(pDir, 'stance', { statement: 'Stance two: Trust burns slowly and is lost quickly.' });
73
+ run(['card', 'approve', pDir, '--all', '--by', 'me', '--statement', 'ok']);
74
+ run(['export', pDir, '--format', 'v1', '--out', path.join(tmp, 'out.kdna')]);
75
+ runKdna(['unpack', path.join(tmp, 'out.kdna'), path.join(tmp, 'unpacked')]);
76
+ const payload = JSON.parse(fs.readFileSync(path.join(tmp, 'unpacked', 'payload.kdnab'), 'utf8'));
77
+ assert.equal((payload.core?.stances || []).length, 2, 'Expected 2 stances in payload');
78
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
79
+ });
80
+
81
+ test('e2e: missing required fields reports ALL fields at once', () => {
82
+ const tmp = tmpDir();
83
+ try {
84
+ const pDir = path.join(tmp, 'p');
85
+ run(['create', pDir, '--name', '@test/e2e-completeness']);
86
+ const r = addCard(pDir, 'axiom', { one_sentence: 'test' });
87
+ assert.notEqual(r.status, 0);
88
+ assert.ok(r.stderr.includes('full_statement'), 'must mention full_statement');
89
+ assert.ok(r.stderr.includes('why'), 'must mention why');
90
+ assert.ok(r.stderr.includes('applies_when'), 'must mention applies_when');
91
+ assert.ok(r.stderr.includes('confidence'), 'must mention confidence');
92
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
93
+ });