@event4u/agent-config 8.4.0 → 8.4.1
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/CHANGELOG.md +6 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +1 -1
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +1 -1
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +1 -1
- package/dist/mcp/registry-manifest.json +1 -1
- package/package.json +4 -1
- package/src/agent-src/README.md +64 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +497 -0
- package/src/agent-src/scripts/scan-seeder-violations.php +145 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +1087 -0
- package/src/agent-src/templates/AGENTS.md +34 -0
- package/src/agent-src/templates/scripts/README.md +195 -0
- package/src/agent-src/templates/scripts/check_memory.ts +662 -0
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +370 -0
- package/src/agent-src/templates/scripts/historical-bug-patterns.example.yml +84 -0
- package/src/agent-src/templates/scripts/implement_ticket/__main__.ts +27 -0
- package/src/agent-src/templates/scripts/memory_hash.ts +355 -0
- package/src/agent-src/templates/scripts/memory_lookup.ts +1089 -0
- package/src/agent-src/templates/scripts/memory_report.ts +868 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +444 -0
- package/src/agent-src/templates/scripts/memory_status.ts +215 -0
- package/src/agent-src/templates/scripts/ownership-map.example.yml +87 -0
- package/src/agent-src/templates/scripts/pr-risk-config.example.yml +76 -0
- package/src/agent-src/templates/scripts/pr_review_routing.ts +544 -0
- package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
- package/src/agent-src/templates/scripts/telemetry/boundary.ts +164 -0
- package/src/agent-src/templates/scripts/telemetry/engagement.ts +479 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +394 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +210 -0
- package/src/agent-src/templates/scripts/telemetry_record.ts +276 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +211 -0
- package/src/agent-src/templates/scripts/telemetry_status.ts +333 -0
- package/src/agent-src/templates/scripts/tier_usage_report.ts +619 -0
- package/src/agent-src/templates/scripts/work_engine/__main__.ts +14 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +1261 -0
- package/src/agent-src/templates/scripts/work_engine/_lib/user_global_paths.ts +329 -0
- package/src/agent-src/templates/scripts/work_engine/cli.ts +206 -0
- package/src/agent-src/templates/scripts/work_engine/cli_args.ts +249 -0
- package/src/agent-src/templates/scripts/work_engine/delivery_state.ts +225 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/analyze.ts +125 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/memory.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/plan.ts +267 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/refine.ts +518 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/report.ts +379 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +268 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +258 -0
- package/src/agent-src/templates/scripts/work_engine/directives/index.ts +32 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/contract.ts +243 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/index.ts +108 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +259 -0
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/ui.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/_passthrough.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +241 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +216 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +506 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +325 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/index.ts +102 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +462 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +474 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +352 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.ts +33 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.ts +213 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/index.ts +111 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.ts +126 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/report.ts +112 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui_trivial/test.ts +164 -0
- package/src/agent-src/templates/scripts/work_engine/dispatcher.ts +515 -0
- package/src/agent-src/templates/scripts/work_engine/emitters.ts +119 -0
- package/src/agent-src/templates/scripts/work_engine/errors.ts +24 -0
- package/src/agent-src/templates/scripts/work_engine/hook_bootstrap.ts +104 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.ts +189 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.ts +41 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.ts +89 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_gate.ts +193 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +304 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.ts +110 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/index.ts +17 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +161 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.ts +45 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/trace.ts +134 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/context.ts +94 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/events.ts +58 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/exceptions.ts +85 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/index.ts +27 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/registry.ts +66 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/runner.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/input_builders.ts +260 -0
- package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/migration/v0_to_v1.ts +531 -0
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +366 -0
- package/src/agent-src/templates/scripts/work_engine/persona_policy.ts +97 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/diff.ts +135 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/file.ts +175 -0
- package/src/agent-src/templates/scripts/work_engine/resolvers/prompt.ts +115 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/confidence.ts +415 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +466 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_trace.ts +298 -0
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +444 -0
- package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +252 -0
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +745 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +1151 -0
- package/src/agent-src/templates/scripts/work_engine/state_io.ts +413 -0
- package/src/install/atomic.ts +111 -0
- package/src/install/conflict.ts +226 -0
- package/src/install/detect.ts +280 -0
- package/src/install/paths.ts +90 -0
- package/src/install/plan.ts +205 -0
- package/src/install/preflight.ts +210 -0
- package/src/install/selectedTools.ts +52 -0
- package/src/install/toolDetection.ts +120 -0
- package/src/install/txlog.ts +161 -0
- package/src/install/types.ts +175 -0
- package/src/install/wizard-plan.ts +216 -0
- package/src/scripts/prepack-check.mjs +108 -5
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Content-addressed hash for a curated memory entry.
|
|
4
|
+
*
|
|
5
|
+
* TypeScript twin of `src/agent-src/templates/scripts/memory_hash.py`
|
|
6
|
+
* (ADR-200, Phase 1 — consumer-shipped template; logic is byte-identical to
|
|
7
|
+
* the dev-side `src/scripts/memory_hash.ts` twin because the template `.py`
|
|
8
|
+
* is identical to the dev-side `.py`). The public API and CLI contract mirror the Python
|
|
9
|
+
* original EXACTLY — same exported names (snake_case kept deliberately),
|
|
10
|
+
* same canonical-JSON serialisation (sorted keys, no whitespace,
|
|
11
|
+
* `ensure_ascii=False`, `default=str`), same SHA-256 truncation, same exit
|
|
12
|
+
* codes, byte-identical messages. No behaviour changes.
|
|
13
|
+
*
|
|
14
|
+
* The hash is SHA-256 over the canonical-JSON-serialized entry, truncated
|
|
15
|
+
* to the first 12 hex chars. Canonical JSON sorts object keys, uses no
|
|
16
|
+
* extra whitespace, and normalises types so two equivalent entries hash
|
|
17
|
+
* identically regardless of YAML formatting.
|
|
18
|
+
*
|
|
19
|
+
* Used by `/memory-promote` to pick the filename
|
|
20
|
+
* `agents/memory/<type>/<hash>.yml` so the same entry promoted on two
|
|
21
|
+
* branches converges to a single file after `git merge`.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* memory_hash --yaml path/to/entry.yml
|
|
25
|
+
* echo '{"id":"x"}' | memory_hash --json-stdin
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { createHash } from 'node:crypto';
|
|
29
|
+
import * as fs from 'node:fs';
|
|
30
|
+
import process from 'node:process';
|
|
31
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
import YAML, { parseDocument } from 'yaml';
|
|
34
|
+
|
|
35
|
+
export const HASH_LEN = 12;
|
|
36
|
+
|
|
37
|
+
// PyYAML timestamp implicit-resolver. A *plain* (unquoted) scalar matching
|
|
38
|
+
// these is constructed as datetime.date / datetime.datetime. str() of a
|
|
39
|
+
// `datetime.date` is `YYYY-MM-DD`; str() of a `datetime.datetime` keeps the
|
|
40
|
+
// time component (e.g. `2026-01-01 13:45:30`). The `yaml` npm parses these
|
|
41
|
+
// to JS Date objects whose String() form differs, so canonical_json would
|
|
42
|
+
// hash differently — this marker preserves PyYAML's str() shape exactly.
|
|
43
|
+
const PYYAML_DATE_ONLY_RE = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
|
|
44
|
+
const PYYAML_TIMESTAMP_RE =
|
|
45
|
+
/^(?:[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt]|[ \t]+)[0-9]{1,2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]*)?(?:[ \t]*(?:Z|[-+][0-9]{1,2}(?::[0-9]{2})?))?)$/;
|
|
46
|
+
|
|
47
|
+
/** Marker carrying PyYAML's `str(datetime.*)` form for canonical hashing. */
|
|
48
|
+
class PyTimestamp {
|
|
49
|
+
constructor(readonly pyStr: string) {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Canonical-JSON serialisation matching Python's
|
|
54
|
+
* `json.dumps(obj, sort_keys=True, separators=(",",":"),
|
|
55
|
+
* ensure_ascii=False, default=str)`, encoded UTF-8.
|
|
56
|
+
*
|
|
57
|
+
* - Object keys sorted (recursively).
|
|
58
|
+
* - No whitespace between tokens.
|
|
59
|
+
* - Non-ASCII characters preserved (NOT escaped).
|
|
60
|
+
* - Non-serialisable scalars fall back to `str(value)` (`default=str`).
|
|
61
|
+
*/
|
|
62
|
+
export function canonical_json(obj: unknown): Buffer {
|
|
63
|
+
return Buffer.from(_dumps(obj), 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function hash_entry(obj: unknown): string {
|
|
67
|
+
return createHash('sha256').update(canonical_json(obj)).digest('hex').slice(0, HASH_LEN);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Serialise with sorted keys + compact separators, mirroring json.dumps. */
|
|
71
|
+
function _dumps(value: unknown): string {
|
|
72
|
+
if (value === null || value === undefined) {
|
|
73
|
+
// Python json: None → "null". `undefined` cannot occur from parsed
|
|
74
|
+
// YAML/JSON; treat it as None for safety.
|
|
75
|
+
return 'null';
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === 'boolean') {
|
|
78
|
+
return value ? 'true' : 'false';
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === 'number') {
|
|
81
|
+
return _numRepr(value);
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === 'string') {
|
|
84
|
+
return _strRepr(value);
|
|
85
|
+
}
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
return `[${value.map((v) => _dumps(v)).join(',')}]`;
|
|
88
|
+
}
|
|
89
|
+
if (value instanceof PyTimestamp) {
|
|
90
|
+
// default=str on a datetime.date/datetime → PyYAML's str() form.
|
|
91
|
+
return _strRepr(value.pyStr);
|
|
92
|
+
}
|
|
93
|
+
if (_isPlainObject(value)) {
|
|
94
|
+
const keys = Object.keys(value).sort();
|
|
95
|
+
const parts = keys.map((k) => `${_strRepr(k)}:${_dumps(value[k])}`);
|
|
96
|
+
return `{${parts.join(',')}}`;
|
|
97
|
+
}
|
|
98
|
+
// default=str — any other type stringified, then JSON-quoted.
|
|
99
|
+
return _strRepr(String(value));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function _isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
103
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** json.dumps number repr: integers stay int-like; floats keep Python form. */
|
|
107
|
+
function _numRepr(n: number): string {
|
|
108
|
+
if (!Number.isFinite(n)) {
|
|
109
|
+
// Python json emits Infinity / -Infinity / NaN (allow_nan default).
|
|
110
|
+
if (Number.isNaN(n)) {
|
|
111
|
+
return 'NaN';
|
|
112
|
+
}
|
|
113
|
+
return n > 0 ? 'Infinity' : '-Infinity';
|
|
114
|
+
}
|
|
115
|
+
if (Number.isInteger(n)) {
|
|
116
|
+
return String(n);
|
|
117
|
+
}
|
|
118
|
+
return String(n);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* json.dumps string repr with `ensure_ascii=False`: standard JSON escapes
|
|
123
|
+
* for control chars / `"` / `\`, but non-ASCII passed through verbatim.
|
|
124
|
+
*/
|
|
125
|
+
function _strRepr(s: string): string {
|
|
126
|
+
let out = '"';
|
|
127
|
+
for (const ch of s) {
|
|
128
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
129
|
+
switch (ch) {
|
|
130
|
+
case '"':
|
|
131
|
+
out += '\\"';
|
|
132
|
+
break;
|
|
133
|
+
case '\\':
|
|
134
|
+
out += '\\\\';
|
|
135
|
+
break;
|
|
136
|
+
case '\n':
|
|
137
|
+
out += '\\n';
|
|
138
|
+
break;
|
|
139
|
+
case '\r':
|
|
140
|
+
out += '\\r';
|
|
141
|
+
break;
|
|
142
|
+
case '\t':
|
|
143
|
+
out += '\\t';
|
|
144
|
+
break;
|
|
145
|
+
case '\b':
|
|
146
|
+
out += '\\b';
|
|
147
|
+
break;
|
|
148
|
+
case '\f':
|
|
149
|
+
out += '\\f';
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
if (code < 0x20) {
|
|
153
|
+
out += `\\u${code.toString(16).padStart(4, '0')}`;
|
|
154
|
+
} else {
|
|
155
|
+
// ensure_ascii=False — emit the char as-is (incl. non-ASCII).
|
|
156
|
+
out += ch;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return `${out}"`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _load_yaml(p: string): unknown {
|
|
164
|
+
// PyYAML availability is the Python-side concern; here a read failure
|
|
165
|
+
// surfaces as a thrown error (mirrors Python's open() raising).
|
|
166
|
+
const text = fs.readFileSync(p, 'utf-8');
|
|
167
|
+
// version: '1.1' matches PyYAML's default (YAML 1.1) so implicit typing
|
|
168
|
+
// (e.g. yes/no → bool, sexagesimals) lands the same way `safe_load` does.
|
|
169
|
+
// The `yaml` npm parses plain timestamp scalars to JS Date objects whose
|
|
170
|
+
// String() form differs from PyYAML's `str(datetime.*)`; rewrite those to
|
|
171
|
+
// a PyTimestamp marker carrying the exact PyYAML str() so canonical_json
|
|
172
|
+
// hashes identically.
|
|
173
|
+
const doc = parseDocument(text, { version: '1.1', prettyErrors: false });
|
|
174
|
+
if (doc.errors.length > 0) {
|
|
175
|
+
const err = doc.errors[0];
|
|
176
|
+
throw new Error(err ? err.message : 'YAML parse error');
|
|
177
|
+
}
|
|
178
|
+
YAML.visit(doc, {
|
|
179
|
+
Scalar(_key, node) {
|
|
180
|
+
if (!node.range) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const raw = text.slice(node.range[0], node.range[1]);
|
|
184
|
+
// Under YAML 1.1 the `yaml` lib resolves plain timestamp scalars to
|
|
185
|
+
// JS Date objects. Recover PyYAML's str() form from the original
|
|
186
|
+
// source text so canonical_json hashes identically.
|
|
187
|
+
if (node.value instanceof Date) {
|
|
188
|
+
if (PYYAML_DATE_ONLY_RE.test(raw)) {
|
|
189
|
+
(node as { value: unknown }).value = new PyTimestamp(raw);
|
|
190
|
+
} else if (PYYAML_TIMESTAMP_RE.test(raw)) {
|
|
191
|
+
(node as { value: unknown }).value = new PyTimestamp(_pyYamlDatetimeStr(raw));
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// PyYAML's bool resolver does NOT treat bare `y/Y/n/N` as boolean
|
|
196
|
+
// (only yes/no/on/off/true/false). The `yaml` lib's 1.1 schema
|
|
197
|
+
// does — restore those four to strings to match `safe_load`.
|
|
198
|
+
if (typeof node.value === 'boolean' && node.type === 'PLAIN' && /^[ynYN]$/.test(raw)) {
|
|
199
|
+
(node as { value: unknown }).value = raw;
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
return doc.toJS({ mapAsMap: false });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Compute PyYAML `str(datetime.datetime(...))` for a matched timestamp scalar. */
|
|
207
|
+
function _pyYamlDatetimeStr(raw: string): string {
|
|
208
|
+
// Split date / time on T|t|whitespace; PyYAML always renders the date with
|
|
209
|
+
// a space separator and zero-padded fields, time as HH:MM:SS[.ffffff][±HH:MM].
|
|
210
|
+
const m =
|
|
211
|
+
/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[Tt]|[ \t]+)(\d{1,2}):(\d{2}):(\d{2})(?:\.(\d*))?(?:[ \t]*(Z|[-+]\d{1,2}(?::\d{2})?))?$/.exec(
|
|
212
|
+
raw,
|
|
213
|
+
);
|
|
214
|
+
if (!m) {
|
|
215
|
+
return raw;
|
|
216
|
+
}
|
|
217
|
+
const [, y, mo, d, h, mi, s, frac, tz] = m;
|
|
218
|
+
const date = `${y}-${_pad2(mo as string)}-${_pad2(d as string)}`;
|
|
219
|
+
let time = `${_pad2(h as string)}:${mi}:${s}`;
|
|
220
|
+
if (frac && /[1-9]/.test(frac)) {
|
|
221
|
+
// datetime.microsecond → str() pads to 6 digits, drops trailing zeros
|
|
222
|
+
// only by truncation to 6 then keeping all 6 (Python keeps 6).
|
|
223
|
+
const micro = (frac + '000000').slice(0, 6);
|
|
224
|
+
time += `.${micro}`;
|
|
225
|
+
}
|
|
226
|
+
let off = '';
|
|
227
|
+
if (tz) {
|
|
228
|
+
if (tz === 'Z') {
|
|
229
|
+
off = '+00:00';
|
|
230
|
+
} else {
|
|
231
|
+
const tm = /^([-+])(\d{1,2})(?::(\d{2}))?$/.exec(tz);
|
|
232
|
+
if (tm) {
|
|
233
|
+
off = `${tm[1]}${_pad2(tm[2] as string)}:${tm[3] ?? '00'}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return `${date} ${time}${off}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function _pad2(v: string): string {
|
|
241
|
+
return v.length === 1 ? `0${v}` : v;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function main(): number {
|
|
245
|
+
const argv = process.argv.slice(2);
|
|
246
|
+
const parsed = _parseArgs(argv);
|
|
247
|
+
let entry: unknown;
|
|
248
|
+
if (parsed.yaml !== null) {
|
|
249
|
+
entry = _load_yaml(parsed.yaml);
|
|
250
|
+
} else {
|
|
251
|
+
entry = JSON.parse(fs.readFileSync(0, 'utf-8'));
|
|
252
|
+
}
|
|
253
|
+
if (!(_isPlainObject(entry) || Array.isArray(entry))) {
|
|
254
|
+
process.stderr.write(`error: expected object/array, got ${_pyTypeName(entry)}\n`);
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
process.stdout.write(`${hash_entry(entry)}\n`);
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Mirror Python `type(x).__name__` for the error message. */
|
|
262
|
+
function _pyTypeName(v: unknown): string {
|
|
263
|
+
if (v === null || v === undefined) {
|
|
264
|
+
return 'NoneType';
|
|
265
|
+
}
|
|
266
|
+
if (typeof v === 'boolean') {
|
|
267
|
+
return 'bool';
|
|
268
|
+
}
|
|
269
|
+
if (typeof v === 'number') {
|
|
270
|
+
return Number.isInteger(v) ? 'int' : 'float';
|
|
271
|
+
}
|
|
272
|
+
if (typeof v === 'string') {
|
|
273
|
+
return 'str';
|
|
274
|
+
}
|
|
275
|
+
return typeof v;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface ParsedArgs {
|
|
279
|
+
yaml: string | null;
|
|
280
|
+
json_stdin: boolean;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const _USAGE = 'usage: memory_hash [-h] (--yaml YAML | --json-stdin)';
|
|
284
|
+
|
|
285
|
+
/** argparse-style error: usage line + `<prog>: error: <msg>` to stderr, exit 2. */
|
|
286
|
+
function _argError(msg: string): never {
|
|
287
|
+
process.stderr.write(`${_USAGE}\n`);
|
|
288
|
+
process.stderr.write(`memory_hash: error: ${msg}\n`);
|
|
289
|
+
process.exit(2);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** argparse mutually-exclusive group (required) for --yaml / --json-stdin. */
|
|
293
|
+
function _parseArgs(argv: string[]): ParsedArgs {
|
|
294
|
+
const args: ParsedArgs = { yaml: null, json_stdin: false };
|
|
295
|
+
let sawGroup = false;
|
|
296
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
297
|
+
const a = argv[i] as string;
|
|
298
|
+
if (a === '--yaml') {
|
|
299
|
+
if (sawGroup) {
|
|
300
|
+
_argError('argument --yaml: not allowed with argument --json-stdin');
|
|
301
|
+
}
|
|
302
|
+
args.yaml = argv[++i] as string;
|
|
303
|
+
sawGroup = true;
|
|
304
|
+
} else if (a.startsWith('--yaml=')) {
|
|
305
|
+
if (sawGroup) {
|
|
306
|
+
_argError('argument --yaml: not allowed with argument --json-stdin');
|
|
307
|
+
}
|
|
308
|
+
args.yaml = a.slice('--yaml='.length);
|
|
309
|
+
sawGroup = true;
|
|
310
|
+
} else if (a === '--json-stdin') {
|
|
311
|
+
if (sawGroup) {
|
|
312
|
+
_argError('argument --json-stdin: not allowed with argument --yaml');
|
|
313
|
+
}
|
|
314
|
+
args.json_stdin = true;
|
|
315
|
+
sawGroup = true;
|
|
316
|
+
} else if (a === '-h' || a === '--help') {
|
|
317
|
+
process.stdout.write(`${_USAGE}\n`);
|
|
318
|
+
process.exit(0);
|
|
319
|
+
} else {
|
|
320
|
+
_argError(`unrecognized arguments: ${a}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (!sawGroup) {
|
|
324
|
+
_argError('one of the arguments --yaml --json-stdin is required');
|
|
325
|
+
}
|
|
326
|
+
return args;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function _isCliEntry(): boolean {
|
|
330
|
+
if (process.argv[1] === undefined) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
334
|
+
if (import.meta.url === argvUrl) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
// A symlinked invocation (e.g. via an installed `.augment/` projection,
|
|
338
|
+
// or macOS /var → /private/var temp dirs) makes the raw URLs differ:
|
|
339
|
+
// import.meta.url is the resolved real path while argv[1] keeps the
|
|
340
|
+
// symlink path. Compare realpaths so the entry guard still fires
|
|
341
|
+
// (without this the CLI silently no-ops when run through a symlink).
|
|
342
|
+
try {
|
|
343
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
344
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
345
|
+
return here === argv;
|
|
346
|
+
} catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const _isMain =
|
|
352
|
+
_isCliEntry();
|
|
353
|
+
if (_isMain) {
|
|
354
|
+
process.exit(main());
|
|
355
|
+
}
|