shapeup-sdlc 3.18.2 → 4.0.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/settings.local.example.json +2 -3
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +115 -56
- package/README.md +101 -143
- package/SECURITY.md +16 -25
- package/agents/orient.md +14 -0
- package/agents/qa-edge-hunter.md +13 -0
- package/agents/spec-evaluator.md +14 -0
- package/agents/task-executor.md +14 -0
- package/bin/init.mjs +13 -16
- package/bin/lib/grant.mjs +8 -19
- package/commands/build.md +9 -14
- package/commands/eval.md +3 -3
- package/commands/hammer.md +9 -5
- package/commands/retro.md +12 -11
- package/commands/scopes.md +1 -12
- package/commands/ship.md +45 -88
- package/commands/wire.md +1 -8
- package/hooks/dispatch-receipt.mjs +94 -8
- package/hooks/hooks.json +0 -41
- package/hooks/lib/decision.mjs +6 -9
- package/hooks/lib/tier.mjs +50 -0
- package/hooks/sandbox-guard.mjs +140 -64
- package/kernel/gate.mjs +203 -22
- package/kernel/harness.mjs +20 -67
- package/kernel/init/run.mjs +175 -523
- package/kernel/lib/contract.mjs +9 -93
- package/kernel/lib/floor.mjs +64 -0
- package/kernel/lib/paths.mjs +39 -112
- package/kernel/order.mjs +157 -0
- package/kernel/probe/digest.mjs +2 -38
- package/kernel/schemas/domain.schema.json +146 -1125
- package/kernel/schemas/work-order.schema.json +8 -4
- package/kernel/schemas/work-result.schema.json +1 -1
- package/kernel/verify/envelope.mjs +2 -2
- package/kernel/verify/skills.mjs +27 -8
- package/kernel/verify/t0.mjs +2 -2
- package/package.json +4 -2
- package/skills/building/SKILL.md +144 -0
- package/skills/building/resources/gates.md +76 -0
- package/skills/building/resources/guidance.md +23 -0
- package/skills/{translator/references/preservation-rules.md → building/resources/intake-preservation.md} +1 -1
- package/skills/building/resources/intake.md +157 -0
- package/skills/building/resources/orders.md +69 -0
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/doc-schemas.md +1 -1
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/task-generation.md +13 -13
- package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/test-surface.md +6 -7
- package/skills/building/resources/plan-scopes.md +95 -0
- package/skills/building/resources/plan-spec.md +102 -0
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/_index.tmpl.md +1 -1
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/assess-report.tmpl.md +10 -11
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/run-state.tmpl.md +4 -4
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/scope-summary.tmpl.md +2 -2
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/synthesis.tmpl.md +4 -4
- package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/usecase.tmpl.md +2 -2
- package/skills/building/resources/plan-wire.md +82 -0
- package/skills/building/resources/plan.md +98 -0
- package/skills/building/resources/retro-research.md +60 -0
- package/skills/building/resources/retro-scan.md +49 -0
- package/skills/building/resources/retro.md +225 -0
- package/skills/building/resources/rounds.md +78 -0
- package/skills/building/resources/stop.md +127 -0
- package/skills/orient/SKILL.md +37 -37
- package/skills/qa-edge-hunter/SKILL.md +29 -14
- package/skills/shapeup/SKILL.md +5 -5
- package/skills/shapeup/resources/breadboarding.md +1 -1
- package/skills/shapeup/resources/context-compaction.md +5 -5
- package/skills/shapeup/resources/shaping.md +1 -1
- package/skills/spec-evaluator/SKILL.md +17 -21
- package/skills/spec-evaluator/references/dimension-contract.md +1 -1
- package/skills/spec-evaluator/references/dimensions/completeness.md +3 -3
- package/skills/spec-evaluator/references/dimensions/spec-conformance.md +1 -1
- package/skills/spec-evaluator/references/dimensions/test-surface-conformance.md +5 -5
- package/skills/task-executor/SKILL.md +11 -15
- package/commands/hill.md +0 -10
- package/hooks/gate-intake.mjs +0 -109
- package/hooks/gate-zerowork.mjs +0 -349
- package/hooks/tier-guard.mjs +0 -162
- package/kernel/compile.mjs +0 -1419
- package/kernel/init/fit.mjs +0 -199
- package/kernel/init/run-args.mjs +0 -206
- package/kernel/lib/breadboard.mjs +0 -165
- package/kernel/probe/attempts.mjs +0 -161
- package/kernel/probe/concurrency.mjs +0 -535
- package/kernel/probe/eval.mjs +0 -405
- package/kernel/probe/hunt.mjs +0 -104
- package/kernel/probe/leg.mjs +0 -229
- package/kernel/probe/owner.mjs +0 -142
- package/kernel/probe/requirements.mjs +0 -328
- package/kernel/probe/resume.mjs +0 -1110
- package/kernel/probe/rounds.mjs +0 -115
- package/kernel/probe/staged.mjs +0 -83
- package/kernel/probe/stats.mjs +0 -477
- package/kernel/probe/t0.mjs +0 -115
- package/kernel/reduce/board.mjs +0 -350
- package/kernel/reduce/graph.mjs +0 -482
- package/kernel/reduce/hill.mjs +0 -322
- package/kernel/reduce/ingest.mjs +0 -767
- package/kernel/reduce/leftovers.mjs +0 -159
- package/kernel/reduce/ship.mjs +0 -529
- package/kernel/reduce/snapshot.mjs +0 -291
- package/kernel/reduce/verdict.mjs +0 -172
- package/kernel/report/export.mjs +0 -384
- package/kernel/report/facts.mjs +0 -234
- package/kernel/verify/budget.mjs +0 -153
- package/kernel/verify/build.mjs +0 -428
- package/kernel/verify/dispatch.mjs +0 -114
- package/kernel/verify/spec.mjs +0 -975
- package/kernel/verify/trace.mjs +0 -604
- package/skills/ba-pitch-analyzer/SKILL.md +0 -204
- package/skills/coach/SKILL.md +0 -397
- package/skills/hill-chart/SKILL.md +0 -154
- package/skills/hill-chart/assets/dashboard.template.html +0 -1432
- package/skills/scope-architect/SKILL.md +0 -174
- package/skills/scope-hammer/SKILL.md +0 -231
- package/skills/solution-architect/SKILL.md +0 -158
- package/skills/tech-lead/SKILL.md +0 -151
- package/skills/tech-lead/references/gates.md +0 -605
- package/skills/tech-lead/references/protocol.md +0 -889
- package/skills/tech-lead/references/tiny-lane.md +0 -53
- package/skills/tech-lead/workflows/shapeup-run.js +0 -2187
- package/skills/translator/SKILL.md +0 -258
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/contract-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/ddd-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/integration-analysis.md +0 -0
- /package/skills/{ba-pitch-analyzer/references → building/resources/plan-references}/ux-behavior-patterns.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/api-feasibility.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/be-service.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/offline-storage.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/contracts/third-party-api.contract.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/context-map.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/event-choreography.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/migration-plan.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/cross-context/team-handoff.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/domain-model.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/feedback.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/integration.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task-board.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task-spike.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/task.tmpl.md +0 -0
- /package/skills/{ba-pitch-analyzer/assets/templates → building/resources/plan-templates}/ux-behavior.tmpl.md +0 -0
package/kernel/order.mjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// harness order — set and retire the pointers the fence reads.
|
|
3
|
+
//
|
|
4
|
+
// harness.mjs order open --slug <slug> --order <path>
|
|
5
|
+
// harness.mjs order close --slug <slug> --status <shipped|escalated|aborted> [--cause <text>]
|
|
6
|
+
//
|
|
7
|
+
// WHY THIS IS A VERB AND NOT A FILE EDIT. The substrate fence defers (allows) every write while no
|
|
8
|
+
// active-order pointer exists, so a pointer the loop forgot to set turns the fence off without a
|
|
9
|
+
// single failing check. `open` names the live order and refuses an order that is not parseable JSON
|
|
10
|
+
// carrying a slug, so a malformed pointer cannot be written. `close` is the other half: it retires
|
|
11
|
+
// both pointers and records the ending — status, cause, timestamp — so a run that ended stops
|
|
12
|
+
// fencing the checkout, and the ending is a row instead of a recollection.
|
|
13
|
+
//
|
|
14
|
+
// A `shipped` ENDING RESTS ON A SIGN-OFF. The ship gate refuses `ship` over a census that did not clear
|
|
15
|
+
// the run, a QA hunt the run owed and never paid, or a worker that ran below the model floor — and none
|
|
16
|
+
// of that held if the loop could write `status: shipped` without crossing the gate at all. So `close
|
|
17
|
+
// --status shipped` reads the gate ledger and refuses unless a Ship Sign-off row of THIS run says `ship`;
|
|
18
|
+
// the close record names the sign-off it rests on. `escalated` and `aborted` need no row: an honest
|
|
19
|
+
// ending is never refused.
|
|
20
|
+
//
|
|
21
|
+
// Exit 0 done · 1 refused (the order is unreadable, or a `shipped` close with no sign-off) · 2 malformed
|
|
22
|
+
// input. `open` also stamps the order's `compiled_at` with the current time.
|
|
23
|
+
|
|
24
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
|
|
25
|
+
import { dirname, join, resolve } from "node:path";
|
|
26
|
+
import { runArgs } from "./lib/argv.mjs";
|
|
27
|
+
import { activeOrder, activeScope, localRoot, insideRunTrace, gates as gatesPath, readRunId } from "./lib/paths.mjs";
|
|
28
|
+
import { validate, SCHEMAS_DIR } from "./verify/envelope.mjs";
|
|
29
|
+
|
|
30
|
+
/** The endings a run may record. */
|
|
31
|
+
export const CLOSE_STATUSES = ["shipped", "escalated", "aborted"];
|
|
32
|
+
|
|
33
|
+
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
34
|
+
export const ARGV_SPEC = {
|
|
35
|
+
usage: "harness.mjs order open --slug <slug> --order <path> | order close --slug <slug> --status <shipped|escalated|aborted> [--cause <text>]",
|
|
36
|
+
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
37
|
+
slug: { type: "str", required: true },
|
|
38
|
+
order: { type: "path" },
|
|
39
|
+
status: { type: "enum", values: CLOSE_STATUSES },
|
|
40
|
+
cause: { type: "str" },
|
|
41
|
+
cwd: { type: "path" },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Stamp the order with the real time, write the active-order pointer, then read it back.
|
|
46
|
+
*
|
|
47
|
+
* THE STAMP IS THE KERNEL'S, NOT THE LOOP'S. The fence decides an order is answered when its result
|
|
48
|
+
* is at least as new as the order's `compiled_at`, so a time the loop typed by hand and got wrong —
|
|
49
|
+
* a few minutes in the future — keeps the order live after its worker has returned and stalls every
|
|
50
|
+
* write the run makes. A real run did exactly that; the clock is read here instead.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} cwd - Project root.
|
|
53
|
+
* @param {string} slug - Feature slug.
|
|
54
|
+
* @param {string} orderPath - Path to the order the pointer names.
|
|
55
|
+
* @returns {{ok: boolean, reason?: string}} Whether the pointer reads back as written.
|
|
56
|
+
*/
|
|
57
|
+
export function openOrder(cwd, slug, orderPath) {
|
|
58
|
+
let order;
|
|
59
|
+
try { order = JSON.parse(readFileSync(orderPath, "utf8")); }
|
|
60
|
+
catch (e) { return { ok: false, reason: `order is not readable JSON: ${e.message}` }; }
|
|
61
|
+
if (!order || typeof order !== "object") return { ok: false, reason: "order is not an object" };
|
|
62
|
+
// The same check the dispatch gate makes, made BEFORE the order is armed: an invalid order that was
|
|
63
|
+
// opened first is live with no way to be edited (the fence freezes it) and no way to be withdrawn.
|
|
64
|
+
const schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, "work-order.schema.json"), "utf8"));
|
|
65
|
+
const { valid, errors } = validate(order, schema);
|
|
66
|
+
if (!valid) return { ok: false, reason: `order fails the order schema, so it was not opened: ${errors.slice(0, 5).join("; ")}` };
|
|
67
|
+
order.compiled_at = new Date().toISOString();
|
|
68
|
+
writeFileSync(orderPath, `${JSON.stringify(order, null, 2)}\n`);
|
|
69
|
+
const p = activeOrder(cwd);
|
|
70
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
71
|
+
writeFileSync(p, `${JSON.stringify({ slug, order_path: orderPath }, null, 2)}\n`);
|
|
72
|
+
const back = JSON.parse(readFileSync(p, "utf8"));
|
|
73
|
+
return back.slug === slug && back.order_path === orderPath
|
|
74
|
+
? { ok: true }
|
|
75
|
+
: { ok: false, reason: `pointer read back as ${JSON.stringify(back)}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* This run's Ship Sign-off row that says `ship`, or null.
|
|
80
|
+
*
|
|
81
|
+
* Read off the gate ledger, keyed by the run id the receipt carries: a sign-off of an earlier run of the
|
|
82
|
+
* same feature is not this run's, and a row that stopped for the PO (`ask`) or was answered otherwise is
|
|
83
|
+
* not a sign-off. With no readable receipt (no run id) any ship row of the slug counts — there is nothing
|
|
84
|
+
* to key by, and refusing would hold a run to a key it never had.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} cwd - Project root.
|
|
87
|
+
* @param {string} slug - Feature slug.
|
|
88
|
+
* @returns {(object|null)} The latest qualifying row, or null when none exists.
|
|
89
|
+
*/
|
|
90
|
+
export function shipSignOff(cwd, slug) {
|
|
91
|
+
const runId = readRunId(cwd, slug);
|
|
92
|
+
let text = "";
|
|
93
|
+
try { text = readFileSync(gatesPath(cwd, slug), "utf8"); } catch { return null; }
|
|
94
|
+
let found = null;
|
|
95
|
+
for (const line of text.split("\n")) {
|
|
96
|
+
let row; try { row = JSON.parse(line); } catch { continue; }
|
|
97
|
+
if (row?.gate === "L4" && row.status === "ok" && row.decision === "ship" && (!runId || row.run_id === runId)) found = row;
|
|
98
|
+
}
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Retire both pointers and record the ending.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} cwd - Project root.
|
|
106
|
+
* @param {string} slug - Feature slug.
|
|
107
|
+
* @param {string} status - One of {@link CLOSE_STATUSES}.
|
|
108
|
+
* @param {string} [cause] - Why the run ended.
|
|
109
|
+
* @param {object} [extra] - Further fields of the record, e.g. the sign-off a `shipped` ending rests on.
|
|
110
|
+
* @returns {{status: string, cause: (string|null), at: string}} The recorded close.
|
|
111
|
+
*/
|
|
112
|
+
export function closeRun(cwd, slug, status, cause, extra = {}) {
|
|
113
|
+
const close = { status, cause: cause || null, at: new Date().toISOString(), ...extra };
|
|
114
|
+
const dir = localRoot(cwd, slug);
|
|
115
|
+
mkdirSync(dir, { recursive: true });
|
|
116
|
+
writeFileSync(join(dir, "close.json"), `${JSON.stringify(close, null, 2)}\n`);
|
|
117
|
+
rmSync(activeOrder(cwd), { force: true });
|
|
118
|
+
rmSync(activeScope(cwd), { force: true });
|
|
119
|
+
return close;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {string[]} rawArgv - `open` or `close`, then flags.
|
|
124
|
+
* @returns {void} Exits with the documented code.
|
|
125
|
+
*/
|
|
126
|
+
export function cli(rawArgv) {
|
|
127
|
+
const [action, ...rest] = rawArgv;
|
|
128
|
+
const args = runArgs(ARGV_SPEC, rest);
|
|
129
|
+
const cwd = args.cwd || process.cwd();
|
|
130
|
+
if (insideRunTrace(cwd)) { console.error(`✋ order: ${resolve(cwd)} is inside a run trace (.shapeup/); run it from the project root.`); process.exit(2); }
|
|
131
|
+
if (action === "open") {
|
|
132
|
+
if (!args.order) { console.error("order open: --order <path> is required"); process.exit(2); }
|
|
133
|
+
const r = openOrder(cwd, args.slug, resolve(cwd, args.order));
|
|
134
|
+
if (!r.ok) { console.error(`✋ order open: ${r.reason}`); process.exit(1); }
|
|
135
|
+
console.log(JSON.stringify({ ok: true, slug: args.slug, order: args.order }));
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
if (action === "close") {
|
|
139
|
+
if (!args.status) { console.error("order close: --status is required"); process.exit(2); }
|
|
140
|
+
let extra = {};
|
|
141
|
+
if (args.status === "shipped") {
|
|
142
|
+
const row = shipSignOff(cwd, args.slug);
|
|
143
|
+
if (!row) {
|
|
144
|
+
const runId = readRunId(cwd, args.slug);
|
|
145
|
+
console.error(`✋ order close: no Ship Sign-off row says "ship" for ${runId ? `run ${runId}` : `slug ${args.slug}`}, so the run ` +
|
|
146
|
+
`cannot be recorded as shipped. Resolve GATE L4 first (harness.mjs gate --resolve L4 --slug ${args.slug} …), or close it as ` +
|
|
147
|
+
`escalated or aborted, which need no sign-off.`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
extra = { signed_off_at: row.at ?? null, signed_off_by: row.source ?? null };
|
|
151
|
+
}
|
|
152
|
+
console.log(JSON.stringify({ ok: true, ...closeRun(cwd, args.slug, args.status, args.cause, extra) }));
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
console.error("usage: harness.mjs order open|close …");
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|
package/kernel/probe/digest.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
// AEGIS digester — compresses a failed verification into the triples the next attempt reads.
|
|
3
2
|
//
|
|
4
3
|
// Distills raw build/test/Playwright logs into {file, line, core_message} triples so a
|
|
@@ -10,9 +9,8 @@
|
|
|
10
9
|
// diagnostic that names a file but no line number (a resource-compiler error, for example)
|
|
11
10
|
// still yields its file — `line` stays null rather than being guessed at.
|
|
12
11
|
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
import { runArgs } from "../lib/argv.mjs";
|
|
12
|
+
// A library, not a subcommand: `verify t0` imports `digest` to compress what a red
|
|
13
|
+
// run printed. Zero dependencies, zero network — same discipline as oracles/*.
|
|
16
14
|
|
|
17
15
|
const PATTERNS = [
|
|
18
16
|
// Node stack frame: " at fn (path/to/file.js:12:34)" or " at path/to/file.js:12:34"
|
|
@@ -118,37 +116,3 @@ export function digest(rawText) {
|
|
|
118
116
|
return true;
|
|
119
117
|
});
|
|
120
118
|
}
|
|
121
|
-
|
|
122
|
-
// --- CLI ---------------------------------------------------------------------
|
|
123
|
-
/** The typed argv contract (see `./lib/argv.mjs`). A bare `-`, or nothing, means stdin. */
|
|
124
|
-
export const ARGV_SPEC = {
|
|
125
|
-
usage: "harness.mjs probe digest [<log-file>|-] (no file → stdin)",
|
|
126
|
-
_: { arity: 0, max: 1, name: "log-file" },
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Digest a build log into AEGIS triples on stdout.
|
|
131
|
-
*
|
|
132
|
-
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
133
|
-
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
134
|
-
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
135
|
-
*/
|
|
136
|
-
export async function cli(rawArgv) {
|
|
137
|
-
const arg = runArgs(ARGV_SPEC, rawArgv)._[0];
|
|
138
|
-
let raw;
|
|
139
|
-
if (arg && arg !== "-") {
|
|
140
|
-
const { readFileSync } = await import("node:fs");
|
|
141
|
-
raw = readFileSync(arg, "utf8");
|
|
142
|
-
} else {
|
|
143
|
-
raw = await new Promise((resolve) => {
|
|
144
|
-
let d = "";
|
|
145
|
-
process.stdin.on("data", (c) => (d += c));
|
|
146
|
-
process.stdin.on("end", () => resolve(d));
|
|
147
|
-
process.stdin.on("error", () => resolve(""));
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
const triples = digest(raw);
|
|
151
|
-
console.log(JSON.stringify(triples, null, 2));
|
|
152
|
-
process.exit(0);
|
|
153
|
-
}
|
|
154
|
-
|