session-orchestrator 3.20.0 → 3.22.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/030-wave-execution.mdc +10 -8
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +515 -0
- package/README.md +16 -11
- package/agents/analyst.md +1 -1
- package/agents/architect-reviewer.md +1 -1
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +1 -1
- package/agents/dialectic-deriver.md +1 -1
- package/agents/docs-writer.md +1 -1
- package/agents/memory-proposal-collector.md +7 -5
- package/agents/qa-strategist.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +42 -1
- package/agents/skill-applied-judge.md +1 -1
- package/agents/test-writer.md +1 -1
- package/agents/ui-developer.md +1 -1
- package/agents/ux-evaluator.md +1 -1
- package/commands/eli5.md +33 -0
- package/commands/release.md +62 -0
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/migration-v3.md +9 -6
- package/docs/persona-panel.md +3 -1
- package/docs/scope-collision-guard.md +167 -0
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/enforce-scope.mjs +103 -3
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +76 -97
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/on-stop.mjs +127 -12
- package/hooks/post-bash-write-verify.mjs +8 -32
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/hooks/pre-task-scope-disjoint.mjs +1042 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/release.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +10 -9
- package/scripts/emit-session.mjs +42 -0
- package/scripts/export-hw-learnings.mjs +61 -2
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/backlog-scan.mjs +106 -15
- package/scripts/lib/build-live-signals.mjs +7 -3
- package/scripts/lib/ci-status-banner.mjs +267 -77
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/git-config-drift.mjs +471 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
- package/scripts/lib/io.mjs +432 -7
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/learnings/select.mjs +157 -3
- package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
- package/scripts/lib/mirror-issues-banner.mjs +266 -0
- package/scripts/lib/named-vault-resolver.mjs +105 -16
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-cards/schema.mjs +6 -2
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/reconcile/writer.mjs +69 -30
- package/scripts/lib/redact-spans.mjs +89 -0
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/scope-baseline.mjs +77 -17
- package/scripts/lib/scope-gate.mjs +658 -0
- package/scripts/lib/secret-masker.mjs +262 -0
- package/scripts/lib/session-lock.mjs +34 -10
- package/scripts/lib/session-registry.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +164 -58
- package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
- package/scripts/lib/validate/check-agents.mjs +77 -5
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-commands.mjs +2 -20
- package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-skills.mjs +191 -0
- package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
- package/scripts/lib/validate/check-unwired-features.mjs +219 -11
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
- package/scripts/lib/validate/frontmatter-block.mjs +61 -0
- package/scripts/lib/validate/tier-inference.mjs +46 -8
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/namespace.mjs +146 -1
- package/scripts/lib/vault-mirror/process.mjs +264 -31
- package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
- package/scripts/lib/vault-status/board-writer.mjs +300 -56
- package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
- package/scripts/lib/vcs-repo-spec.mjs +680 -30
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/print-learnings-index.mjs +30 -3
- package/scripts/release.mjs +983 -107
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/site-numbers.mjs +1049 -0
- package/scripts/validate-plugin.mjs +64 -0
- package/scripts/validate-wave-scope.mjs +286 -12
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +26 -1
- package/skills/_shared/monitor-patterns.md +24 -4
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +49 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/SKILL.md +9 -1
- package/skills/debug/SKILL.md +4 -1
- package/skills/discovery/SKILL.md +28 -24
- package/skills/discovery/issue-templates.md +4 -4
- package/skills/discovery/probes-code.md +2 -2
- package/skills/discovery/probes-feature.md +6 -6
- package/skills/discovery/probes-infra.md +2 -2
- package/skills/discovery/probes-session.md +5 -5
- package/skills/dispatcher/SKILL.md +10 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/frontmatter-guard/SKILL.md +9 -1
- package/skills/gitlab-ops/SKILL.md +73 -59
- package/skills/gitlab-portfolio/SKILL.md +10 -1
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +20 -7
- package/skills/npm-publish/SKILL.md +23 -51
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/persona-panel/SKILL.md +3 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +42 -12
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/repo-audit/SKILL.md +10 -1
- package/skills/session-end/SKILL.md +97 -22
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-end/session-metrics-write.md +4 -10
- package/skills/session-plan/SKILL.md +2 -2
- package/skills/session-plan/wave-template.md +1 -1
- package/skills/session-start/SKILL.md +82 -36
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/soul.md +110 -0
- package/skills/spinout/SKILL.md +5 -1
- package/skills/sunset-review/SKILL.md +11 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/tmux-layout/SKILL.md +7 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-mirror/SKILL.md +10 -1
- package/skills/vault-sync/SKILL.md +10 -1
- package/skills/vault-sync/validator.mjs +55 -6
- package/skills/wave-executor/wave-loop.md +64 -12
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/lib/mission-status-schema.mjs +0 -114
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-skills.mjs — Validate the frontmatter contract of every skills/<name>/SKILL.md.
|
|
3
|
+
// Usage: check-skills.mjs <plugin-root>
|
|
4
|
+
// Outputs lines of the form " PASS: ..." / " FAIL: ..."
|
|
5
|
+
// Exit 0 = all checks passed; exit 1 = at least one failure.
|
|
6
|
+
//
|
|
7
|
+
// WHY A REAL PARSER: the sibling check-agents.mjs validates agent frontmatter with
|
|
8
|
+
// line-oriented regexes. That is exactly why 12 of 46 SKILL.md frontmatter blocks
|
|
9
|
+
// could sit unparseable in the tree unnoticed — an unquoted single-line
|
|
10
|
+
// `description:` containing a `: ` (e.g. "Iron Law: NO FIXES") is not YAML, but a
|
|
11
|
+
// regex that only looks for `^description:` sees nothing wrong. Claude Code's own
|
|
12
|
+
// loader is lenient, so the defect was latent rather than visible. This check
|
|
13
|
+
// follows check-commands.mjs instead: a real js-yaml CORE_SCHEMA parse (R8) is the
|
|
14
|
+
// rule the other five hang off, because none of the field rules can be evaluated
|
|
15
|
+
// on a block that does not parse.
|
|
16
|
+
//
|
|
17
|
+
// DELIBERATELY NOT IMPLEMENTED — see the report for #<wave>:
|
|
18
|
+
// * A block-scalar ban. check-agents.mjs bans `description: >` for agents/*.md
|
|
19
|
+
// because the agent loader cannot read it. For SKILL.md the sign is REVERSED:
|
|
20
|
+
// the folded block scalar is the only form that makes the `: ` collision above
|
|
21
|
+
// structurally impossible, 23 of 46 files already use it, and the 12 repairs
|
|
22
|
+
// that made this check green all landed on it. Porting the agent rule here
|
|
23
|
+
// would red 35 of 46 files and forbid the fix.
|
|
24
|
+
// * Length ceilings on `name` / `description`. No spec vendored in this repo
|
|
25
|
+
// states one, and an invented requirement is worse than none. (Measured
|
|
26
|
+
// 2026-08-15 at the repairing commit: longest name 22 chars, longest
|
|
27
|
+
// description 1012 chars — a hypothetical 1024 ceiling would sit 12 chars
|
|
28
|
+
// above the live corpus and go red on the next sentence added.)
|
|
29
|
+
|
|
30
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
31
|
+
import { join } from 'node:path';
|
|
32
|
+
import yaml from 'js-yaml';
|
|
33
|
+
|
|
34
|
+
import { extractInitialFrontmatter } from './frontmatter-block.mjs';
|
|
35
|
+
|
|
36
|
+
const [, , pluginRoot] = process.argv;
|
|
37
|
+
|
|
38
|
+
if (!pluginRoot) {
|
|
39
|
+
console.error('Usage: check-skills.mjs <plugin-root>');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SKILLS_DIR_NAME = 'skills';
|
|
44
|
+
const SKILL_FILE = 'SKILL.md';
|
|
45
|
+
const KEBAB_CASE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
46
|
+
|
|
47
|
+
let passed = 0;
|
|
48
|
+
let failed = 0;
|
|
49
|
+
|
|
50
|
+
function pass(msg) {
|
|
51
|
+
console.log(` PASS: ${msg}`);
|
|
52
|
+
passed++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function fail(msg) {
|
|
56
|
+
console.log(` FAIL: ${msg}`);
|
|
57
|
+
failed++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Enumerate `skills/<dir>/SKILL.md` one level below the skills root.
|
|
62
|
+
*
|
|
63
|
+
* Depth is capped at one level on purpose: R5 (name === directory name) is only
|
|
64
|
+
* meaningful for a skill that owns its immediate directory. Measured 2026-08-15:
|
|
65
|
+
* `find skills -mindepth 3 -name SKILL.md` returns 0, so the cap loses nothing
|
|
66
|
+
* today. Revisit if nested skill packages ever land.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} skillsDir - absolute path to the skills root
|
|
69
|
+
* @returns {Array<{ dirName: string, relPath: string, absPath: string }>} sorted by directory name
|
|
70
|
+
*/
|
|
71
|
+
function collectSkillFiles(skillsDir) {
|
|
72
|
+
const found = [];
|
|
73
|
+
for (const dirName of readdirSync(skillsDir).sort()) {
|
|
74
|
+
const dirPath = join(skillsDir, dirName);
|
|
75
|
+
let isDir;
|
|
76
|
+
try {
|
|
77
|
+
isDir = statSync(dirPath).isDirectory();
|
|
78
|
+
} catch {
|
|
79
|
+
isDir = false;
|
|
80
|
+
}
|
|
81
|
+
if (!isDir) continue;
|
|
82
|
+
|
|
83
|
+
const absPath = join(dirPath, SKILL_FILE);
|
|
84
|
+
if (!existsSync(absPath)) continue;
|
|
85
|
+
|
|
86
|
+
found.push({ dirName, relPath: `${SKILLS_DIR_NAME}/${dirName}/${SKILL_FILE}`, absPath });
|
|
87
|
+
}
|
|
88
|
+
return found;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Apply the frontmatter contract to one SKILL.md.
|
|
93
|
+
*
|
|
94
|
+
* Rules: R1 frontmatter block present · R8 block parses as a YAML mapping ·
|
|
95
|
+
* R2 `name` present · R3 `name` is kebab-case · R5 `name` === directory name ·
|
|
96
|
+
* R6 `description` present and non-empty.
|
|
97
|
+
*
|
|
98
|
+
* @param {{ dirName: string, relPath: string, absPath: string }} skill
|
|
99
|
+
* @returns {boolean} true when every rule holds
|
|
100
|
+
*/
|
|
101
|
+
function validateSkillFrontmatter(skill) {
|
|
102
|
+
const before = failed;
|
|
103
|
+
const content = readFileSync(skill.absPath, 'utf8');
|
|
104
|
+
|
|
105
|
+
// R1 — frontmatter block present.
|
|
106
|
+
const extracted = extractInitialFrontmatter(content);
|
|
107
|
+
if (!extracted.ok) {
|
|
108
|
+
fail(`${skill.relPath}: ${extracted.diagnostic}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// R8 — the block is valid YAML. Everything below depends on this holding.
|
|
113
|
+
let frontmatter;
|
|
114
|
+
try {
|
|
115
|
+
frontmatter = yaml.load(extracted.yamlText, { schema: yaml.CORE_SCHEMA });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
const reason = error?.reason ?? error?.message ?? String(error);
|
|
118
|
+
const location = error?.mark
|
|
119
|
+
? ` at frontmatter line ${error.mark.line + 1}, column ${error.mark.column + 1}`
|
|
120
|
+
: '';
|
|
121
|
+
fail(`${skill.relPath}: invalid YAML frontmatter: ${reason}${location}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (frontmatter === null || typeof frontmatter !== 'object' || Array.isArray(frontmatter)) {
|
|
126
|
+
fail(`${skill.relPath}: YAML frontmatter must be a non-null mapping/object`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// R2 — name present.
|
|
131
|
+
const name = frontmatter.name;
|
|
132
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
133
|
+
fail(`${skill.relPath}: missing or empty required frontmatter field: name`);
|
|
134
|
+
} else {
|
|
135
|
+
// R3 — name is kebab-case.
|
|
136
|
+
if (!KEBAB_CASE.test(name)) {
|
|
137
|
+
fail(`${skill.relPath}: name must be kebab-case (^[a-z0-9]+(-[a-z0-9]+)*$), got: '${name}'`);
|
|
138
|
+
}
|
|
139
|
+
// R5 — name matches the directory that owns the skill.
|
|
140
|
+
if (name !== skill.dirName) {
|
|
141
|
+
fail(`${skill.relPath}: name '${name}' does not match its directory '${skill.dirName}'`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// R6 — description present and non-empty.
|
|
146
|
+
const description = frontmatter.description;
|
|
147
|
+
if (typeof description !== 'string' || description.trim() === '') {
|
|
148
|
+
fail(`${skill.relPath}: missing or empty required frontmatter field: description`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return failed === before;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Check: skill frontmatter (SKILL.md)
|
|
156
|
+
// ============================================================================
|
|
157
|
+
console.log('--- Check: skill frontmatter (SKILL.md) ---');
|
|
158
|
+
|
|
159
|
+
const skillsDir = join(pluginRoot, SKILLS_DIR_NAME);
|
|
160
|
+
|
|
161
|
+
if (!existsSync(skillsDir)) {
|
|
162
|
+
fail(`skills directory not found at conventional location: ./${SKILLS_DIR_NAME}`);
|
|
163
|
+
} else {
|
|
164
|
+
let skills;
|
|
165
|
+
try {
|
|
166
|
+
skills = collectSkillFiles(skillsDir);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
skills = [];
|
|
169
|
+
fail(`skills directory is unreadable: ${error?.message ?? String(error)}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (skills.length === 0) {
|
|
173
|
+
fail(`skills directory contains no ${SKILL_FILE} files`);
|
|
174
|
+
} else {
|
|
175
|
+
pass(`skills directory contains ${skills.length} ${SKILL_FILE} files`);
|
|
176
|
+
|
|
177
|
+
let valid = 0;
|
|
178
|
+
for (const skill of skills) {
|
|
179
|
+
if (validateSkillFrontmatter(skill)) valid++;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (valid === skills.length) {
|
|
183
|
+
pass(`all ${skills.length} ${SKILL_FILE} frontmatter blocks parse as YAML and carry a kebab-case name matching their directory plus a non-empty description`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log('');
|
|
189
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
190
|
+
|
|
191
|
+
process.exit(failed > 0 ? 1 : 0);
|