@gregiteen/ssss-cli 0.9.6 → 0.9.7

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/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.7] - 2026-09-27
11
+ ### Fixed
12
+ - Frontmatter kept number-like strings as text only until the next patch. The
13
+ serializer wrote `"07003"` unquoted and the parser read any all-digit value
14
+ as an integer, so a zip code or license number with a leading zero lost it
15
+ (`07003` became `7003`) once any other field of the document was patched,
16
+ and YAML 1.1 readers such as PyYAML read the unquoted value as octal
17
+ (`07003` → 3587).
18
+
19
+ ### Behavior changes hosts should check
20
+ - Strings that would read back as a number, boolean or null (`"07003"`,
21
+ `"00123"`, `"1.10"`, `"true"`, `"null"`) are now written double-quoted.
22
+ - An unquoted scalar with a leading zero (`zip: 07003`) now parses as the
23
+ string `"07003"`, not an integer; plain integers (`0`, `42`, `-5`) are
24
+ unchanged. Documents already rewritten without the zero cannot be recovered
25
+ by the library.
26
+ - New conformance check: number-like strings survive repeated patches.
27
+
10
28
  ## [0.9.6] - 2026-09-25
11
29
  ### Changed
12
30
  - The push skill describes this repo's real release path. `/push` is always a
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.9.6
1
+ 0.9.7
@@ -106,7 +106,7 @@
106
106
  "file_count": 14,
107
107
  "provenance": {
108
108
  "content_hash": "sha256:7febd673938a3dc04c068e0bf08097a9783c09e612606d2df9a4db8dcb8907f5",
109
- "exporter": "@gregiteen/ssss-cli@0.9.6"
109
+ "exporter": "@gregiteen/ssss-cli@0.9.7"
110
110
  }
111
111
  },
112
112
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gregiteen/ssss-cli",
3
- "version": "0.9.6",
3
+ "version": "0.9.7",
4
4
  "description": "CLI tools and the reference engine for the Structured Semantic Syntax System",
5
5
  "main": "src/engine.mjs",
6
6
  "exports": {
@@ -121,7 +121,9 @@ export async function run(argv) {
121
121
  }
122
122
  if (withTR) {
123
123
  log('\nInitializing Total Recall memory vault…');
124
- tryRun('npx', ['-p', 'total-recall-brain', 'total-recall', 'init'], root);
124
+ if (!tryRun('npx', ['-p', 'total-recall-brain', 'total-recall', 'init', '--project'], root)) {
125
+ die('scaffolded and installed, but project Total Recall init failed. Run `npx total-recall init --project` in the new directory.', 1);
126
+ }
125
127
  }
126
128
  log('\nVerifying the toolchain (npm test)…');
127
129
  if (!tryRun('npm', ['test'], root)) {
@@ -135,7 +137,7 @@ export async function run(argv) {
135
137
  console.log(` cd ${path.relative(process.cwd(), root) || '.'}`);
136
138
  if (!flags.install) {
137
139
  console.log(' npm install');
138
- if (withTR) console.log(' npx -p total-recall-brain total-recall init # seed the memory vault');
140
+ if (withTR) console.log(' npx -p total-recall-brain total-recall init --project # seed this project memory vault');
139
141
  }
140
142
  console.log(' npm test # replays the canonical fixtures + round-trips the vault');
141
143
  console.log(' npx ssss export vault --profile sale --out dist/bundle.ucw.json');
@@ -226,7 +228,7 @@ ${withTR ? `
226
228
  This project is also wired for [Total Recall](https://github.com/gregiteen/total-recall):
227
229
 
228
230
  \`\`\`bash
229
- npx -p total-recall-brain total-recall init # seed .agent/skills/total-recall
231
+ npx -p total-recall-brain total-recall init --project # seed this project memory vault
230
232
  npx total-recall remember fact "..." # persist a fact
231
233
  npx total-recall recall "..." # semantic recall
232
234
  \`\`\`
@@ -6,7 +6,7 @@ import { parseArgs, die, wantsHelp } from './lib/cli.mjs';
6
6
 
7
7
  const HELP = 'Usage: ssss registry compose|lock|verify [--extension <file> ...] [--lock <file>] [--out <file>]';
8
8
  const read = (file) => JSON.parse(fs.readFileSync(path.resolve(file), 'utf8'));
9
- function serializable(set) { return { primitives: Object.fromEntries(set.primitives), aliases: Object.fromEntries(set.aliases), extension_versions: Object.fromEntries(set.extensionVersions) }; }
9
+ function serializable(set) { return { primitives: Object.fromEntries(set.types), aliases: Object.fromEntries(set.aliases), extension_versions: Object.fromEntries(set.extensionVersions) }; }
10
10
  export async function run(argv) {
11
11
  if (wantsHelp(argv)) { console.log(HELP); return; }
12
12
  const verb = argv[0];
@@ -404,6 +404,29 @@ async function runOperationContractRegressionConformance() {
404
404
  patchRes.success === true && Array.isArray(patchedData.triggers) && patchedData.triggers[0]?.cron === '0 8 * * *',
405
405
  patched);
406
406
 
407
+ const codeRes = await engine.processOperation({
408
+ type: 'patch',
409
+ idempotency_key: 'leading-zero-patch',
410
+ workspace_id: 'ws-reg',
411
+ path: workflowPath,
412
+ actor: { role: 'system' },
413
+ patches: { zip: '07003', license: '00123', flag: 'true' },
414
+ }, vault);
415
+ await engine.processOperation({
416
+ type: 'patch',
417
+ idempotency_key: 'leading-zero-repatch',
418
+ workspace_id: 'ws-reg',
419
+ path: workflowPath,
420
+ actor: { role: 'system' },
421
+ patches: { description: 'A later, unrelated patch.' },
422
+ }, vault);
423
+ const codeText = fs.readFileSync(path.join(vault, workflowPath), 'utf8');
424
+ const codeData = parseDocument(codeText).data;
425
+ check('number-like strings (zip 07003, license 00123, "true") survive repeated patches',
426
+ codeRes.success === true && codeData.zip === '07003' && codeData.license === '00123'
427
+ && codeData.flag === 'true' && codeText.includes('zip: "07003"'),
428
+ codeText);
429
+
407
430
  const leaseTarget = 'rules/leased.md';
408
431
  const leasePathKey = crypto.createHash('sha256').update(leaseTarget).digest('hex');
409
432
  const leaseDir = path.join(leaseStore, 'ws-reg');
@@ -728,6 +751,42 @@ async function runCliSmokeConformance() {
728
751
  typeof lockOut.integrity === 'string' && lockOut.integrity.startsWith('sha256:'),
729
752
  JSON.stringify(lockOut));
730
753
 
754
+ const composed = JSON.parse(execFileSync('node', [
755
+ path.join(__dirname, 'ssss.mjs'),
756
+ 'registry', 'compose',
757
+ '--extension', extensionPath,
758
+ ], { encoding: 'utf8' }));
759
+ check('ssss registry compose includes core and extension primitives',
760
+ !!composed.primitives.rule && composed.aliases['ssss:rule'] === 'rule' &&
761
+ composed.primitives[primitiveOut.primitive_id]?.registry === 'acme',
762
+ JSON.stringify(composed));
763
+
764
+ const fakeBin = path.join(tmp, 'bin');
765
+ const calls = path.join(tmp, 'install-calls.txt');
766
+ fs.mkdirSync(fakeBin);
767
+ for (const command of ['npm', 'npx']) {
768
+ const executable = path.join(fakeBin, command);
769
+ fs.writeFileSync(executable, '#!/bin/sh\nprintf "%s|%s|%s\\n" "$PWD" "$0" "$*" >> "$SSSS_TEST_CALLS"\n');
770
+ fs.chmodSync(executable, 0o755);
771
+ }
772
+ const trStarter = path.join(tmp, 'with-total-recall');
773
+ execFileSync('node', [path.join(__dirname, 'ssss.mjs'), 'new', trStarter,
774
+ '--with-total-recall', '--install', '--no-git'], {
775
+ stdio: 'ignore',
776
+ env: { ...process.env, PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`, SSSS_TEST_CALLS: calls },
777
+ });
778
+ const installCalls = fs.readFileSync(calls, 'utf8').trim().split('\n');
779
+ const normalizedCalls = installCalls.map((line) => {
780
+ const [cwd, executable, args] = line.split('|');
781
+ return `${fs.realpathSync(cwd)}|${path.basename(executable)}|${args}`;
782
+ });
783
+ const realStarter = fs.realpathSync(trStarter);
784
+ check('ssss new initializes only the new project Total Recall brain',
785
+ normalizedCalls.some((line) => line === `${realStarter}|npx|-p total-recall-brain total-recall init --project`) &&
786
+ normalizedCalls.some((line) => line === `${realStarter}|npm|install --no-audit --no-fund`) &&
787
+ normalizedCalls.some((line) => line === `${realStarter}|npm|test`),
788
+ JSON.stringify(installCalls));
789
+
731
790
  const adapterOut = JSON.parse(execFileSync('node', [
732
791
  path.join(__dirname, 'ssss.mjs'),
733
792
  'adapter', 'conformance',
@@ -36,7 +36,8 @@ function coerce(raw) {
36
36
  if (v === 'true') return true;
37
37
  if (v === 'false') return false;
38
38
  if (v === 'null' || v === '~') return null;
39
- if (/^-?\d+$/.test(v)) return parseInt(v, 10);
39
+ // A leading zero (zip 07003, license 00123) marks a code, not a number.
40
+ if (/^-?(0|[1-9]\d*)$/.test(v)) return parseInt(v, 10);
40
41
  if (/^-?\d*\.\d+$/.test(v)) return parseFloat(v);
41
42
  if (v.startsWith('[') && v.endsWith(']')) {
42
43
  const inner = v.slice(1, -1).trim();
@@ -200,12 +201,18 @@ export function parseDocument(content) {
200
201
  return { data: parseFrontmatter(fm), body };
201
202
  }
202
203
 
204
+ const LOOKS_TYPED =
205
+ /^(?:[-+]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?|[-+]?\.\d+|0[xXoObB][0-9a-fA-F_]+|true|false|yes|no|on|off|null|~|\.inf|\.nan)$/i;
206
+
203
207
  function serializeValue(v) {
204
208
  if (v === null) return 'null';
205
209
  if (typeof v === 'boolean' || typeof v === 'number') return String(v);
206
210
  if (Array.isArray(v)) return `[${v.map((x) => serializeValue(x)).join(', ')}]`;
207
211
  const s = String(v);
208
212
  if (s === '' || /[:#\[\]{}"'\\\n\r\t|>]/.test(s) || /^\s|\s$/.test(s)) return JSON.stringify(s);
213
+ // A string that would read back as a number, boolean or null stays quoted,
214
+ // so "07003" and "true" survive a round trip (here and in YAML 1.1 readers).
215
+ if (LOOKS_TYPED.test(s)) return JSON.stringify(s);
209
216
  return s;
210
217
  }
211
218