@ngockhoale/ukit 3.0.3 → 3.0.4
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 +17 -0
- package/bin/ukit +12 -4
- package/manifests/engineConformance.yaml +29 -0
- package/manifests/platform.full.yaml +13 -0
- package/package.json +2 -1
- package/scripts/audit/decision-coverage.mjs +295 -0
- package/scripts/bench/outline-savings.mjs +19 -4
- package/scripts/bench/parallel-agents.mjs +15 -4
- package/scripts/bench/runGold.mjs +22 -4
- package/scripts/bench/v3-ceremony.mjs +7 -1
- package/scripts/bug/triage.mjs +56 -17
- package/scripts/index/build-index.mjs +94 -28
- package/scripts/index/query-index.mjs +48 -14
- package/scripts/index/refresh-index.mjs +142 -62
- package/scripts/perf/audit-perf.mjs +8 -2
- package/scripts/skill/audit-skill.mjs +54 -25
- package/src/bug/triageBug.js +9 -6
- package/src/cli/adapters.js +6 -0
- package/src/cli/commands/code.js +7 -1
- package/src/cli/commands/indexArgs.js +4 -2
- package/src/cli/commands/indexTools.js +8 -1
- package/src/cli/commands/install.js +13 -0
- package/src/cli/commands/memory.js +10 -3
- package/src/cli/commands/status.js +17 -1
- package/src/cli/commands/update.js +7 -0
- package/src/context/detectProjectContext.js +3 -1
- package/src/core/codeintel/analogy.js +1 -1
- package/src/core/codeintel/diagnostics.js +9 -6
- package/src/core/codeintel/graph.js +14 -8
- package/src/core/codeintel/impact.js +0 -1
- package/src/core/codeintel/invalidation.js +5 -3
- package/src/core/codeintel/packet.js +11 -0
- package/src/core/codeintel/router.js +17 -3
- package/src/core/codeintel/semanticProvider.js +1 -1
- package/src/core/codeintel/summaries.js +11 -7
- package/src/core/compact/contextBudget.js +26 -12
- package/src/core/compact/index.js +15 -8
- package/src/core/docContracts.js +10 -2
- package/src/core/experiments/deliberation.js +321 -0
- package/src/core/experiments/dynamicWorkflow.js +492 -0
- package/src/core/fileOps.js +8 -1
- package/src/core/gatewayProbe.js +29 -1
- package/src/core/gatewayResilienceEnv.js +44 -3
- package/src/core/handoffDocValidator.js +3 -1
- package/src/core/hookChainDoctor.js +16 -1
- package/src/core/memory/deltaOverlays.js +448 -0
- package/src/core/memory/learningCandidates.js +302 -0
- package/src/core/memory/migrate.js +59 -32
- package/src/core/memory/recordStore.js +24 -1
- package/src/core/memory/store.js +44 -8
- package/src/core/memory/storeV2.js +48 -33
- package/src/core/memory/userMemory.js +11 -10
- package/src/core/output/index.js +15 -10
- package/src/core/permissionPolicy.js +8 -0
- package/src/core/runtimeConfig.js +224 -4
- package/src/core/sensitiveValueScanner.js +10 -2
- package/src/core/taskBudgetValidator.js +12 -17
- package/src/core/taskProgressGuard.js +59 -9
- package/src/core/unattendedDoctor.js +5 -2
- package/src/core/uninstall.js +37 -8
- package/src/decision/client.js +371 -0
- package/src/decision/lease.js +198 -0
- package/src/decision/preflight.js +492 -0
- package/src/decision/protocol.js +308 -0
- package/src/decision/registry.js +384 -0
- package/src/decision/shadow.js +281 -0
- package/src/decision/statePacket.js +165 -0
- package/src/diagnostics/failurePatterns.js +2 -1
- package/src/diagnostics/ledgerFiles.js +3 -1
- package/src/diagnostics/routeOutcomes.js +1 -29
- package/src/index/buildIndex.js +23 -11
- package/src/index/impactContext.js +21 -2
- package/src/index/importResolution.js +13 -7
- package/src/index/queryIndex.js +11 -5
- package/src/index/resolveContext.js +16 -6
- package/src/index/taskRouting.js +63 -2
- package/src/index/verificationPlan.js +12 -1
- package/src/learning/patternProposals.js +6 -0
- package/src/render/renderTemplate.js +1 -1
- package/src/skill/auditSkill.js +3 -1
- package/src/stack/detectStack.js +3 -1
- package/template_project/.claude/agents/handoff-planner.md +2 -5
- package/template_project/.claude/hooks/auto-allow-bash.sh +5 -0
- package/template_project/.claude/hooks/block-dangerous.mjs +11 -4
- package/template_project/.claude/hooks/context-hardcap-gate.sh +10 -1
- package/template_project/.claude/hooks/handoff-model-guard.sh +14 -4
- package/template_project/.claude/hooks/handoff-resume.sh +10 -1
- package/template_project/.claude/hooks/protect-files.sh +0 -1
- package/template_project/.claude/hooks/record-execution.mjs +13 -1
- package/template_project/.claude/hooks/sensitive-data-guard.mjs +71 -5
- package/template_project/.claude/hooks/session-episode.sh +9 -2
- package/template_project/.claude/skills/pdf-processing-pro/SKILL.md +1 -1
- package/template_project/.claude/ukit/index/handoff-doc-validator.mjs +3 -1
- package/template_project/.claude/ukit/index/lib/index-core.mjs +123 -39
- package/template_project/.claude/ukit/index/route-task.mjs +444 -0
- package/template_project/.claude/ukit/index/task-budget-validator.mjs +12 -16
- package/template_project/.claude/ukit/index/unic-decision.mjs +786 -0
- package/template_project/.claude/ukit/index/verify-context.mjs +11 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +116 -9
- package/template_project/.claude/ukit/runtime/project-important.mjs +9 -7
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +48 -0
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +596 -0
- package/template_project/.claude/ukit/runtime/sensitive-value-scanner.mjs +4 -7
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +1 -1
- package/template_project/docs/AI_HANDOFF/PLAN.md +7 -7
- package/template_project/docs/AI_HANDOFF/RULES.md +1 -1
- package/template_project/ukit/storage/config.json +34 -1
- package/template_project/.claude/ukit/skill-router-state.json +0 -1
- package/template_project/.ukit/storage/cache/hook-latency/unknown.jsonl +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to UKit are documented here.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## 3.0.4 - 2026-09-24
|
|
7
|
+
|
|
8
|
+
- **Full source audit (cycle C53) — ~86 defects fixed across all 14 audit areas**; every fix carries a RED→GREEN regression test under `tests/audit/`:
|
|
9
|
+
- **CLI** (`bin/ukit`, `src/cli/**`): watchdog `unref()` silent-exit-0 on hung `runCli`; `--tools=` empty value deselecting all adapters; `install --help` exiting 1; `update`/`status` silently ignoring positional/unknown args; unknown `--adapter` silently mapping to `.claude`; `--flag=a=b` truncation; `extractFlag` swallowing the next flag as a value; `memory` crash on null ledger receipt.
|
|
10
|
+
- **Lifecycle** (`src/core` install/update/uninstall/plan): uninstall data loss — tracked *file* entries removed via recursive `fs.rm` (a user dir replacing a managed file was deleted wholesale); `cleanupEmptyParents` crash on ENOTEMPTY/ENOENT races.
|
|
11
|
+
- **Hooks/policy/security**: `hookChainDoctor` TypeError on missing `projectRoot`; `permissionPolicy` non-array surface lists miscompiled/crashed → now fail-closed `UKIT_EPOLICY`; `unattendedDoctor` counted only `'prompt'` not `'ask'`; uppercase SHA-256 allowlist digests silently unmatched; `gatewayProbe`/`resolveGatewayApiKey` threw despite never-throw contracts; `detectCustomGatewayAsync` was a dead sync wrapper now truly async and wired.
|
|
12
|
+
- **Handoff/contracts/compact/experiments**: 10 defects incl. forced anchors dropped past `maxLines` while still counted; unlocked read-modify-write race in `appendCompactHistory`; shallow freeze letting a judge hide hard-constraint violations; retry-corrupted `state.budget.remaining`.
|
|
13
|
+
- **Codeintel/output/token**: duplicate call edges + edges to non-existent callee nodes; `JsonGraphStore` crash on corrupt codegraph; `semanticProvider` reading `raw.files` vs `{items}` shape (index-wide resolve always empty); `PATH_RE` misrouting "and/or"/"n/a"; `packetToText` never rendering `packet.memory`; `drainDirty` dropping `saturated` flag; `parseCheckLine` returning wrong stderr line; `summaries` provider bound to `process.cwd()`; `sweepTeeCache` unenforceable maxFiles cap; `appendOutputHistory` dropping empty-command entries.
|
|
14
|
+
- **Memory**: `addRecord` never ran `ensureMigrated` (first v2 write stranded all v1 data); corrupt legacy JSON silently dropped and hidden from `needsMigration`; unbounded `observeLearningSignal` text; concurrent-run counter races.
|
|
15
|
+
- **Index/routing**: `queryCodeIndex`/`queryAnalog` TypeError on non-string input; corrupt artifact `items` crashing `groupBy`; substring signal misclassification ('score'→'core', 'install '→'all '); importers ignoring aliased specifiers; `extends: "pkg"` resolving `pkg.json` not `pkg/tsconfig.json`; `shellEscape` dropping apostrophes; empty verification-plan floor; `test:*` scripts missed by `TEST_RUNNER_COMMAND`; installed index-core mirror drift re-synced.
|
|
16
|
+
- **Decision plane**: `sanitizeValue` `__proto__` prototype mutation; half-open circuit breaker with no single-probe gate and never-refreshed `openedAt`; `invalidateLease` raw TypeError on malformed records.
|
|
17
|
+
- **Diagnostics/render/skill/learning/stack/bug**: `ledgerFiles` omitted-limit NaN → empty results; `renderTemplate` `{{constructor}}`/`{{toString}}` rendering `[native code]`; `statusSkillAudit` crash on null entries; `knownSignaturesAndTexts` missing legacy `signature` collection; `detectStack` raw SyntaxError on corrupt package.json; `triageBug` ignoring declared `packageManager`.
|
|
18
|
+
- **Scripts toolchain**: missing flag values silently defaulting across 9 scripts; unknown flags ignored; unhandled promise rejections; silent partial index on unreadable files; `audit-perf.mjs` unbounded `spawnSync`/`execFileSync` (hang) → 60s SIGKILL timeout.
|
|
19
|
+
- **Shipped hooks** (`template_project/.claude/hooks`): `block-dangerous` deadline fail-open + missed bare `git push -f`; `record-execution` never armed deadline; `sensitive-data-guard` missed joined/`=` credential forms and wrapper-command bypass (`sudo`/`env`/`bash -c`/`xargs`); `session-episode` hardcoded stdin watchdog; `handoff-model-guard` silent crash exit; `auto-allow-bash` unquoted glob expansion corrupting allow rules.
|
|
20
|
+
- **Template payload/manifests/docs**: stale dev telemetry shipped via `runtime-cache` manifest item; `pdf-processing-pro` frontmatter name mismatch; handoff-planner `§7` drift; PLAN.md seed format drift; `QUICK_START` `@ukit/cli` ref; `RULES.md` nonexistent `PLAN.md §7` reference; broken archive doc links.
|
|
21
|
+
- **Test health**: `test:handoff` now self-installs UKit first (19/50 failures on fresh clones); stale pre-TASK-025 watchdog wiring assertion fixed; dead conditionals removed.
|
|
22
|
+
|
|
6
23
|
## 3.0.3 - 2026-09-23
|
|
7
24
|
|
|
8
25
|
- Same content as 3.0.2 (npm staged-publish never finalized; re-published as 3.0.3).
|
package/bin/ukit
CHANGED
|
@@ -6,9 +6,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { runCli } from '../src/cli/index.js';
|
|
7
7
|
|
|
8
8
|
async function readPackageVersion(packageRoot) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
9
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
10
|
+
const raw = await fs.readFile(packageJsonPath, 'utf8');
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(raw).version;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
throw new Error(`Failed to parse ${packageJsonPath}: ${error?.message ?? error}`);
|
|
15
|
+
}
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
const DEFAULT_CLI_DEADLINE_MS = 120_000;
|
|
@@ -26,7 +30,11 @@ async function main() {
|
|
|
26
30
|
console.error(`[UKit] timed out after ${deadlineMs}ms — likely wedged filesystem; retry`);
|
|
27
31
|
process.exit(1);
|
|
28
32
|
}, deadlineMs);
|
|
29
|
-
|
|
33
|
+
// NOT unref'd: a hung runCli that holds no event-loop handles (e.g. a promise
|
|
34
|
+
// that never settles after its input stream closed — the BUG-C21-13 class)
|
|
35
|
+
// would otherwise let the process exit 0 silently with the work unfinished.
|
|
36
|
+
// The ref'd timer is the only handle that guarantees the deadline still fires;
|
|
37
|
+
// it is always cleared in the finally below, so it cannot outlive main().
|
|
30
38
|
|
|
31
39
|
try {
|
|
32
40
|
const currentFile = fileURLToPath(import.meta.url);
|
|
@@ -24,9 +24,38 @@ classes:
|
|
|
24
24
|
claude-code: { support: enforced, evidence: tests/hooks/executionLedgerRiskGate.test.js }
|
|
25
25
|
codex: { support: degraded, note: 'no Stop hook; completionEvidence is advisory in .codex/README.md' }
|
|
26
26
|
omp: { support: enforced, evidence: tests/hooks/ompHookBridge.test.js }
|
|
27
|
+
# C52 mechanism rows (FR-019). Each carries promotionEvidence (FR-020):
|
|
28
|
+
# {stage, evaluatedAt, verdict, evidenceRef}; verdict enum
|
|
29
|
+
# not-evaluated|net-gain|neutral|negative; not-evaluated => null date/ref.
|
|
30
|
+
mechanisms:
|
|
31
|
+
decision-plane-preflight:
|
|
32
|
+
hosts:
|
|
33
|
+
claude-code: { support: enforced, evidence: tests/decision/shadow.test.js }
|
|
34
|
+
codex: { support: degraded, note: 'no hook surface; route-task shadow/preflight runs only when invoked manually, receipts advisory' }
|
|
35
|
+
omp: { support: enforced, evidence: tests/index/unicDecision.test.js }
|
|
36
|
+
promotionEvidence: { stage: off, evaluatedAt: null, verdict: not-evaluated, evidenceRef: null }
|
|
37
|
+
resumable-run-state:
|
|
38
|
+
hosts:
|
|
39
|
+
claude-code: { support: enforced, evidence: tests/hooks/resumableRunWiring.test.js }
|
|
40
|
+
codex: { support: degraded, note: 'no session-start/stop hooks; resume record is advisory context, never auto-reinjected' }
|
|
41
|
+
omp: { support: enforced, evidence: tests/hooks/reinjectContextHook.test.js }
|
|
42
|
+
promotionEvidence: { stage: off, evaluatedAt: null, verdict: not-evaluated, evidenceRef: null }
|
|
43
|
+
learning-overlays:
|
|
44
|
+
hosts:
|
|
45
|
+
claude-code: { support: enforced, evidence: tests/core/deltaOverlays.test.js }
|
|
46
|
+
codex: { support: degraded, note: 'overlay approval gate lives in shared runtime; codex consumes rendered policy without a hook gate' }
|
|
47
|
+
omp: { support: enforced, evidence: tests/core/deltaOverlays.test.js }
|
|
48
|
+
promotionEvidence: { stage: off, evaluatedAt: null, verdict: not-evaluated, evidenceRef: null }
|
|
27
49
|
advisory:
|
|
28
50
|
description: stdout/prompt text the model is asked to respect (routes, suppress, budgets)
|
|
29
51
|
hosts:
|
|
30
52
|
claude-code: { support: advisory }
|
|
31
53
|
codex: { support: advisory }
|
|
32
54
|
omp: { support: advisory }
|
|
55
|
+
mechanisms:
|
|
56
|
+
experiment-flags:
|
|
57
|
+
hosts:
|
|
58
|
+
claude-code: { support: advisory }
|
|
59
|
+
codex: { support: advisory }
|
|
60
|
+
omp: { support: advisory }
|
|
61
|
+
promotionEvidence: { stage: off, evaluatedAt: null, verdict: not-evaluated, evidenceRef: null }
|
|
@@ -1738,6 +1738,18 @@ items:
|
|
|
1738
1738
|
packs:
|
|
1739
1739
|
- core
|
|
1740
1740
|
|
|
1741
|
+
- id: ukit-index-unic-decision-script
|
|
1742
|
+
type: config
|
|
1743
|
+
sourceTemplate: .claude/ukit/index/unic-decision.mjs
|
|
1744
|
+
targetPath: .claude/ukit/index/unic-decision.mjs
|
|
1745
|
+
requires:
|
|
1746
|
+
- ukit-runtime-scripts
|
|
1747
|
+
mergeStrategy: overwrite_with_backup
|
|
1748
|
+
variables: []
|
|
1749
|
+
enabledByDefault: true
|
|
1750
|
+
packs:
|
|
1751
|
+
- core
|
|
1752
|
+
|
|
1741
1753
|
- id: ukit-index-extract-image-script
|
|
1742
1754
|
type: config
|
|
1743
1755
|
sourceTemplate: .claude/ukit/index/extract-image.mjs
|
|
@@ -1757,6 +1769,7 @@ items:
|
|
|
1757
1769
|
- ukit-index-cache-utils-script
|
|
1758
1770
|
- ukit-index-route-catalog-script
|
|
1759
1771
|
- ukit-index-unic-gateway-script
|
|
1772
|
+
- ukit-index-unic-decision-script
|
|
1760
1773
|
- ukit-index-extract-image-script
|
|
1761
1774
|
mergeStrategy: overwrite_with_backup
|
|
1762
1775
|
variables: []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngockhoale/ukit",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "Install/update an index-first AI workspace for Claude Code, OpenAI Codex and omp (Oh My Pi).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"release:verify": "node ./scripts/release/verify-release.mjs",
|
|
64
64
|
"test": "vitest run",
|
|
65
65
|
"test:liveness": "vitest run tests/liveness/",
|
|
66
|
+
"test:handoff": "node ./bin/ukit install >/dev/null && node --test --test-concurrency=1 'tests/handoff/**/*.test.mjs' 'tests/handoff/**/*.test.js'",
|
|
66
67
|
"test:watch": "vitest"
|
|
67
68
|
},
|
|
68
69
|
"engines": {
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* decision-coverage.mjs (TASK-004 / M07, SPEC §5 FR-010)
|
|
4
|
+
*
|
|
5
|
+
* CI coverage guard: every consequential decision point in the fixed scan set
|
|
6
|
+
* must be covered by a DECISION_REGISTRY entry or a committed allowlist entry.
|
|
7
|
+
*
|
|
8
|
+
* Discovery convention (deterministic, no invented semantics):
|
|
9
|
+
* (a) an exported function named /^(derive|resolve|select|choose|decide)[A-Z]/
|
|
10
|
+
* whose body returns a value drawn from a >=2-member choice set — a
|
|
11
|
+
* Set/Object.freeze([...])/object-literal constant, a switch, or a
|
|
12
|
+
* ternary over >=2 string literals;
|
|
13
|
+
* (b) any site carrying a `// @decision-point <key>` annotation.
|
|
14
|
+
* Point key = the annotation key, else `<basename>:<functionName>`.
|
|
15
|
+
*
|
|
16
|
+
* Allowlist: docs/pstack/decision-coverage-allowlist.json —
|
|
17
|
+
* [{"key":"<pointKey>","reason":"deterministic|delegated|out-of-scope","owner":"<file>"}]
|
|
18
|
+
*
|
|
19
|
+
* Exit 0 when every discovered point is covered; exit 1 printing each
|
|
20
|
+
* uncovered key (and each invalid allowlist entry). Reports only — does not
|
|
21
|
+
* yet block promotion.
|
|
22
|
+
*
|
|
23
|
+
* Usage: node scripts/audit/decision-coverage.mjs
|
|
24
|
+
* [--root <dir>] [--allowlist <path>] [--extra-scan <file>...]
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import fs from 'node:fs';
|
|
28
|
+
import path from 'node:path';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
import { DECISION_REGISTRY } from '../../src/decision/registry.js';
|
|
32
|
+
|
|
33
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const DEFAULT_ROOT = path.resolve(SCRIPT_DIR, '..', '..');
|
|
35
|
+
|
|
36
|
+
// Fixed scan set (FR-010) — relative to the repo root.
|
|
37
|
+
const SCAN_FILES = [
|
|
38
|
+
'src/index/taskRouting.js',
|
|
39
|
+
'src/core/executionContracts.js',
|
|
40
|
+
'template_project/.claude/ukit/index/route-task.mjs',
|
|
41
|
+
];
|
|
42
|
+
const SCAN_GLOBS = ['src/decision/'];
|
|
43
|
+
const DEFAULT_ALLOWLIST = 'docs/pstack/decision-coverage-allowlist.json';
|
|
44
|
+
|
|
45
|
+
const DECISION_NAME_RE =
|
|
46
|
+
/^export\s+(?:async\s+)?function\s+((?:derive|resolve|select|choose|decide)[A-Z]\w*)\s*\(/gm;
|
|
47
|
+
const ANNOTATION_RE = /@decision-point\s+([A-Za-z0-9._-]+)/g;
|
|
48
|
+
const CONST_RE =
|
|
49
|
+
/(?:^|[;{}\n])\s*(?:export\s+)?const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*(?:new\s+Set\s*\(|Object\.freeze\s*\()?\s*([\[{])/g;
|
|
50
|
+
const ALLOWED_REASONS = new Set(['deterministic', 'delegated', 'out-of-scope']);
|
|
51
|
+
|
|
52
|
+
// --- tiny lexical scanner (strings/comments aware) ---------------------------
|
|
53
|
+
|
|
54
|
+
function skipString(source, i) {
|
|
55
|
+
const quote = source[i];
|
|
56
|
+
for (let j = i + 1; j < source.length; j += 1) {
|
|
57
|
+
if (source[j] === '\\') { j += 1; continue; }
|
|
58
|
+
if (source[j] === quote) return j;
|
|
59
|
+
}
|
|
60
|
+
return source.length;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function skipLineComment(source, i) {
|
|
64
|
+
const nl = source.indexOf('\n', i);
|
|
65
|
+
return nl === -1 ? source.length : nl;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function skipBlockComment(source, i) {
|
|
69
|
+
const end = source.indexOf('*/', i + 2);
|
|
70
|
+
return end === -1 ? source.length : end + 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Index of the bracket closing source[openIdx], or -1.
|
|
74
|
+
function findMatching(source, openIdx) {
|
|
75
|
+
const open = source[openIdx];
|
|
76
|
+
const close = open === '{' ? '}' : open === '[' ? ']' : ')';
|
|
77
|
+
let depth = 0;
|
|
78
|
+
for (let i = openIdx; i < source.length; i += 1) {
|
|
79
|
+
const ch = source[i];
|
|
80
|
+
if (ch === "'" || ch === '"' || ch === '`') { i = skipString(source, i); continue; }
|
|
81
|
+
if (ch === '/' && source[i + 1] === '/') { i = skipLineComment(source, i); continue; }
|
|
82
|
+
if (ch === '/' && source[i + 1] === '*') { i = skipBlockComment(source, i); continue; }
|
|
83
|
+
if (ch === open) depth += 1;
|
|
84
|
+
else if (ch === close) {
|
|
85
|
+
depth -= 1;
|
|
86
|
+
if (depth === 0) return i;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Commas at bracket depth 0 inside (start, end) — member count = commas + 1.
|
|
93
|
+
function countTopLevelCommas(source, start, end) {
|
|
94
|
+
let depth = 0;
|
|
95
|
+
let commas = 0;
|
|
96
|
+
let sawContent = false;
|
|
97
|
+
for (let i = start; i < end; i += 1) {
|
|
98
|
+
const ch = source[i];
|
|
99
|
+
if (ch === "'" || ch === '"' || ch === '`') { i = skipString(source, i); sawContent = true; continue; }
|
|
100
|
+
if (ch === '/' && source[i + 1] === '/') { i = skipLineComment(source, i); continue; }
|
|
101
|
+
if (ch === '/' && source[i + 1] === '*') { i = skipBlockComment(source, i); continue; }
|
|
102
|
+
if (ch === '{' || ch === '[' || ch === '(') depth += 1;
|
|
103
|
+
else if (ch === '}' || ch === ']' || ch === ')') depth -= 1;
|
|
104
|
+
else if (ch === ',' && depth === 0) commas += 1;
|
|
105
|
+
else if (!/\s/.test(ch)) sawContent = true;
|
|
106
|
+
}
|
|
107
|
+
return sawContent ? commas + 1 : 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- discovery ---------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
// Names of >=2-member choice-set constants declared anywhere in the file.
|
|
113
|
+
function collectChoiceSets(source) {
|
|
114
|
+
const names = new Set();
|
|
115
|
+
CONST_RE.lastIndex = 0;
|
|
116
|
+
let match;
|
|
117
|
+
while ((match = CONST_RE.exec(source)) !== null) {
|
|
118
|
+
const openIdx = match.index + match[0].lastIndexOf(match[2]);
|
|
119
|
+
const closeIdx = findMatching(source, openIdx);
|
|
120
|
+
if (closeIdx === -1) continue;
|
|
121
|
+
if (countTopLevelCommas(source, openIdx + 1, closeIdx) >= 2) {
|
|
122
|
+
names.add(match[1]);
|
|
123
|
+
}
|
|
124
|
+
CONST_RE.lastIndex = openIdx + 1;
|
|
125
|
+
}
|
|
126
|
+
return names;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Heuristic (a): the body returns from a >=2-member choice set — a switch, a
|
|
130
|
+
// ternary over >=2 string literals, or a reference to a choice-set constant.
|
|
131
|
+
function isConsequential(body, choiceSetNames) {
|
|
132
|
+
if (/\bswitch\s*\(/.test(body)) return true;
|
|
133
|
+
if (/\?\s*['"][^'"]*['"]\s*:\s*['"]/.test(body)) return true;
|
|
134
|
+
for (const name of choiceSetNames) {
|
|
135
|
+
if (new RegExp(`\\b${name}\\b`).test(body)) return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function discoverInSource(source, basename) {
|
|
141
|
+
const points = [];
|
|
142
|
+
const choiceSets = collectChoiceSets(source);
|
|
143
|
+
|
|
144
|
+
DECISION_NAME_RE.lastIndex = 0;
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = DECISION_NAME_RE.exec(source)) !== null) {
|
|
147
|
+
const paramsOpen = source.indexOf('(', match.index + match[0].indexOf('('));
|
|
148
|
+
const paramsClose = findMatching(source, paramsOpen);
|
|
149
|
+
if (paramsClose === -1) continue;
|
|
150
|
+
const bodyOpen = source.indexOf('{', paramsClose);
|
|
151
|
+
if (bodyOpen === -1) continue;
|
|
152
|
+
const bodyClose = findMatching(source, bodyOpen);
|
|
153
|
+
if (bodyClose === -1) continue;
|
|
154
|
+
const body = source.slice(bodyOpen, bodyClose + 1);
|
|
155
|
+
if (isConsequential(body, choiceSets)) {
|
|
156
|
+
points.push(`${basename}:${match[1]}`);
|
|
157
|
+
}
|
|
158
|
+
DECISION_NAME_RE.lastIndex = bodyClose + 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ANNOTATION_RE.lastIndex = 0;
|
|
162
|
+
while ((match = ANNOTATION_RE.exec(source)) !== null) {
|
|
163
|
+
points.push(match[1]);
|
|
164
|
+
}
|
|
165
|
+
return points;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- CLI ---------------------------------------------------------------------
|
|
169
|
+
|
|
170
|
+
function parseArgs(argv) {
|
|
171
|
+
const args = { root: DEFAULT_ROOT, allowlist: null, extraScan: [] };
|
|
172
|
+
const valueFor = (flag, i) => {
|
|
173
|
+
const next = argv[i + 1];
|
|
174
|
+
if (next === undefined || next.startsWith('--')) {
|
|
175
|
+
console.error(`missing value for ${flag}`);
|
|
176
|
+
process.exit(2);
|
|
177
|
+
}
|
|
178
|
+
return next;
|
|
179
|
+
};
|
|
180
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
181
|
+
if (argv[i] === '--root') { args.root = path.resolve(valueFor('--root', i)); i += 1; }
|
|
182
|
+
else if (argv[i] === '--allowlist') { args.allowlist = path.resolve(valueFor('--allowlist', i)); i += 1; }
|
|
183
|
+
else if (argv[i] === '--extra-scan') { args.extraScan.push(path.resolve(valueFor('--extra-scan', i))); i += 1; }
|
|
184
|
+
else {
|
|
185
|
+
console.error(`unknown argument: ${argv[i]}`);
|
|
186
|
+
process.exit(2);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return args;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function scanFiles(root, extraScan) {
|
|
193
|
+
const files = SCAN_FILES.map((f) => path.join(root, f));
|
|
194
|
+
for (const dir of SCAN_GLOBS) {
|
|
195
|
+
const absDir = path.join(root, dir);
|
|
196
|
+
if (!fs.existsSync(absDir)) {
|
|
197
|
+
return { error: `scan directory missing: ${dir}` };
|
|
198
|
+
}
|
|
199
|
+
for (const entry of fs.readdirSync(absDir).sort()) {
|
|
200
|
+
if (entry.endsWith('.js')) files.push(path.join(absDir, entry));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
files.push(...extraScan);
|
|
204
|
+
return { files };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function loadAllowlist(allowlistPath) {
|
|
208
|
+
if (!fs.existsSync(allowlistPath)) {
|
|
209
|
+
return { entries: [], errors: [`allowlist missing: ${allowlistPath}`] };
|
|
210
|
+
}
|
|
211
|
+
let parsed;
|
|
212
|
+
try {
|
|
213
|
+
parsed = JSON.parse(fs.readFileSync(allowlistPath, 'utf8'));
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return { entries: [], errors: [`allowlist is not valid JSON: ${error.message}`] };
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(parsed)) {
|
|
218
|
+
return { entries: [], errors: ['allowlist must be a JSON array'] };
|
|
219
|
+
}
|
|
220
|
+
const entries = [];
|
|
221
|
+
const errors = [];
|
|
222
|
+
parsed.forEach((entry, index) => {
|
|
223
|
+
const label = `allowlist[${index}]`;
|
|
224
|
+
if (!entry || typeof entry !== 'object') {
|
|
225
|
+
errors.push(`${label}: entry must be an object`);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (typeof entry.key !== 'string' || entry.key.length === 0) {
|
|
229
|
+
errors.push(`${label}: key must be a non-empty string`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!ALLOWED_REASONS.has(entry.reason)) {
|
|
233
|
+
errors.push(
|
|
234
|
+
`${label} (${entry.key}): reason must be one of: ${[...ALLOWED_REASONS].join(', ')}`,
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (typeof entry.owner !== 'string' || entry.owner.length === 0) {
|
|
239
|
+
errors.push(`${label} (${entry.key}): owner must be a non-empty string`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
entries.push(entry);
|
|
243
|
+
});
|
|
244
|
+
return { entries, errors };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function main() {
|
|
248
|
+
const args = parseArgs(process.argv.slice(2));
|
|
249
|
+
const allowlistPath = args.allowlist ?? path.join(args.root, DEFAULT_ALLOWLIST);
|
|
250
|
+
|
|
251
|
+
const { files, error } = scanFiles(args.root, args.extraScan);
|
|
252
|
+
if (error) {
|
|
253
|
+
console.error(`decision-coverage: ${error}`);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const discovered = [];
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
if (!fs.existsSync(file)) {
|
|
260
|
+
console.error(`decision-coverage: scan file missing: ${path.relative(args.root, file)}`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
264
|
+
discovered.push(...discoverInSource(source, path.basename(file)));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const registryKeys = new Set(DECISION_REGISTRY.map((entry) => entry.decisionKey));
|
|
268
|
+
const { entries, errors } = loadAllowlist(allowlistPath);
|
|
269
|
+
const allowlisted = new Set(entries.map((entry) => entry.key));
|
|
270
|
+
|
|
271
|
+
const uncovered = [...new Set(discovered)]
|
|
272
|
+
.filter((key) => !registryKeys.has(key) && !allowlisted.has(key))
|
|
273
|
+
.sort();
|
|
274
|
+
|
|
275
|
+
for (const err of errors) {
|
|
276
|
+
console.log(`INVALID-ALLOWLIST ${err}`);
|
|
277
|
+
}
|
|
278
|
+
for (const key of uncovered) {
|
|
279
|
+
console.log(`UNCOVERED ${key}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (errors.length > 0 || uncovered.length > 0) {
|
|
283
|
+
console.log(
|
|
284
|
+
`decision-coverage: FAIL — ${uncovered.length} uncovered, ${errors.length} invalid allowlist entries`,
|
|
285
|
+
);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
const registryCovered = [...new Set(discovered)].filter((k) => registryKeys.has(k)).length;
|
|
289
|
+
console.log(
|
|
290
|
+
`decision-coverage: ok — ${new Set(discovered).size} decision points covered ` +
|
|
291
|
+
`(registry: ${registryCovered}, allowlisted: ${new Set(discovered).size - registryCovered})`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
main();
|
|
@@ -19,8 +19,12 @@ const DEFAULT_QUERY = 'resolveContext';
|
|
|
19
19
|
|
|
20
20
|
async function main() {
|
|
21
21
|
const args = process.argv.slice(2);
|
|
22
|
-
const rootDir = readFlagValue(args, '--root') ?? process.cwd();
|
|
23
|
-
const
|
|
22
|
+
const rootDir = path.resolve(readFlagValue(args, '--root') ?? process.cwd());
|
|
23
|
+
const limitRaw = readFlagValue(args, '--limit');
|
|
24
|
+
const limit = limitRaw === null ? 3 : Number.parseInt(limitRaw, 10);
|
|
25
|
+
if (limitRaw !== null && (Number.isNaN(limit) || limit < 1)) {
|
|
26
|
+
throw new Error(`invalid --limit value: "${limitRaw}" (expected a positive integer)`);
|
|
27
|
+
}
|
|
24
28
|
const query = collectPositionalArgs(args, ['--root', '--limit']) || DEFAULT_QUERY;
|
|
25
29
|
|
|
26
30
|
await buildCodeIndex({ rootDir });
|
|
@@ -68,7 +72,13 @@ function renderOutline(outline) {
|
|
|
68
72
|
|
|
69
73
|
function readFlagValue(argv, flag) {
|
|
70
74
|
const exact = argv.indexOf(flag);
|
|
71
|
-
if (exact >= 0
|
|
75
|
+
if (exact >= 0) {
|
|
76
|
+
const maybeValue = argv[exact + 1];
|
|
77
|
+
if (!maybeValue || maybeValue.startsWith('--')) {
|
|
78
|
+
throw new Error(`Missing value for ${flag}.`);
|
|
79
|
+
}
|
|
80
|
+
return maybeValue;
|
|
81
|
+
}
|
|
72
82
|
const withEquals = argv.find((item) => item.startsWith(`${flag}=`));
|
|
73
83
|
return withEquals ? withEquals.slice(flag.length + 1) : null;
|
|
74
84
|
}
|
|
@@ -89,4 +99,9 @@ function isFlagOrValue(argv, index, flagsWithValues = []) {
|
|
|
89
99
|
return true;
|
|
90
100
|
}
|
|
91
101
|
|
|
92
|
-
|
|
102
|
+
try {
|
|
103
|
+
await main();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(error && error.message ? error.message : error);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
@@ -28,12 +28,17 @@ const LEVEL_TIMEOUT_MS = Number(process.env.UKIT_BENCH_LEVEL_TIMEOUT_MS) || 30 *
|
|
|
28
28
|
|
|
29
29
|
function parseArgs(argv) {
|
|
30
30
|
const opts = { levels: '1,3,5,10', cmd: 'yarn test:release-core', out: '.cache/bench/parallel-agents.json' };
|
|
31
|
-
for (let i = 0; i < argv.length; i +=
|
|
31
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
32
32
|
const key = argv[i];
|
|
33
|
+
if (key !== '--levels' && key !== '--cmd' && key !== '--out') {
|
|
34
|
+
throw new Error(`unknown argument: ${key}`);
|
|
35
|
+
}
|
|
33
36
|
const val = argv[i + 1];
|
|
34
|
-
if (
|
|
35
|
-
|
|
37
|
+
if (val === undefined || val.startsWith('--')) {
|
|
38
|
+
throw new Error(`missing value for ${key}`);
|
|
36
39
|
}
|
|
40
|
+
opts[key.slice(2)] = val;
|
|
41
|
+
i += 1;
|
|
37
42
|
}
|
|
38
43
|
return opts;
|
|
39
44
|
}
|
|
@@ -117,7 +122,13 @@ function fail(msg) {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
async function main() {
|
|
120
|
-
const opts =
|
|
125
|
+
const opts = (() => {
|
|
126
|
+
try {
|
|
127
|
+
return parseArgs(process.argv.slice(2));
|
|
128
|
+
} catch (err) {
|
|
129
|
+
fail(err.message);
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
121
132
|
let levels;
|
|
122
133
|
try {
|
|
123
134
|
levels = parseLevels(opts.levels);
|
|
@@ -12,20 +12,31 @@ import { fileURLToPath } from 'node:url';
|
|
|
12
12
|
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
13
13
|
const DEFAULT_CORPUS = path.join(REPO_ROOT, 'scripts', 'bench', 'goldTasks.json');
|
|
14
14
|
|
|
15
|
+
const USAGE = 'usage: runGold.mjs [--corpus <path>] [--project <dir>] [--json <path>]';
|
|
16
|
+
|
|
15
17
|
function parseArgs(argv) {
|
|
16
18
|
let corpusPath = DEFAULT_CORPUS;
|
|
17
19
|
let projectRoot = REPO_ROOT;
|
|
18
20
|
let jsonPath = null;
|
|
21
|
+
const valueFor = (flag, i) => {
|
|
22
|
+
const next = argv[i + 1];
|
|
23
|
+
if (next === undefined || next.startsWith('--')) {
|
|
24
|
+
throw new Error(`missing value for ${flag}`);
|
|
25
|
+
}
|
|
26
|
+
return next;
|
|
27
|
+
};
|
|
19
28
|
for (let i = 0; i < argv.length; i += 1) {
|
|
20
29
|
if (argv[i] === '--corpus') {
|
|
21
|
-
corpusPath = path.resolve(
|
|
30
|
+
corpusPath = path.resolve(valueFor('--corpus', i));
|
|
22
31
|
i += 1;
|
|
23
32
|
} else if (argv[i] === '--project') {
|
|
24
|
-
projectRoot = path.resolve(
|
|
33
|
+
projectRoot = path.resolve(valueFor('--project', i));
|
|
25
34
|
i += 1;
|
|
26
35
|
} else if (argv[i] === '--json') {
|
|
27
|
-
jsonPath =
|
|
36
|
+
jsonPath = path.resolve(valueFor('--json', i));
|
|
28
37
|
i += 1;
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error(`unknown argument: ${argv[i]}`);
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
return { corpusPath, projectRoot, jsonPath };
|
|
@@ -202,7 +213,14 @@ export function printScorecard(results) {
|
|
|
202
213
|
|
|
203
214
|
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
204
215
|
if (isMain) {
|
|
205
|
-
|
|
216
|
+
let parsed;
|
|
217
|
+
try {
|
|
218
|
+
parsed = parseArgs(process.argv.slice(2));
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(`${USAGE}\n${error?.message ?? error}`);
|
|
221
|
+
process.exit(2);
|
|
222
|
+
}
|
|
223
|
+
const { corpusPath, projectRoot, jsonPath } = parsed;
|
|
206
224
|
try {
|
|
207
225
|
const { results, pass } = await runGold({ projectRoot, corpusPath });
|
|
208
226
|
printScorecard(results);
|
|
@@ -191,7 +191,13 @@ function renderTsv(report) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
async function main() {
|
|
194
|
-
const
|
|
194
|
+
const args = process.argv.slice(2);
|
|
195
|
+
const unknown = args.filter((arg) => arg !== '--tsv');
|
|
196
|
+
if (unknown.length > 0) {
|
|
197
|
+
console.error(`usage: v3-ceremony.mjs [--tsv]\nunknown argument: ${unknown[0]}`);
|
|
198
|
+
process.exit(2);
|
|
199
|
+
}
|
|
200
|
+
const tsv = args.includes('--tsv');
|
|
195
201
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ukit-v3-ceremony-'));
|
|
196
202
|
try {
|
|
197
203
|
await seedFixtureProject(tmpDir);
|
package/scripts/bug/triage.mjs
CHANGED
|
@@ -3,16 +3,28 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { triageBug } from '../../src/bug/triageBug.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
async function main() {
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const { value: rootArg, args: remainingArgs } = extractFlagValue(args, '--root');
|
|
9
|
+
const rootDir = rootArg ? path.resolve(rootArg) : process.cwd();
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
// Everything after `--` is signature text; before it, only positional args
|
|
12
|
+
// are — the --root value was already consumed by extractFlagValue.
|
|
13
|
+
const separatorIndex = remainingArgs.indexOf('--');
|
|
14
|
+
const positional = separatorIndex === -1 ? remainingArgs : remainingArgs.slice(0, separatorIndex);
|
|
15
|
+
const afterSeparator = separatorIndex === -1 ? [] : remainingArgs.slice(separatorIndex + 1);
|
|
16
|
+
const unknown = positional.filter((arg) => arg.startsWith('--'));
|
|
17
|
+
if (unknown.length > 0) {
|
|
18
|
+
throw new Error(`unknown argument: ${unknown[0]}`);
|
|
19
|
+
}
|
|
20
|
+
const signature = [...positional, ...afterSeparator].join(' ').trim();
|
|
21
|
+
|
|
22
|
+
if (!signature) {
|
|
23
|
+
console.error('Usage: yarn bug:triage -- "<error signature>"');
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
11
27
|
|
|
12
|
-
if (!signature) {
|
|
13
|
-
console.error('Usage: yarn bug:triage -- "<error signature>"');
|
|
14
|
-
process.exitCode = 1;
|
|
15
|
-
} else {
|
|
16
28
|
const result = await triageBug({ rootDir, signature });
|
|
17
29
|
console.log('[bug:triage]');
|
|
18
30
|
console.log(`lane: ${result.lane}`);
|
|
@@ -22,16 +34,43 @@ if (!signature) {
|
|
|
22
34
|
console.log(`test: ${result.recommendedTestCommand}`);
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
function extractFlagValue(argv, flag) {
|
|
38
|
+
let value = null;
|
|
39
|
+
const nextArgs = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
42
|
+
const arg = argv[i];
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
if (arg === flag) {
|
|
45
|
+
if (value !== null) {
|
|
46
|
+
nextArgs.push(arg);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const maybeValue = argv[i + 1];
|
|
50
|
+
if (!maybeValue || maybeValue.startsWith('--')) {
|
|
51
|
+
throw new Error(`Missing value for ${flag}.`);
|
|
52
|
+
}
|
|
53
|
+
value = maybeValue;
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
59
|
+
if (value === null) {
|
|
60
|
+
value = arg.slice(flag.length + 1);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
nextArgs.push(arg);
|
|
34
66
|
}
|
|
35
67
|
|
|
36
|
-
return
|
|
68
|
+
return { value, args: nextArgs };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await main();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error(error && error.message ? error.message : error);
|
|
75
|
+
process.exit(1);
|
|
37
76
|
}
|