@gregiteen/ssss-cli 0.9.5 → 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 +31 -0
- package/VERSION +1 -1
- package/conformance/reference-bundle.ucw.json +1 -1
- package/package.json +1 -1
- package/scripts/cmd-new.mjs +5 -3
- package/scripts/cmd-registry.mjs +1 -1
- package/scripts/conformance.mjs +59 -0
- package/src/frontmatter.mjs +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ 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
|
+
|
|
28
|
+
## [0.9.6] - 2026-09-25
|
|
29
|
+
### Changed
|
|
30
|
+
- The push skill describes this repo's real release path. `/push` is always a
|
|
31
|
+
full release (bump, preflight, commit, push, tag, npm publish, verify),
|
|
32
|
+
including for docs-only changes. The droplet and `production`-branch deploy
|
|
33
|
+
section, which came from another repo, is gone, and files are staged by name
|
|
34
|
+
instead of `git add .`.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- `skills/push/scripts/verify-publish.mjs` waits out npm's propagation delay,
|
|
38
|
+
then confirms the published `gitHead` matches the release tag and that
|
|
39
|
+
`latest` points at the new version.
|
|
40
|
+
|
|
10
41
|
## [0.9.5] - 2026-09-25
|
|
11
42
|
### Changed
|
|
12
43
|
- The packaged CHANGELOG now lists the 0.9.4 behavior changes that hosts should
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
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.
|
|
109
|
+
"exporter": "@gregiteen/ssss-cli@0.9.7"
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
112
|
"files": [
|
package/package.json
CHANGED
package/scripts/cmd-new.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
\`\`\`
|
package/scripts/cmd-registry.mjs
CHANGED
|
@@ -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.
|
|
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];
|
package/scripts/conformance.mjs
CHANGED
|
@@ -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',
|
package/src/frontmatter.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|