mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-18 14:53:05 +08:00
Add preview pack smoke gate
This commit is contained in:
@@ -16,6 +16,7 @@ surfaces, or posting announcements.
|
|||||||
| `docs/architecture/harness-adapter-compliance.md` | Adapter matrix and scorecard | Verified by `npm run harness:adapters -- --check` |
|
| `docs/architecture/harness-adapter-compliance.md` | Adapter matrix and scorecard | Verified by `npm run harness:adapters -- --check` |
|
||||||
| `docs/architecture/observability-readiness.md` | Local operator-readiness gate | Verified by `npm run observability:ready` |
|
| `docs/architecture/observability-readiness.md` | Local operator-readiness gate | Verified by `npm run observability:ready` |
|
||||||
| `docs/architecture/progress-sync-contract.md` | GitHub, Linear, handoff, roadmap, and work-item sync boundary | Checked by `node scripts/platform-audit.js --format json --allow-untracked docs/drafts/` |
|
| `docs/architecture/progress-sync-contract.md` | GitHub, Linear, handoff, roadmap, and work-item sync boundary | Checked by `node scripts/platform-audit.js --format json --allow-untracked docs/drafts/` |
|
||||||
|
| `scripts/preview-pack-smoke.js` | Deterministic preview-pack smoke gate | Verified by `npm run preview-pack:smoke` |
|
||||||
| `docs/releases/2.0.0-rc.1/release-notes.md` | GitHub release copy source | Must be refreshed with final live release/package/plugin URLs before publication |
|
| `docs/releases/2.0.0-rc.1/release-notes.md` | GitHub release copy source | Must be refreshed with final live release/package/plugin URLs before publication |
|
||||||
| `docs/releases/2.0.0-rc.1/quickstart.md` | Clone-to-first-workflow path | Covers clone, install, verify, first skill, and harness switch |
|
| `docs/releases/2.0.0-rc.1/quickstart.md` | Clone-to-first-workflow path | Covers clone, install, verify, first skill, and harness switch |
|
||||||
| `docs/releases/2.0.0-rc.1/launch-checklist.md` | Operator launch checklist | Must remain approval-gated for release, package, plugin, and announcement actions |
|
| `docs/releases/2.0.0-rc.1/launch-checklist.md` | Operator launch checklist | Must remain approval-gated for release, package, plugin, and announcement actions |
|
||||||
@@ -69,6 +70,7 @@ Run these from the exact release commit before publication:
|
|||||||
```bash
|
```bash
|
||||||
git status --short --branch
|
git status --short --branch
|
||||||
node scripts/platform-audit.js --format json --allow-untracked docs/drafts/
|
node scripts/platform-audit.js --format json --allow-untracked docs/drafts/
|
||||||
|
npm run preview-pack:smoke
|
||||||
npm run harness:adapters -- --check
|
npm run harness:adapters -- --check
|
||||||
npm run harness:audit -- --format json
|
npm run harness:audit -- --format json
|
||||||
npm run observability:ready
|
npm run observability:ready
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ Record the exact commit SHA and command output before any publication action:
|
|||||||
| Evidence | Command | Required result | Recorded output |
|
| Evidence | Command | Required result | Recorded output |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Clean release branch | `git status --short --branch` | On intended release commit; no unrelated files | Pending final strict clean-checkout release pass; `publication-evidence-2026-05-17.md` records current `main` with unrelated untracked `docs/drafts/` |
|
| Clean release branch | `git status --short --branch` | On intended release commit; no unrelated files | Pending final strict clean-checkout release pass; `publication-evidence-2026-05-17.md` records current `main` with unrelated untracked `docs/drafts/` |
|
||||||
|
| Preview-pack smoke | `npm run preview-pack:smoke` | Preview pack artifacts, Hermes boundary, final verification command list, and publication blockers pass | Pending final strict clean-checkout release pass; deterministic smoke gate is in-tree |
|
||||||
| Harness audit | `npm run harness:audit -- --format json` | 70/70 passing | `publication-evidence-2026-05-17.md`: 70/70 |
|
| Harness audit | `npm run harness:audit -- --format json` | 70/70 passing | `publication-evidence-2026-05-17.md`: 70/70 |
|
||||||
| Adapter scorecard | `npm run harness:adapters -- --check` | PASS | `publication-evidence-2026-05-16.md`: PASS, 11 adapters |
|
| Adapter scorecard | `npm run harness:adapters -- --check` | PASS | `publication-evidence-2026-05-16.md`: PASS, 11 adapters |
|
||||||
| Observability readiness | `npm run observability:ready` | 21/21 passing | `publication-evidence-2026-05-17.md`: 21/21, ready yes |
|
| Observability readiness | `npm run observability:ready` | 21/21 passing | `publication-evidence-2026-05-17.md`: 21/21, ready yes |
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
"scripts/observability-readiness.js",
|
"scripts/observability-readiness.js",
|
||||||
"scripts/operator-readiness-dashboard.js",
|
"scripts/operator-readiness-dashboard.js",
|
||||||
"scripts/platform-audit.js",
|
"scripts/platform-audit.js",
|
||||||
|
"scripts/preview-pack-smoke.js",
|
||||||
"scripts/hooks/",
|
"scripts/hooks/",
|
||||||
"scripts/install-apply.js",
|
"scripts/install-apply.js",
|
||||||
"scripts/install-plan.js",
|
"scripts/install-plan.js",
|
||||||
@@ -300,6 +301,7 @@
|
|||||||
"harness:audit": "node scripts/harness-audit.js",
|
"harness:audit": "node scripts/harness-audit.js",
|
||||||
"observability:ready": "node scripts/observability-readiness.js",
|
"observability:ready": "node scripts/observability-readiness.js",
|
||||||
"operator:dashboard": "node scripts/operator-readiness-dashboard.js",
|
"operator:dashboard": "node scripts/operator-readiness-dashboard.js",
|
||||||
|
"preview-pack:smoke": "node scripts/preview-pack-smoke.js",
|
||||||
"platform:audit": "node scripts/platform-audit.js",
|
"platform:audit": "node scripts/platform-audit.js",
|
||||||
"discussion:audit": "node scripts/discussion-audit.js",
|
"discussion:audit": "node scripts/discussion-audit.js",
|
||||||
"security:ioc-scan": "node scripts/ci/scan-supply-chain-iocs.js",
|
"security:ioc-scan": "node scripts/ci/scan-supply-chain-iocs.js",
|
||||||
|
|||||||
@@ -469,6 +469,7 @@ function buildRequirements(rootDir, platformReport) {
|
|||||||
const publicationReadiness = readText(rootDir, 'docs/releases/2.0.0-rc.1/publication-readiness.md');
|
const publicationReadiness = readText(rootDir, 'docs/releases/2.0.0-rc.1/publication-readiness.md');
|
||||||
const namingMatrix = readText(rootDir, 'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md');
|
const namingMatrix = readText(rootDir, 'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md');
|
||||||
const previewManifest = readText(rootDir, 'docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
const previewManifest = readText(rootDir, 'docs/releases/2.0.0-rc.1/preview-pack-manifest.md');
|
||||||
|
const previewPackSmoke = readText(rootDir, 'scripts/preview-pack-smoke.js');
|
||||||
const progressSync = readText(rootDir, 'docs/architecture/progress-sync-contract.md');
|
const progressSync = readText(rootDir, 'docs/architecture/progress-sync-contract.md');
|
||||||
const observabilityReadiness = readText(rootDir, 'docs/architecture/observability-readiness.md');
|
const observabilityReadiness = readText(rootDir, 'docs/architecture/observability-readiness.md');
|
||||||
const stalePrSalvage = readText(rootDir, 'docs/stale-pr-salvage-ledger.md');
|
const stalePrSalvage = readText(rootDir, 'docs/stale-pr-salvage-ledger.md');
|
||||||
@@ -478,6 +479,22 @@ function buildRequirements(rootDir, platformReport) {
|
|||||||
const packageJson = readPackage(rootDir);
|
const packageJson = readPackage(rootDir);
|
||||||
const scripts = packageJson.scripts || {};
|
const scripts = packageJson.scripts || {};
|
||||||
const legacyContext = { stalePrSalvage, legacyInventory, roadmap };
|
const legacyContext = { stalePrSalvage, legacyInventory, roadmap };
|
||||||
|
const previewPackManifestReady = includesAll(previewManifest, [
|
||||||
|
'publication-readiness.md',
|
||||||
|
'release-notes.md',
|
||||||
|
'quickstart.md'
|
||||||
|
]);
|
||||||
|
const previewPackSmokeReady = scripts['preview-pack:smoke'] === 'node scripts/preview-pack-smoke.js'
|
||||||
|
&& fileExists(rootDir, 'scripts/preview-pack-smoke.js')
|
||||||
|
&& includesAll(previewManifest, ['scripts/preview-pack-smoke.js', 'npm run preview-pack:smoke'])
|
||||||
|
&& includesAll(previewPackSmoke, [
|
||||||
|
'ecc.preview-pack-smoke.v1',
|
||||||
|
'preview-pack-artifacts-present',
|
||||||
|
'hermes-boundary-sanitized',
|
||||||
|
'publication-blockers-preserved'
|
||||||
|
]);
|
||||||
|
const hermesArtifactsReady = fileExists(rootDir, 'docs/HERMES-SETUP.md')
|
||||||
|
&& fileExists(rootDir, 'skills/hermes-imports/SKILL.md');
|
||||||
|
|
||||||
const githubLive = !platformReport.github.skipped && platformReport.github.totals.errors === 0;
|
const githubLive = !platformReport.github.skipped && platformReport.github.totals.errors === 0;
|
||||||
const queuesCurrent = githubLive
|
const queuesCurrent = githubLive
|
||||||
@@ -535,23 +552,29 @@ function buildRequirements(rootDir, platformReport) {
|
|||||||
'ecc-preview-pack',
|
'ecc-preview-pack',
|
||||||
'ECC 2.0 preview pack ready',
|
'ECC 2.0 preview pack ready',
|
||||||
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md',
|
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md',
|
||||||
includesAll(previewManifest, ['publication-readiness.md', 'release-notes.md', 'quickstart.md']) ? 'in_progress' : 'not_complete',
|
previewPackManifestReady && previewPackSmokeReady ? 'current' : previewPackManifestReady ? 'in_progress' : 'not_complete',
|
||||||
includesAll(previewManifest, ['publication-readiness.md', 'release-notes.md', 'quickstart.md'])
|
previewPackManifestReady && previewPackSmokeReady
|
||||||
|
? 'preview pack manifest and deterministic smoke gate are in-tree'
|
||||||
|
: previewPackManifestReady
|
||||||
? 'preview pack manifest is in-tree'
|
? 'preview pack manifest is in-tree'
|
||||||
: 'preview pack manifest is incomplete',
|
: 'preview pack manifest is incomplete',
|
||||||
'final clean-checkout release approval and publish evidence still pending'
|
previewPackManifestReady && previewPackSmokeReady
|
||||||
|
? 'repeat clean-checkout preview-pack smoke before publication'
|
||||||
|
: 'final clean-checkout release approval and publish evidence still pending'
|
||||||
),
|
),
|
||||||
buildRequirement(
|
buildRequirement(
|
||||||
'hermes-specialized-skills',
|
'hermes-specialized-skills',
|
||||||
'Include Hermes specialized skills safely',
|
'Include Hermes specialized skills safely',
|
||||||
'docs/HERMES-SETUP.md and skills/hermes-imports/SKILL.md',
|
'docs/HERMES-SETUP.md and skills/hermes-imports/SKILL.md',
|
||||||
fileExists(rootDir, 'docs/HERMES-SETUP.md') && fileExists(rootDir, 'skills/hermes-imports/SKILL.md')
|
hermesArtifactsReady && previewPackSmokeReady ? 'current' : hermesArtifactsReady ? 'in_progress' : 'not_complete',
|
||||||
? 'in_progress'
|
hermesArtifactsReady && previewPackSmokeReady
|
||||||
: 'not_complete',
|
? 'Hermes setup/import artifacts are covered by preview-pack smoke'
|
||||||
fileExists(rootDir, 'docs/HERMES-SETUP.md') && fileExists(rootDir, 'skills/hermes-imports/SKILL.md')
|
: hermesArtifactsReady
|
||||||
? 'Hermes setup and import skill are present'
|
? 'Hermes setup and import skill are present'
|
||||||
: 'Hermes setup/import artifacts missing',
|
: 'Hermes setup/import artifacts missing',
|
||||||
'final preview-pack smoke and release review pending'
|
hermesArtifactsReady && previewPackSmokeReady
|
||||||
|
? 'repeat preview-pack smoke before release review'
|
||||||
|
: 'final preview-pack smoke and release review pending'
|
||||||
),
|
),
|
||||||
buildRequirement(
|
buildRequirement(
|
||||||
'naming-and-plugin-publication',
|
'naming-and-plugin-publication',
|
||||||
|
|||||||
353
scripts/preview-pack-smoke.js
Normal file
353
scripts/preview-pack-smoke.js
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const RELEASE = '2.0.0-rc.1';
|
||||||
|
const RELEASE_DIR = `docs/releases/${RELEASE}`;
|
||||||
|
const SCHEMA_VERSION = 'ecc.preview-pack-smoke.v1';
|
||||||
|
|
||||||
|
const REQUIRED_ARTIFACTS = [
|
||||||
|
'README.md',
|
||||||
|
'docs/HERMES-SETUP.md',
|
||||||
|
'skills/hermes-imports/SKILL.md',
|
||||||
|
'docs/architecture/cross-harness.md',
|
||||||
|
'docs/architecture/harness-adapter-compliance.md',
|
||||||
|
'docs/architecture/observability-readiness.md',
|
||||||
|
'docs/architecture/progress-sync-contract.md',
|
||||||
|
'scripts/preview-pack-smoke.js',
|
||||||
|
`${RELEASE_DIR}/release-notes.md`,
|
||||||
|
`${RELEASE_DIR}/quickstart.md`,
|
||||||
|
`${RELEASE_DIR}/launch-checklist.md`,
|
||||||
|
`${RELEASE_DIR}/publication-readiness.md`,
|
||||||
|
`${RELEASE_DIR}/publication-evidence-2026-05-15.md`,
|
||||||
|
`${RELEASE_DIR}/publication-evidence-2026-05-16.md`,
|
||||||
|
`${RELEASE_DIR}/publication-evidence-2026-05-17.md`,
|
||||||
|
`${RELEASE_DIR}/operator-readiness-dashboard-2026-05-17.md`,
|
||||||
|
`${RELEASE_DIR}/naming-and-publication-matrix.md`,
|
||||||
|
`${RELEASE_DIR}/x-thread.md`,
|
||||||
|
`${RELEASE_DIR}/linkedin-post.md`,
|
||||||
|
`${RELEASE_DIR}/article-outline.md`,
|
||||||
|
`${RELEASE_DIR}/telegram-handoff.md`,
|
||||||
|
`${RELEASE_DIR}/demo-prompts.md`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const REQUIRED_VERIFICATION_COMMANDS = [
|
||||||
|
'git status --short --branch',
|
||||||
|
'node scripts/platform-audit.js --format json --allow-untracked docs/drafts/',
|
||||||
|
'npm run preview-pack:smoke',
|
||||||
|
'npm run harness:adapters -- --check',
|
||||||
|
'npm run harness:audit -- --format json',
|
||||||
|
'npm run observability:ready',
|
||||||
|
'npm run security:ioc-scan',
|
||||||
|
'npm audit --audit-level=moderate',
|
||||||
|
'npm audit signatures',
|
||||||
|
'node tests/docs/ecc2-release-surface.test.js',
|
||||||
|
'node tests/run-all.js',
|
||||||
|
'cd ecc2 && cargo test',
|
||||||
|
];
|
||||||
|
|
||||||
|
const REQUIRED_PUBLICATION_BLOCKERS = [
|
||||||
|
'GitHub prerelease `v2.0.0-rc.1`',
|
||||||
|
'npm `ecc-universal@2.0.0-rc.1`',
|
||||||
|
'Claude plugin tag',
|
||||||
|
'Codex repo-marketplace distribution evidence',
|
||||||
|
'ECC Tools billing/product readiness',
|
||||||
|
];
|
||||||
|
|
||||||
|
const HERMES_BOUNDARY_MARKERS = [
|
||||||
|
'Public Release Candidate Scope',
|
||||||
|
'ECC v2.0.0-rc.1 documents the Hermes surface',
|
||||||
|
'Sanitization Checklist',
|
||||||
|
'Do not ship raw workspace exports',
|
||||||
|
'Output Contract',
|
||||||
|
];
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
console.log([
|
||||||
|
'Usage: node scripts/preview-pack-smoke.js [--format <text|json>] [--root <dir>]',
|
||||||
|
'',
|
||||||
|
'Deterministic smoke gate for the ECC 2.0 rc.1 preview pack.',
|
||||||
|
'',
|
||||||
|
'Options:',
|
||||||
|
' --format <text|json> Output format (default: text)',
|
||||||
|
' --root <dir> Repository root to inspect (default: cwd)',
|
||||||
|
' --help, -h Show this help',
|
||||||
|
].join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readArgValue(args, index, flagName) {
|
||||||
|
const value = args[index + 1];
|
||||||
|
if (!value || value.startsWith('--')) {
|
||||||
|
throw new Error(`${flagName} requires a value`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(argv) {
|
||||||
|
const args = argv.slice(2);
|
||||||
|
const parsed = {
|
||||||
|
format: 'text',
|
||||||
|
help: false,
|
||||||
|
root: path.resolve(process.cwd()),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let index = 0; index < args.length; index += 1) {
|
||||||
|
const arg = args[index];
|
||||||
|
|
||||||
|
if (arg === '--help' || arg === '-h') {
|
||||||
|
parsed.help = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--format') {
|
||||||
|
parsed.format = readArgValue(args, index, arg).toLowerCase();
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.startsWith('--format=')) {
|
||||||
|
parsed.format = arg.slice('--format='.length).toLowerCase();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg === '--root') {
|
||||||
|
parsed.root = path.resolve(readArgValue(args, index, arg));
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.startsWith('--root=')) {
|
||||||
|
parsed.root = path.resolve(arg.slice('--root='.length));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown argument: ${arg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['text', 'json'].includes(parsed.format)) {
|
||||||
|
throw new Error(`Invalid format: ${parsed.format}. Use text or json.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readText(rootDir, relativePath) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(path.join(rootDir, relativePath), 'utf8');
|
||||||
|
} catch (_error) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExists(rootDir, relativePath) {
|
||||||
|
return fs.existsSync(path.join(rootDir, relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeParseJson(text) {
|
||||||
|
if (!text.trim()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(text);
|
||||||
|
} catch (_error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function includesAll(text, needles) {
|
||||||
|
return needles.every(needle => text.includes(needle));
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineNumberForIndex(text, index) {
|
||||||
|
return text.slice(0, index).split('\n').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findForbiddenContent(rootDir, relativePaths) {
|
||||||
|
const offenders = [];
|
||||||
|
const privatePathPattern = /\/Users\/(?!\.\.\.)[A-Za-z0-9._-]+|\/home\/(?!user|runner)[A-Za-z0-9._-]+/g;
|
||||||
|
|
||||||
|
for (const relativePath of relativePaths) {
|
||||||
|
const text = readText(rootDir, relativePath);
|
||||||
|
if (!text) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const match of text.matchAll(privatePathPattern)) {
|
||||||
|
offenders.push({
|
||||||
|
path: relativePath,
|
||||||
|
line: lineNumberForIndex(text, match.index),
|
||||||
|
marker: match[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return offenders;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCheck(id, status, evidence, fix) {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
status,
|
||||||
|
evidence,
|
||||||
|
fix: status === 'pass' ? '' : fix,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildReport(options = {}) {
|
||||||
|
const rootDir = path.resolve(options.root || process.cwd());
|
||||||
|
const packageJson = safeParseJson(readText(rootDir, 'package.json')) || {};
|
||||||
|
const packageScripts = packageJson.scripts || {};
|
||||||
|
const packageFiles = Array.isArray(packageJson.files) ? packageJson.files : [];
|
||||||
|
const manifestPath = `${RELEASE_DIR}/preview-pack-manifest.md`;
|
||||||
|
const manifest = readText(rootDir, manifestPath);
|
||||||
|
const hermesSetup = readText(rootDir, 'docs/HERMES-SETUP.md');
|
||||||
|
const hermesSkill = readText(rootDir, 'skills/hermes-imports/SKILL.md');
|
||||||
|
|
||||||
|
const missingArtifacts = REQUIRED_ARTIFACTS.filter(relativePath => !fileExists(rootDir, relativePath));
|
||||||
|
const unlistedArtifacts = REQUIRED_ARTIFACTS.filter(relativePath => !manifest.includes(`\`${relativePath}\``));
|
||||||
|
const missingCommands = REQUIRED_VERIFICATION_COMMANDS.filter(command => !manifest.includes(command));
|
||||||
|
const missingBlockers = REQUIRED_PUBLICATION_BLOCKERS.filter(blocker => !manifest.includes(blocker));
|
||||||
|
const missingHermesMarkers = HERMES_BOUNDARY_MARKERS.filter(marker => !`${hermesSetup}\n${hermesSkill}`.includes(marker));
|
||||||
|
const forbiddenContent = findForbiddenContent(rootDir, [
|
||||||
|
...REQUIRED_ARTIFACTS,
|
||||||
|
manifestPath,
|
||||||
|
'docs/business/social-launch-copy.md',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const checks = [
|
||||||
|
makeCheck(
|
||||||
|
'preview-pack-script-registered',
|
||||||
|
packageScripts['preview-pack:smoke'] === 'node scripts/preview-pack-smoke.js'
|
||||||
|
&& packageFiles.includes('scripts/preview-pack-smoke.js')
|
||||||
|
&& fileExists(rootDir, 'scripts/preview-pack-smoke.js')
|
||||||
|
? 'pass'
|
||||||
|
: 'fail',
|
||||||
|
'package script and npm package file entry for preview-pack smoke gate',
|
||||||
|
'Add preview-pack:smoke to package scripts and include scripts/preview-pack-smoke.js in package files.'
|
||||||
|
),
|
||||||
|
makeCheck(
|
||||||
|
'preview-pack-artifacts-present',
|
||||||
|
missingArtifacts.length === 0 && unlistedArtifacts.length === 0 ? 'pass' : 'fail',
|
||||||
|
missingArtifacts.length === 0 && unlistedArtifacts.length === 0
|
||||||
|
? `${REQUIRED_ARTIFACTS.length} required artifacts exist and are listed in the manifest`
|
||||||
|
: `missing artifacts: ${missingArtifacts.join(', ') || 'none'}; unlisted artifacts: ${unlistedArtifacts.join(', ') || 'none'}`,
|
||||||
|
'Restore missing preview-pack artifacts and list every required artifact in preview-pack-manifest.md.'
|
||||||
|
),
|
||||||
|
makeCheck(
|
||||||
|
'final-verification-commands-listed',
|
||||||
|
missingCommands.length === 0 ? 'pass' : 'fail',
|
||||||
|
missingCommands.length === 0
|
||||||
|
? `${REQUIRED_VERIFICATION_COMMANDS.length} final verification commands are listed`
|
||||||
|
: `missing commands: ${missingCommands.join('; ')}`,
|
||||||
|
'Add the missing final verification commands to preview-pack-manifest.md.'
|
||||||
|
),
|
||||||
|
makeCheck(
|
||||||
|
'hermes-boundary-sanitized',
|
||||||
|
missingHermesMarkers.length === 0 && forbiddenContent.length === 0 ? 'pass' : 'fail',
|
||||||
|
missingHermesMarkers.length === 0 && forbiddenContent.length === 0
|
||||||
|
? 'Hermes setup and import skill preserve the public sanitization boundary'
|
||||||
|
: `missing markers: ${missingHermesMarkers.join(', ') || 'none'}; forbidden content: ${forbiddenContent.map(item => `${item.path}:${item.line}`).join(', ') || 'none'}`,
|
||||||
|
'Restore Hermes sanitization language and remove private local paths from preview-pack docs.'
|
||||||
|
),
|
||||||
|
makeCheck(
|
||||||
|
'publication-blockers-preserved',
|
||||||
|
missingBlockers.length === 0
|
||||||
|
&& /approval-gated release, package, plugin, and\s+announcement steps/.test(manifest)
|
||||||
|
? 'pass'
|
||||||
|
: 'fail',
|
||||||
|
missingBlockers.length === 0
|
||||||
|
? 'publication remains explicitly approval-gated'
|
||||||
|
: `missing blockers: ${missingBlockers.join(', ')}`,
|
||||||
|
'Keep publication blockers explicit until the live release, package, plugin, and billing surfaces exist.'
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const failed = checks.filter(check => check.status !== 'pass');
|
||||||
|
const digest = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(JSON.stringify(checks.map(check => [check.id, check.status, check.evidence])))
|
||||||
|
.digest('hex')
|
||||||
|
.slice(0, 12);
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema_version: SCHEMA_VERSION,
|
||||||
|
release: RELEASE,
|
||||||
|
ready: failed.length === 0,
|
||||||
|
digest,
|
||||||
|
summary: {
|
||||||
|
passed: checks.length - failed.length,
|
||||||
|
failed: failed.length,
|
||||||
|
total: checks.length,
|
||||||
|
},
|
||||||
|
checks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderText(report) {
|
||||||
|
const lines = [
|
||||||
|
'ECC preview pack smoke',
|
||||||
|
`Release: ${report.release}`,
|
||||||
|
`Ready: ${report.ready ? 'yes' : 'no'}`,
|
||||||
|
`Digest: ${report.digest}`,
|
||||||
|
'',
|
||||||
|
'Checks:',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const check of report.checks) {
|
||||||
|
lines.push(`- ${check.status} ${check.id}: ${check.evidence}`);
|
||||||
|
if (check.fix) {
|
||||||
|
lines.push(` fix: ${check.fix}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`Passed: ${report.summary.passed}`);
|
||||||
|
lines.push(`Failed: ${report.summary.failed}`);
|
||||||
|
|
||||||
|
return `${lines.join('\n')}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
let parsed;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsed = parseArgs(process.argv);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error: ${error.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.help) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const report = buildReport({ root: parsed.root });
|
||||||
|
|
||||||
|
if (parsed.format === 'json') {
|
||||||
|
console.log(JSON.stringify(report, null, 2));
|
||||||
|
} else {
|
||||||
|
process.stdout.write(renderText(report));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report.ready) {
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
REQUIRED_ARTIFACTS,
|
||||||
|
REQUIRED_PUBLICATION_BLOCKERS,
|
||||||
|
REQUIRED_VERIFICATION_COMMANDS,
|
||||||
|
buildReport,
|
||||||
|
parseArgs,
|
||||||
|
renderText,
|
||||||
|
};
|
||||||
@@ -171,6 +171,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
|
|||||||
'docs/HERMES-SETUP.md',
|
'docs/HERMES-SETUP.md',
|
||||||
'skills/hermes-imports/SKILL.md',
|
'skills/hermes-imports/SKILL.md',
|
||||||
'docs/architecture/harness-adapter-compliance.md',
|
'docs/architecture/harness-adapter-compliance.md',
|
||||||
|
'scripts/preview-pack-smoke.js',
|
||||||
'docs/releases/2.0.0-rc.1/publication-readiness.md',
|
'docs/releases/2.0.0-rc.1/publication-readiness.md',
|
||||||
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md',
|
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md',
|
||||||
]) {
|
]) {
|
||||||
@@ -189,6 +190,7 @@ test('preview pack manifest assembles release, Hermes, and publication gates', (
|
|||||||
|
|
||||||
assert.ok(manifest.includes('no raw workspace exports'));
|
assert.ok(manifest.includes('no raw workspace exports'));
|
||||||
assert.ok(manifest.includes('Final Verification Commands'));
|
assert.ok(manifest.includes('Final Verification Commands'));
|
||||||
|
assert.ok(manifest.includes('npm run preview-pack:smoke'));
|
||||||
assert.ok(manifest.includes('Reference-Inspired Adapter Direction'));
|
assert.ok(manifest.includes('Reference-Inspired Adapter Direction'));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -263,6 +265,8 @@ test('publication readiness checklist gates public release actions on evidence',
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.ok(source.includes('publication-evidence-2026-05-15.md'));
|
assert.ok(source.includes('publication-evidence-2026-05-15.md'));
|
||||||
|
assert.ok(source.includes('Preview-pack smoke'));
|
||||||
|
assert.ok(source.includes('npm run preview-pack:smoke'));
|
||||||
assert.ok(may15Evidence.includes('PR #1921'));
|
assert.ok(may15Evidence.includes('PR #1921'));
|
||||||
assert.ok(may15Evidence.includes('PR #1933'));
|
assert.ok(may15Evidence.includes('PR #1933'));
|
||||||
assert.ok(may15Evidence.includes('PR #1934'));
|
assert.ok(may15Evidence.includes('PR #1934'));
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ function buildExpectedPublishPaths(repoRoot) {
|
|||||||
"scripts/observability-readiness.js",
|
"scripts/observability-readiness.js",
|
||||||
"scripts/operator-readiness-dashboard.js",
|
"scripts/operator-readiness-dashboard.js",
|
||||||
"scripts/platform-audit.js",
|
"scripts/platform-audit.js",
|
||||||
|
"scripts/preview-pack-smoke.js",
|
||||||
"scripts/skill-create-output.js",
|
"scripts/skill-create-output.js",
|
||||||
"scripts/repair.js",
|
"scripts/repair.js",
|
||||||
"scripts/harness-adapter-compliance.js",
|
"scripts/harness-adapter-compliance.js",
|
||||||
@@ -129,6 +130,7 @@ function main() {
|
|||||||
"scripts/consult.js",
|
"scripts/consult.js",
|
||||||
"scripts/discussion-audit.js",
|
"scripts/discussion-audit.js",
|
||||||
"scripts/operator-readiness-dashboard.js",
|
"scripts/operator-readiness-dashboard.js",
|
||||||
|
"scripts/preview-pack-smoke.js",
|
||||||
"scripts/work-items.js",
|
"scripts/work-items.js",
|
||||||
"scripts/platform-audit.js",
|
"scripts/platform-audit.js",
|
||||||
".gemini/GEMINI.md",
|
".gemini/GEMINI.md",
|
||||||
|
|||||||
@@ -32,18 +32,26 @@ function seedRepo(rootDir, overrides = {}) {
|
|||||||
files: [
|
files: [
|
||||||
'scripts/observability-readiness.js',
|
'scripts/observability-readiness.js',
|
||||||
'scripts/operator-readiness-dashboard.js',
|
'scripts/operator-readiness-dashboard.js',
|
||||||
'scripts/platform-audit.js'
|
'scripts/platform-audit.js',
|
||||||
|
'scripts/preview-pack-smoke.js'
|
||||||
],
|
],
|
||||||
scripts: {
|
scripts: {
|
||||||
'discussion:audit': 'node scripts/discussion-audit.js',
|
'discussion:audit': 'node scripts/discussion-audit.js',
|
||||||
'observability:ready': 'node scripts/observability-readiness.js',
|
'observability:ready': 'node scripts/observability-readiness.js',
|
||||||
'operator:dashboard': 'node scripts/operator-readiness-dashboard.js',
|
'operator:dashboard': 'node scripts/operator-readiness-dashboard.js',
|
||||||
'platform:audit': 'node scripts/platform-audit.js',
|
'platform:audit': 'node scripts/platform-audit.js',
|
||||||
|
'preview-pack:smoke': 'node scripts/preview-pack-smoke.js',
|
||||||
'security:ioc-scan': 'node scripts/ci/scan-supply-chain-iocs.js',
|
'security:ioc-scan': 'node scripts/ci/scan-supply-chain-iocs.js',
|
||||||
'security:advisory-sources': 'node scripts/ci/supply-chain-advisory-sources.js'
|
'security:advisory-sources': 'node scripts/ci/supply-chain-advisory-sources.js'
|
||||||
}
|
}
|
||||||
}, null, 2),
|
}, null, 2),
|
||||||
'scripts/operator-readiness-dashboard.js': 'operator dashboard generator',
|
'scripts/operator-readiness-dashboard.js': 'operator dashboard generator',
|
||||||
|
'scripts/preview-pack-smoke.js': [
|
||||||
|
'ecc.preview-pack-smoke.v1',
|
||||||
|
'preview-pack-artifacts-present',
|
||||||
|
'hermes-boundary-sanitized',
|
||||||
|
'publication-blockers-preserved'
|
||||||
|
].join('\n'),
|
||||||
'docs/ECC-2.0-GA-ROADMAP.md': [
|
'docs/ECC-2.0-GA-ROADMAP.md': [
|
||||||
'https://linear.app/itomarkets/project/ecc-platform-roadmap-52b328ee03e1',
|
'https://linear.app/itomarkets/project/ecc-platform-roadmap-52b328ee03e1',
|
||||||
'Linear ITO-44 ITO-59',
|
'Linear ITO-44 ITO-59',
|
||||||
@@ -63,7 +71,11 @@ function seedRepo(rootDir, overrides = {}) {
|
|||||||
].join('\n'),
|
].join('\n'),
|
||||||
'docs/releases/2.0.0-rc.1/publication-readiness.md': 'Claude plugin Codex plugin',
|
'docs/releases/2.0.0-rc.1/publication-readiness.md': 'Claude plugin Codex plugin',
|
||||||
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md': 'Claude plugin Codex plugin npm package Publication Paths',
|
'docs/releases/2.0.0-rc.1/naming-and-publication-matrix.md': 'Claude plugin Codex plugin npm package Publication Paths',
|
||||||
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'publication-readiness.md release-notes.md quickstart.md',
|
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': [
|
||||||
|
'publication-readiness.md release-notes.md quickstart.md',
|
||||||
|
'`scripts/preview-pack-smoke.js`',
|
||||||
|
'npm run preview-pack:smoke'
|
||||||
|
].join('\n'),
|
||||||
'docs/releases/2.0.0-rc.1/release-notes.md': 'release notes',
|
'docs/releases/2.0.0-rc.1/release-notes.md': 'release notes',
|
||||||
'docs/releases/2.0.0-rc.1/x-thread.md': 'x thread',
|
'docs/releases/2.0.0-rc.1/x-thread.md': 'x thread',
|
||||||
'docs/releases/2.0.0-rc.1/linkedin-post.md': 'linkedin post',
|
'docs/releases/2.0.0-rc.1/linkedin-post.md': 'linkedin post',
|
||||||
@@ -77,8 +89,8 @@ function seedRepo(rootDir, overrides = {}) {
|
|||||||
'PR queue',
|
'PR queue',
|
||||||
'Not complete'
|
'Not complete'
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
'docs/HERMES-SETUP.md': 'Hermes setup',
|
'docs/HERMES-SETUP.md': 'Hermes setup Public Release Candidate Scope',
|
||||||
'skills/hermes-imports/SKILL.md': 'Hermes imports',
|
'skills/hermes-imports/SKILL.md': 'Hermes imports Sanitization Checklist Do not ship raw workspace exports Output Contract',
|
||||||
'docs/stale-pr-salvage-ledger.md': [
|
'docs/stale-pr-salvage-ledger.md': [
|
||||||
'Remaining Manual-Review Backlog',
|
'Remaining Manual-Review Backlog',
|
||||||
'Linear ITO-55',
|
'Linear ITO-55',
|
||||||
@@ -218,6 +230,18 @@ function runTests() {
|
|||||||
assert.strictEqual(report.ready, false);
|
assert.strictEqual(report.ready, false);
|
||||||
assert.strictEqual(report.publicationReady, false);
|
assert.strictEqual(report.publicationReady, false);
|
||||||
assert.ok(report.requirements.some(item => item.id === 'completion-dashboard' && item.status === 'complete'));
|
assert.ok(report.requirements.some(item => item.id === 'completion-dashboard' && item.status === 'complete'));
|
||||||
|
assert.ok(report.requirements.some(item => (
|
||||||
|
item.id === 'ecc-preview-pack'
|
||||||
|
&& item.status === 'current'
|
||||||
|
&& item.evidence.includes('deterministic smoke gate')
|
||||||
|
&& item.gap === 'repeat clean-checkout preview-pack smoke before publication'
|
||||||
|
)));
|
||||||
|
assert.ok(report.requirements.some(item => (
|
||||||
|
item.id === 'hermes-specialized-skills'
|
||||||
|
&& item.status === 'current'
|
||||||
|
&& item.evidence.includes('covered by preview-pack smoke')
|
||||||
|
&& item.gap === 'repeat preview-pack smoke before release review'
|
||||||
|
)));
|
||||||
assert.ok(report.requirements.some(item => item.id === 'ecc-tools-next-level' && item.status === 'in_progress'));
|
assert.ok(report.requirements.some(item => item.id === 'ecc-tools-next-level' && item.status === 'in_progress'));
|
||||||
assert.ok(report.requirements.some(item => (
|
assert.ok(report.requirements.some(item => (
|
||||||
item.id === 'agentshield-enterprise-iteration'
|
item.id === 'agentshield-enterprise-iteration'
|
||||||
@@ -253,6 +277,8 @@ function runTests() {
|
|||||||
&& item.gap === 'repeat Linear/project status update and local work-items sync after each significant merge batch'
|
&& item.gap === 'repeat Linear/project status update and local work-items sync after each significant merge batch'
|
||||||
)));
|
)));
|
||||||
assert.ok(report.top_actions.some(item => item.id === 'naming-and-plugin-publication'));
|
assert.ok(report.top_actions.some(item => item.id === 'naming-and-plugin-publication'));
|
||||||
|
assert.ok(!report.top_actions.some(item => item.id === 'ecc-preview-pack'));
|
||||||
|
assert.ok(!report.top_actions.some(item => item.id === 'hermes-specialized-skills'));
|
||||||
assert.ok(!report.top_actions.some(item => item.id === 'legacy-salvage'));
|
assert.ok(!report.top_actions.some(item => item.id === 'legacy-salvage'));
|
||||||
assert.ok(!report.top_actions.some(item => item.id === 'linear-roadmap-and-progress'));
|
assert.ok(!report.top_actions.some(item => item.id === 'linear-roadmap-and-progress'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -287,6 +313,45 @@ function runTests() {
|
|||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('preview pack and Hermes gates stay in progress until smoke gate is wired', () => {
|
||||||
|
const rootDir = createTempDir('operator-dashboard-preview-smoke-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedRepo(rootDir, {
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
files: [
|
||||||
|
'scripts/observability-readiness.js',
|
||||||
|
'scripts/operator-readiness-dashboard.js',
|
||||||
|
'scripts/platform-audit.js'
|
||||||
|
],
|
||||||
|
scripts: {
|
||||||
|
'discussion:audit': 'node scripts/discussion-audit.js',
|
||||||
|
'observability:ready': 'node scripts/observability-readiness.js',
|
||||||
|
'operator:dashboard': 'node scripts/operator-readiness-dashboard.js',
|
||||||
|
'platform:audit': 'node scripts/platform-audit.js',
|
||||||
|
'security:ioc-scan': 'node scripts/ci/scan-supply-chain-iocs.js',
|
||||||
|
'security:advisory-sources': 'node scripts/ci/supply-chain-advisory-sources.js'
|
||||||
|
}
|
||||||
|
}, null, 2),
|
||||||
|
'scripts/preview-pack-smoke.js': null,
|
||||||
|
'docs/releases/2.0.0-rc.1/preview-pack-manifest.md': 'publication-readiness.md release-notes.md quickstart.md'
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = buildSeededReport(rootDir);
|
||||||
|
const previewPack = report.requirements.find(item => item.id === 'ecc-preview-pack');
|
||||||
|
const hermes = report.requirements.find(item => item.id === 'hermes-specialized-skills');
|
||||||
|
|
||||||
|
assert.strictEqual(previewPack.status, 'in_progress');
|
||||||
|
assert.strictEqual(previewPack.gap, 'final clean-checkout release approval and publish evidence still pending');
|
||||||
|
assert.strictEqual(hermes.status, 'in_progress');
|
||||||
|
assert.strictEqual(hermes.gap, 'final preview-pack smoke and release review pending');
|
||||||
|
assert.ok(report.top_actions.some(item => item.id === 'ecc-preview-pack'));
|
||||||
|
assert.ok(report.top_actions.some(item => item.id === 'hermes-specialized-skills'));
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('AgentShield enterprise evidence covers export and policy promotion markers', () => {
|
if (test('AgentShield enterprise evidence covers export and policy promotion markers', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
|
|||||||
260
tests/scripts/preview-pack-smoke.test.js
Normal file
260
tests/scripts/preview-pack-smoke.test.js
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const { execFileSync, spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'preview-pack-smoke.js');
|
||||||
|
const {
|
||||||
|
REQUIRED_ARTIFACTS,
|
||||||
|
REQUIRED_PUBLICATION_BLOCKERS,
|
||||||
|
REQUIRED_VERIFICATION_COMMANDS,
|
||||||
|
buildReport,
|
||||||
|
parseArgs,
|
||||||
|
renderText,
|
||||||
|
} = require(SCRIPT);
|
||||||
|
|
||||||
|
const RELEASE_DIR = 'docs/releases/2.0.0-rc.1';
|
||||||
|
|
||||||
|
function createTempDir(prefix) {
|
||||||
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(dirPath) {
|
||||||
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(rootDir, relativePath, content) {
|
||||||
|
const targetPath = path.join(rootDir, relativePath);
|
||||||
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
||||||
|
fs.writeFileSync(targetPath, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function manifestContent() {
|
||||||
|
return [
|
||||||
|
'# ECC v2.0.0-rc.1 Preview Pack Manifest',
|
||||||
|
'',
|
||||||
|
'## Pack Contents',
|
||||||
|
'',
|
||||||
|
'| Artifact | Role | Gate |',
|
||||||
|
'| --- | --- | --- |',
|
||||||
|
...REQUIRED_ARTIFACTS.map(artifact => `| \`${artifact}\` | release artifact | checked |`),
|
||||||
|
'',
|
||||||
|
'## Hermes Skill Boundary',
|
||||||
|
'',
|
||||||
|
'- no raw workspace exports;',
|
||||||
|
'',
|
||||||
|
'## Final Verification Commands',
|
||||||
|
'',
|
||||||
|
'```bash',
|
||||||
|
...REQUIRED_VERIFICATION_COMMANDS,
|
||||||
|
'```',
|
||||||
|
'',
|
||||||
|
'## Publication Blockers',
|
||||||
|
'',
|
||||||
|
...REQUIRED_PUBLICATION_BLOCKERS.map(blocker => `- ${blocker}`),
|
||||||
|
'',
|
||||||
|
'The preview pack is not public without approval-gated release, package, plugin, and announcement steps.',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function seedRepo(rootDir, overrides = {}) {
|
||||||
|
const files = {
|
||||||
|
'package.json': JSON.stringify({
|
||||||
|
files: ['scripts/preview-pack-smoke.js'],
|
||||||
|
scripts: {
|
||||||
|
'preview-pack:smoke': 'node scripts/preview-pack-smoke.js',
|
||||||
|
},
|
||||||
|
}, null, 2),
|
||||||
|
'scripts/preview-pack-smoke.js': 'preview pack smoke script',
|
||||||
|
[`${RELEASE_DIR}/preview-pack-manifest.md`]: manifestContent(),
|
||||||
|
'docs/HERMES-SETUP.md': [
|
||||||
|
'# Hermes Setup',
|
||||||
|
'Public Release Candidate Scope',
|
||||||
|
'ECC v2.0.0-rc.1 documents the Hermes surface',
|
||||||
|
'No raw workspace export is included.',
|
||||||
|
].join('\n'),
|
||||||
|
'skills/hermes-imports/SKILL.md': [
|
||||||
|
'---',
|
||||||
|
'name: hermes-imports',
|
||||||
|
'---',
|
||||||
|
'Sanitization Checklist',
|
||||||
|
'Do not ship raw workspace exports',
|
||||||
|
'Output Contract',
|
||||||
|
].join('\n'),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const artifact of REQUIRED_ARTIFACTS) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(files, artifact)) {
|
||||||
|
files[artifact] = `${artifact} public preview-pack content`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
|
||||||
|
if (content === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
writeFile(rootDir, relativePath, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run(args = [], options = {}) {
|
||||||
|
return execFileSync('node', [SCRIPT, ...args], {
|
||||||
|
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runProcess(args = [], options = {}) {
|
||||||
|
return spawnSync('node', [SCRIPT, ...args], {
|
||||||
|
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(name, fn) {
|
||||||
|
try {
|
||||||
|
fn();
|
||||||
|
console.log(` PASS ${name}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(` FAIL ${name}`);
|
||||||
|
console.log(` Error: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests() {
|
||||||
|
console.log('\n=== Testing preview-pack-smoke.js ===\n');
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
if (test('parseArgs accepts smoke flags and rejects invalid values', () => {
|
||||||
|
const rootDir = createTempDir('preview-pack-smoke-args-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = parseArgs([
|
||||||
|
'node',
|
||||||
|
'script',
|
||||||
|
'--format=json',
|
||||||
|
`--root=${rootDir}`,
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert.strictEqual(parsed.format, 'json');
|
||||||
|
assert.strictEqual(parsed.root, path.resolve(rootDir));
|
||||||
|
assert.throws(() => parseArgs(['node', 'script', '--format', 'xml']), /Invalid format/);
|
||||||
|
assert.throws(() => parseArgs(['node', 'script', '--root']), /--root requires a value/);
|
||||||
|
assert.throws(() => parseArgs(['node', 'script', '--unknown']), /Unknown argument/);
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('seeded release pack passes every smoke check', () => {
|
||||||
|
const rootDir = createTempDir('preview-pack-smoke-pass-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedRepo(rootDir);
|
||||||
|
const report = buildReport({ root: rootDir });
|
||||||
|
|
||||||
|
assert.strictEqual(report.schema_version, 'ecc.preview-pack-smoke.v1');
|
||||||
|
assert.strictEqual(report.ready, true);
|
||||||
|
assert.strictEqual(report.summary.failed, 0);
|
||||||
|
assert.ok(report.checks.every(check => check.status === 'pass'));
|
||||||
|
|
||||||
|
const text = renderText(report);
|
||||||
|
assert.ok(text.includes('Ready: yes'));
|
||||||
|
assert.ok(text.includes('Passed: 5'));
|
||||||
|
assert.ok(text.includes('Failed: 0'));
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('script registration fails closed without package wiring', () => {
|
||||||
|
const rootDir = createTempDir('preview-pack-smoke-package-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedRepo(rootDir, {
|
||||||
|
'package.json': JSON.stringify({ files: [], scripts: {} }, null, 2),
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = buildReport({ root: rootDir });
|
||||||
|
const registration = report.checks.find(check => check.id === 'preview-pack-script-registered');
|
||||||
|
|
||||||
|
assert.strictEqual(report.ready, false);
|
||||||
|
assert.strictEqual(registration.status, 'fail');
|
||||||
|
assert.ok(registration.fix.includes('preview-pack:smoke'));
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('Hermes boundary fails closed on private local paths', () => {
|
||||||
|
const rootDir = createTempDir('preview-pack-smoke-private-path-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedRepo(rootDir, {
|
||||||
|
[`${RELEASE_DIR}/quickstart.md`]: 'Do not ship /Users/affoon/private-state in public docs.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const report = buildReport({ root: rootDir });
|
||||||
|
const boundary = report.checks.find(check => check.id === 'hermes-boundary-sanitized');
|
||||||
|
|
||||||
|
assert.strictEqual(report.ready, false);
|
||||||
|
assert.strictEqual(boundary.status, 'fail');
|
||||||
|
assert.ok(boundary.evidence.includes(`${RELEASE_DIR}/quickstart.md:1`));
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('CLI emits json and uses status 2 for failed smoke reports', () => {
|
||||||
|
const rootDir = createTempDir('preview-pack-smoke-cli-');
|
||||||
|
|
||||||
|
try {
|
||||||
|
seedRepo(rootDir);
|
||||||
|
const stdout = run(['--format=json', `--root=${rootDir}`], { cwd: rootDir });
|
||||||
|
const parsed = JSON.parse(stdout);
|
||||||
|
assert.strictEqual(parsed.ready, true);
|
||||||
|
|
||||||
|
writeFile(rootDir, 'package.json', JSON.stringify({ files: [], scripts: {} }, null, 2));
|
||||||
|
const failedRun = runProcess(['--format=json', `--root=${rootDir}`], { cwd: rootDir });
|
||||||
|
assert.strictEqual(failedRun.status, 2);
|
||||||
|
assert.strictEqual(failedRun.stderr, '');
|
||||||
|
assert.ok(failedRun.stdout.includes('"ready": false'));
|
||||||
|
} finally {
|
||||||
|
cleanup(rootDir);
|
||||||
|
}
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('CLI help exits successfully and invalid flags fail before reporting', () => {
|
||||||
|
const help = runProcess(['--help']);
|
||||||
|
assert.strictEqual(help.status, 0);
|
||||||
|
assert.strictEqual(help.stderr, '');
|
||||||
|
assert.ok(help.stdout.includes('Usage: node scripts/preview-pack-smoke.js'));
|
||||||
|
|
||||||
|
const invalid = runProcess(['--format=xml']);
|
||||||
|
assert.strictEqual(invalid.status, 1);
|
||||||
|
assert.strictEqual(invalid.stdout, '');
|
||||||
|
assert.match(invalid.stderr, /Error: Invalid format/);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
console.log(`\nPassed: ${passed}`);
|
||||||
|
console.log(`Failed: ${failed}`);
|
||||||
|
|
||||||
|
if (failed > 0) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
runTests();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user