@skeletiq/mcp 0.2.0 → 0.4.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/README.md +5 -4
- package/dist/index.js +522 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,8 +85,8 @@ change your provider keys, see billing, or delete your account, whatever scopes
|
|
|
85
85
|
| Tool | What it does |
|
|
86
86
|
|---|---|
|
|
87
87
|
| `list_projects` | Find a project by name. Returns the candidates rather than guessing between them. |
|
|
88
|
-
| `get_design` | Read a design, in one of six modes: `overview`, `component`, `brief`, `readiness`, `build_order`, `gaps`. `overview` and `component` resolve the design's `R`-ids to the requirements they name, and what became of each. |
|
|
89
|
-
| `generate_architecture` | Design a system from a prompt. Spends credits and takes minutes. |
|
|
88
|
+
| `get_design` | Read a design, in one of six modes: `overview`, `component`, `brief`, `readiness`, `build_order`, `gaps`. Where a version records its requirements, `overview` and `component` resolve the design's `R`-ids to the requirements they name, and say what became of each. |
|
|
89
|
+
| `generate_architecture` | Design a system from a prompt. Spends credits and takes minutes. A prompt that states no load comes back with a question instead, and nothing is charged: ask your human, and answer "Not sure yet" when they do not know rather than guessing. |
|
|
90
90
|
| `get_generation_status` | Poll a generation started with `wait: false`. |
|
|
91
91
|
| `critique_architecture` | Check a design against SkeletIQ's rules. Deterministic, free, stores nothing. Tell it the `domain` and the `exposure`. |
|
|
92
92
|
| `check_drift` | Report what you built; hear what is missing, half done, or not in the design. |
|
|
@@ -114,8 +114,9 @@ unknown ids with suggestions, they are suggestions — put them to a person rath
|
|
|
114
114
|
mapping.
|
|
115
115
|
|
|
116
116
|
**`critique_architecture`'s optional inputs are not neutral.** Omitting one does not skip a
|
|
117
|
-
question; it answers it. With no `domain
|
|
118
|
-
|
|
117
|
+
question; it answers it. With no `domain`, no compliance framework applies (`secondary_domains`
|
|
118
|
+
select none — they add that domain's design checks as advice — so name a framework when it must
|
|
119
|
+
apply), so no compliance finding is possible and the score comes back higher than the SkeletIQ app shows
|
|
119
120
|
for the same design — by up to 15 points. With no `exposure`, the design is assessed as
|
|
120
121
|
internet-facing, which is how an air-gapped system gets told to add a CDN and a WAF. The response
|
|
121
122
|
says what was actually used — `frameworks_checked` and `exposure_assessed` — and the text output
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
var SERVER_NAME = "skeletiq";
|
|
8
|
-
var SERVER_VERSION = "0.
|
|
8
|
+
var SERVER_VERSION = "0.4.0";
|
|
9
9
|
var DEFAULT_API_URL = "https://api.skeletiq.com";
|
|
10
10
|
var TOKEN_PREFIX = "skq_";
|
|
11
11
|
var ConfigError = class extends Error {
|
|
@@ -60,12 +60,12 @@ var SkeletiqApiError = class extends Error {
|
|
|
60
60
|
/**
|
|
61
61
|
* A `GET` may be retried after a 429; a `POST` that may have spent credits may not.
|
|
62
62
|
*
|
|
63
|
-
* A spent daily allowance is the
|
|
64
|
-
* measured in hours, so a caller backing off and trying
|
|
65
|
-
* call that cannot succeed today.
|
|
63
|
+
* A spent daily allowance is the 429 that is *not* retryable — the account's, or a runtime's
|
|
64
|
+
* shared one: each refills on a clock measured in hours, so a caller backing off and trying
|
|
65
|
+
* again is burning its budget on a call that cannot succeed today.
|
|
66
66
|
*/
|
|
67
67
|
get isRetryable() {
|
|
68
|
-
if (this.code === "DAILY_LIMIT_REACHED") return false;
|
|
68
|
+
if (this.code === "DAILY_LIMIT_REACHED" || this.code === "RUNTIME_ALLOWANCE_SPENT") return false;
|
|
69
69
|
return this.status === 429 || this.code === "IDEMPOTENCY_UNAVAILABLE";
|
|
70
70
|
}
|
|
71
71
|
};
|
|
@@ -138,6 +138,12 @@ function describeApiError(error) {
|
|
|
138
138
|
if (upgradeUrl) lines.push(`A larger plan raises the daily limit: ${upgradeUrl}`);
|
|
139
139
|
break;
|
|
140
140
|
}
|
|
141
|
+
case "RUNTIME_ALLOWANCE_SPENT": {
|
|
142
|
+
lines.push(error.message);
|
|
143
|
+
const resetAt = asString(detail?.reset_at);
|
|
144
|
+
if (resetAt) lines.push(`The allowance resets at ${resetAt}.`);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
141
147
|
case "PLAN_LIMIT_EXCEEDED":
|
|
142
148
|
case "FEATURE_NOT_AVAILABLE":
|
|
143
149
|
case "VERSION_LOCKED": {
|
|
@@ -331,10 +337,49 @@ var RequirementLedgerRowSchema = z.looseObject({
|
|
|
331
337
|
* value added server-side must not stop an older connector parsing the row. */
|
|
332
338
|
status: z.string().optional(),
|
|
333
339
|
verifier_checked: z.boolean().optional(),
|
|
340
|
+
/** Which check answered this line: `simulation` | `cost` | `rules` | `structure` | `verifier`.
|
|
341
|
+
* Four are deterministic; `verifier` is the model check, and it only owns a row when it gave a
|
|
342
|
+
* reason. A string rather than a union, for the reason `status` is one. Absent from older APIs. */
|
|
343
|
+
check_method: z.string().nullable().optional(),
|
|
344
|
+
/** What that check said: `fails` | `passes` | `no_gap_found` | `not_checkable` | `not_checked`.
|
|
345
|
+
* `passes` is only ever a measurement; `no_gap_found` is the weaker "nothing contradicted it".
|
|
346
|
+
* Reported beside `status`, which stays populated. Absent from older APIs. */
|
|
347
|
+
verdict: z.string().nullable().optional(),
|
|
348
|
+
/** Why, in one sentence, with the figure or the finding behind it. */
|
|
349
|
+
verdict_reason: z.string().nullable().optional(),
|
|
350
|
+
/** The model behind a `verifier` verdict. Null on every deterministic row — naming a model
|
|
351
|
+
* beside a number the server computed would be a false attribution. */
|
|
352
|
+
checked_by: z.string().nullable().optional(),
|
|
353
|
+
/** Whether this row holds a handoff back: a `fails` a measurement reached (capacity, cost, the
|
|
354
|
+
* review's rules). A model check's `fails` does not block. Absent from older APIs, where
|
|
355
|
+
* `requirementBlocksHandoff` falls back to the method. */
|
|
356
|
+
blocks_handoff: z.boolean().optional(),
|
|
357
|
+
/** Why a `fails` does not block: `model_check` — only the model check failed it, nothing
|
|
358
|
+
* measured it; `unstated_input` — a shortfall that turns on an input nobody gave (an unstated
|
|
359
|
+
* split, or an assumed load — told apart by `unstated_input`); `advisory_finding` — only review
|
|
360
|
+
* advice failed it. Null on every other row. A string, for the reason `status` is one. Absent
|
|
361
|
+
* from older APIs. */
|
|
362
|
+
unconfirmed_reason: z.string().nullable().optional(),
|
|
363
|
+
/** True on a review-rules `fails` whose every finding is advice (`advisory_finding`). */
|
|
364
|
+
advisory_only: z.boolean().optional(),
|
|
365
|
+
/** The assumption an `unstated_input` row turns on, as a clause. Absent from older APIs. */
|
|
366
|
+
unstated_input: z.string().nullable().optional(),
|
|
334
367
|
cited_by_components: z.array(z.string()).nullable().optional(),
|
|
335
368
|
/** Indices into `architecture_json.grounded_decisions`, the raw array — blanks are not compacted. */
|
|
336
369
|
cited_by_decisions: z.array(z.number()).nullable().optional(),
|
|
337
|
-
added_in_version: z.number().nullable().optional()
|
|
370
|
+
added_in_version: z.number().nullable().optional(),
|
|
371
|
+
/** Set only on a row under `RequirementLedger.superseded`: an instruction an earlier analysis
|
|
372
|
+
* recorded as a requirement, now the change request it names (`CR5`). Absent from older APIs. */
|
|
373
|
+
superseded_by_change_request: z.string().nullable().optional(),
|
|
374
|
+
superseded_in_version: z.number().nullable().optional(),
|
|
375
|
+
/** What a person said about this line: `confirmed` ("yes, that one is real") or `removed`
|
|
376
|
+
* ("that is not a requirement"). Null or absent means nobody has said, which is neither. A
|
|
377
|
+
* `removed` row is listed under `RequirementLedger.removed` and never in `rows`, so a design
|
|
378
|
+
* is not held to it; it keeps its id so a citation naming it still resolves. `confirmed`
|
|
379
|
+
* changes no count and no verdict. Absent from older APIs. */
|
|
380
|
+
decision: z.string().nullable().optional(),
|
|
381
|
+
/** The version that was on screen when that decision was recorded. */
|
|
382
|
+
decided_in_version: z.number().nullable().optional()
|
|
338
383
|
});
|
|
339
384
|
var RequirementLedgerCountsSchema = z.looseObject({
|
|
340
385
|
total: z.number().optional(),
|
|
@@ -343,10 +388,33 @@ var RequirementLedgerCountsSchema = z.looseObject({
|
|
|
343
388
|
unmet: z.number().optional(),
|
|
344
389
|
uncited: z.number().optional(),
|
|
345
390
|
unchecked: z.number().optional(),
|
|
346
|
-
cited: z.number().optional()
|
|
391
|
+
cited: z.number().optional(),
|
|
392
|
+
/** Rows citing no component and no decision (overlaps `unmet`). Absent from older APIs. */
|
|
393
|
+
untraced: z.number().optional(),
|
|
394
|
+
/** The verdict partition, which sums to `total` exactly as the status partition above does —
|
|
395
|
+
* two readings of one set of rows, not two sets. Absent from older APIs. */
|
|
396
|
+
fails: z.number().optional(),
|
|
397
|
+
passes: z.number().optional(),
|
|
398
|
+
no_gap_found: z.number().optional(),
|
|
399
|
+
not_checkable: z.number().optional(),
|
|
400
|
+
not_checked: z.number().optional(),
|
|
401
|
+
/** Rows a person has confirmed are real requirements. In no partition — it overlaps all of
|
|
402
|
+
* them, because confirming a line says nothing about whether the design meets it. */
|
|
403
|
+
confirmed: z.number().optional(),
|
|
404
|
+
/** Ids a person said are **not** requirements. Not part of `total`: those rows are not in
|
|
405
|
+
* `rows` at all. Absent from older APIs. */
|
|
406
|
+
removed: z.number().optional()
|
|
347
407
|
});
|
|
348
408
|
var RequirementLedgerSchema = z.looseObject({
|
|
349
409
|
rows: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
410
|
+
/** Ids that were instructions, not requirements. Counted nowhere; listed so an id a component
|
|
411
|
+
* or decision still cites resolves to what became of it. Absent from older APIs. */
|
|
412
|
+
superseded: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
413
|
+
/** Ids a person said are not requirements, so the design is not held to them. Kept apart from
|
|
414
|
+
* `superseded` because it is a different statement by a different author. Counted nowhere;
|
|
415
|
+
* listed for the same reason, so a citation naming one still resolves. Absent from older
|
|
416
|
+
* APIs. */
|
|
417
|
+
removed: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
350
418
|
analysis_version: z.number().nullable().optional(),
|
|
351
419
|
analysis_degraded: z.boolean().optional(),
|
|
352
420
|
/** `grounded` | `not_grounded` | `diverged`. */
|
|
@@ -400,6 +468,12 @@ var BuildOrderSchema = z.looseObject({
|
|
|
400
468
|
steps: z.array(BuildStepSchema),
|
|
401
469
|
release: ReleaseFactsSchema
|
|
402
470
|
});
|
|
471
|
+
var UnavailableSourceSchema = z.looseObject({
|
|
472
|
+
source: z.string(),
|
|
473
|
+
reason: z.string(),
|
|
474
|
+
/** `failed` | `missing`. A string, not a union: a value added server-side must still parse. */
|
|
475
|
+
kind: z.string().optional()
|
|
476
|
+
});
|
|
403
477
|
var ReadinessRowSchema = z.looseObject({
|
|
404
478
|
key: z.string(),
|
|
405
479
|
label: z.string(),
|
|
@@ -409,7 +483,13 @@ var ReadinessRowSchema = z.looseObject({
|
|
|
409
483
|
state: z.string(),
|
|
410
484
|
category: z.string(),
|
|
411
485
|
gating: z.boolean(),
|
|
412
|
-
detail: z.string().nullable().optional()
|
|
486
|
+
detail: z.string().nullable().optional(),
|
|
487
|
+
/** On a row that could not be answered: what would answer it ("Saving a change runs the
|
|
488
|
+
* check on the new version."). Absent on a row that has an answer. */
|
|
489
|
+
clears: z.string().nullable().optional(),
|
|
490
|
+
/** True when stating the expected load is what would answer this row — `clears` then says
|
|
491
|
+
* how. Optional so a server that predates the field still parses. */
|
|
492
|
+
clears_by_load: z.boolean().optional()
|
|
413
493
|
});
|
|
414
494
|
var ReadinessSchema = z.looseObject({
|
|
415
495
|
architecture_id: z.string(),
|
|
@@ -438,7 +518,16 @@ var ReadinessSchema = z.looseObject({
|
|
|
438
518
|
* Optional here only so a server that predates the field still parses — it is required on the
|
|
439
519
|
* wire, and an empty array is the server saying it looked and found nothing.
|
|
440
520
|
*/
|
|
441
|
-
release_warnings: z.array(z.string()).optional()
|
|
521
|
+
release_warnings: z.array(z.string()).optional(),
|
|
522
|
+
/**
|
|
523
|
+
* Sources this checklist could not speak for, exactly as `/todo` reports them.
|
|
524
|
+
*
|
|
525
|
+
* A failed read reaches each row builder as that source's own empty value — no questions, no
|
|
526
|
+
* decisions, no ledger — and every one of those empties reads as *clear*, so an outage could
|
|
527
|
+
* answer `ready: true` with `release_warnings: []`. Each one also has a row of its own now;
|
|
528
|
+
* this is the same fact in the shape a caller showing both lists can join on.
|
|
529
|
+
*/
|
|
530
|
+
unavailable_sources: z.array(UnavailableSourceSchema).optional()
|
|
442
531
|
});
|
|
443
532
|
var DesignGapSchema = z.looseObject({
|
|
444
533
|
gap_id: z.string(),
|
|
@@ -447,7 +536,21 @@ var DesignGapSchema = z.looseObject({
|
|
|
447
536
|
resolved: z.boolean(),
|
|
448
537
|
action: z.string().nullable().optional(),
|
|
449
538
|
note: z.string().nullable().optional(),
|
|
450
|
-
adr_id: z.string().nullable().optional()
|
|
539
|
+
adr_id: z.string().nullable().optional(),
|
|
540
|
+
/** `gap:{gap_id}` — the identity the review, the advisor and the decision suggestions share,
|
|
541
|
+
* and the key a dismissal is recorded against. Absent from servers before 0.3.0. */
|
|
542
|
+
problem_key: z.string().optional(),
|
|
543
|
+
/** An answer is recorded and this version was not built with it, so iterating the design
|
|
544
|
+
* would change something. False once a generation has been handed the answer. */
|
|
545
|
+
answer_pending: z.boolean().optional(),
|
|
546
|
+
/** The read/write mix or peak this assumption states that the design is not sized on. Null when
|
|
547
|
+
* the two agree; absent from older servers. */
|
|
548
|
+
sizing_conflict: z.looseObject({
|
|
549
|
+
dimension: z.string(),
|
|
550
|
+
assumed: z.string(),
|
|
551
|
+
sized: z.string(),
|
|
552
|
+
resize_to: z.string().nullable().optional()
|
|
553
|
+
}).nullable().optional()
|
|
451
554
|
});
|
|
452
555
|
var OrphanedAnswerSchema = z.looseObject({
|
|
453
556
|
gap_id: z.string(),
|
|
@@ -465,29 +568,6 @@ var DesignGapListSchema = z.looseObject({
|
|
|
465
568
|
/** Answers whose question is not in this version — recorded, not silently dropped. */
|
|
466
569
|
orphaned_answers: z.array(OrphanedAnswerSchema).optional()
|
|
467
570
|
});
|
|
468
|
-
var ComponentRefSchema = z.looseObject({
|
|
469
|
-
id: z.string(),
|
|
470
|
-
name: z.string(),
|
|
471
|
-
type: z.string()
|
|
472
|
-
});
|
|
473
|
-
var UnknownIdSchema = z.looseObject({
|
|
474
|
-
id: z.string(),
|
|
475
|
-
suggestions: z.array(ComponentRefSchema).optional()
|
|
476
|
-
});
|
|
477
|
-
var DriftSchema = z.looseObject({
|
|
478
|
-
architecture_id: z.string(),
|
|
479
|
-
version: z.number(),
|
|
480
|
-
in_sync: z.boolean(),
|
|
481
|
-
missing: z.array(ComponentRefSchema),
|
|
482
|
-
unreported: z.array(ComponentRefSchema),
|
|
483
|
-
partial: z.array(ComponentRefSchema),
|
|
484
|
-
elsewhere: z.array(ComponentRefSchema),
|
|
485
|
-
unknown_ids: z.array(UnknownIdSchema),
|
|
486
|
-
extra_components: z.array(z.looseObject({ name: z.string() })),
|
|
487
|
-
extra_connections: z.array(z.looseObject({ source: z.string(), target: z.string() })),
|
|
488
|
-
counts: z.record(z.string(), z.number()),
|
|
489
|
-
release: ReleaseFactsSchema
|
|
490
|
-
});
|
|
491
571
|
var CritiqueFindingSchema = z.looseObject({
|
|
492
572
|
message: z.string(),
|
|
493
573
|
pattern_id: z.string(),
|
|
@@ -495,7 +575,17 @@ var CritiqueFindingSchema = z.looseObject({
|
|
|
495
575
|
severity: z.string().optional(),
|
|
496
576
|
affected_components: z.array(z.string()).optional(),
|
|
497
577
|
evidence: z.string().optional(),
|
|
498
|
-
remediation: z.string().optional()
|
|
578
|
+
remediation: z.string().optional(),
|
|
579
|
+
/** Reported in full and never charged against the score — advice, not a problem to fix. */
|
|
580
|
+
advisory_only: z.boolean().optional(),
|
|
581
|
+
/**
|
|
582
|
+
* The problem this finding is about. Stable across rewording, and shared with the advisor
|
|
583
|
+
* suggestion and decision record raised by the same problem — the key a dismissal is recorded
|
|
584
|
+
* against.
|
|
585
|
+
*/
|
|
586
|
+
problem_key: z.string().optional(),
|
|
587
|
+
/** The project set this problem aside, with a reason. Still reported; never set on an unsaved payload. */
|
|
588
|
+
dismissed: z.boolean().optional()
|
|
499
589
|
});
|
|
500
590
|
var PayloadCritiqueSchema = z.looseObject({
|
|
501
591
|
architecture_score: z.number(),
|
|
@@ -513,13 +603,119 @@ var PayloadCritiqueSchema = z.looseObject({
|
|
|
513
603
|
compliance_note: z.string().nullable().optional(),
|
|
514
604
|
exposure_assessed: z.string().optional()
|
|
515
605
|
});
|
|
606
|
+
var TodoDecisionContextSchema = z.looseObject({
|
|
607
|
+
context: z.string().nullable().optional(),
|
|
608
|
+
consequences: z.string().nullable().optional(),
|
|
609
|
+
alternatives: z.string().nullable().optional(),
|
|
610
|
+
/** `records` | `open` | `tradeoff`. A string, not a union: a value added server-side must still parse. */
|
|
611
|
+
disposition: z.string(),
|
|
612
|
+
supersedes_adr_id: z.string().nullable().optional(),
|
|
613
|
+
source_version: z.number()
|
|
614
|
+
});
|
|
615
|
+
var TodoSourceSchema = z.looseObject({
|
|
616
|
+
source: z.string(),
|
|
617
|
+
message: z.string(),
|
|
618
|
+
detail: z.string().nullable().optional(),
|
|
619
|
+
/** A handle to act on through that source: a suggestion id, a gap id, an R-id. */
|
|
620
|
+
reference_id: z.string().nullable().optional(),
|
|
621
|
+
/** Present on a `critique` or `compliance` source; null on every other kind. */
|
|
622
|
+
finding: CritiqueFindingSchema.nullable().optional(),
|
|
623
|
+
/** Present on a `decisions` source; null on every other kind. */
|
|
624
|
+
decision: TodoDecisionContextSchema.nullable().optional()
|
|
625
|
+
});
|
|
626
|
+
var TodoActionSchema = z.looseObject({
|
|
627
|
+
action: z.string(),
|
|
628
|
+
available: z.boolean(),
|
|
629
|
+
reason: z.string().nullable().optional(),
|
|
630
|
+
reference_id: z.string().nullable().optional()
|
|
631
|
+
});
|
|
632
|
+
var TodoItemSchema = z.looseObject({
|
|
633
|
+
problem_key: z.string(),
|
|
634
|
+
group: z.string(),
|
|
635
|
+
/** `problem` | `question` | `assumption` | `decision` | `verdict`. A string, not a union: a
|
|
636
|
+
* value added server-side must not stop an older connector parsing the card. */
|
|
637
|
+
kind: z.string(),
|
|
638
|
+
title: z.string(),
|
|
639
|
+
severity: z.string(),
|
|
640
|
+
detail: z.string().nullable().optional(),
|
|
641
|
+
components: z.array(z.string()).optional(),
|
|
642
|
+
sources: z.array(TodoSourceSchema).optional(),
|
|
643
|
+
actions: z.array(TodoActionSchema).optional(),
|
|
644
|
+
carried: z.boolean().optional(),
|
|
645
|
+
/** The oldest earlier version known to have had this problem. Null with `carried: true` means
|
|
646
|
+
* it goes back further than the server looked — "an earlier version", never version zero. */
|
|
647
|
+
carried_from_version: z.number().nullable().optional(),
|
|
648
|
+
dismissed: z.boolean().optional(),
|
|
649
|
+
dismissed_reason: z.string().nullable().optional(),
|
|
650
|
+
/** The key of the one readiness row this card is counted on, so the readiness counts can be
|
|
651
|
+
* traced to cards. Null on a card no row counts. */
|
|
652
|
+
counts_toward: z.string().nullable().optional()
|
|
653
|
+
});
|
|
654
|
+
var TodoGroupSchema = z.looseObject({
|
|
655
|
+
key: z.string(),
|
|
656
|
+
label: z.string(),
|
|
657
|
+
items: z.array(TodoItemSchema),
|
|
658
|
+
bulk_action: TodoActionSchema.nullable().optional()
|
|
659
|
+
});
|
|
660
|
+
var TodoSchema = z.looseObject({
|
|
661
|
+
architecture_id: z.string(),
|
|
662
|
+
version: z.number(),
|
|
663
|
+
groups: z.array(TodoGroupSchema),
|
|
664
|
+
dismissed: z.array(TodoItemSchema).optional(),
|
|
665
|
+
counts: z.record(z.string(), z.number()).optional(),
|
|
666
|
+
/** Rows the sources raised before grouping. The gap between this and `counts.total` is the
|
|
667
|
+
* duplication the endpoint removes, reported so the claim stays checkable. */
|
|
668
|
+
raw_row_count: z.number().optional(),
|
|
669
|
+
unavailable_sources: z.array(UnavailableSourceSchema).optional(),
|
|
670
|
+
generation_running: z.boolean().optional(),
|
|
671
|
+
can_edit: z.boolean().optional(),
|
|
672
|
+
/** The project's newest version. Above `version` when this list is for an older one — a
|
|
673
|
+
* release resolved behind newer drafts, or a version asked for by number. Null when unread. */
|
|
674
|
+
latest_version: z.number().nullable().optional()
|
|
675
|
+
});
|
|
676
|
+
var ComponentRefSchema = z.looseObject({
|
|
677
|
+
id: z.string(),
|
|
678
|
+
name: z.string(),
|
|
679
|
+
type: z.string()
|
|
680
|
+
});
|
|
681
|
+
var UnknownIdSchema = z.looseObject({
|
|
682
|
+
id: z.string(),
|
|
683
|
+
suggestions: z.array(ComponentRefSchema).optional()
|
|
684
|
+
});
|
|
685
|
+
var DriftSchema = z.looseObject({
|
|
686
|
+
architecture_id: z.string(),
|
|
687
|
+
version: z.number(),
|
|
688
|
+
in_sync: z.boolean(),
|
|
689
|
+
missing: z.array(ComponentRefSchema),
|
|
690
|
+
unreported: z.array(ComponentRefSchema),
|
|
691
|
+
partial: z.array(ComponentRefSchema),
|
|
692
|
+
elsewhere: z.array(ComponentRefSchema),
|
|
693
|
+
unknown_ids: z.array(UnknownIdSchema),
|
|
694
|
+
extra_components: z.array(z.looseObject({ name: z.string() })),
|
|
695
|
+
extra_connections: z.array(z.looseObject({ source: z.string(), target: z.string() })),
|
|
696
|
+
counts: z.record(z.string(), z.number()),
|
|
697
|
+
release: ReleaseFactsSchema
|
|
698
|
+
});
|
|
699
|
+
var ClarifyingOptionDetailSchema = z.looseObject({
|
|
700
|
+
value: z.string(),
|
|
701
|
+
label: z.string(),
|
|
702
|
+
detail: z.string().optional()
|
|
703
|
+
});
|
|
516
704
|
var ClarifyingQuestionSchema = z.looseObject({
|
|
517
705
|
id: z.string(),
|
|
518
706
|
question: z.string(),
|
|
519
707
|
why: z.string().optional(),
|
|
520
708
|
kind: z.string().optional(),
|
|
521
709
|
options: z.array(z.string()).nullable().optional(),
|
|
522
|
-
decline_options: z.array(z.string()).nullable().optional()
|
|
710
|
+
decline_options: z.array(z.string()).nullable().optional(),
|
|
711
|
+
/** Options that withdraw the instruction to be rewritten, not answer it ("I'll name the parts"). */
|
|
712
|
+
dismiss_options: z.array(z.string()).nullable().optional(),
|
|
713
|
+
/** Each option's label and what it means, keyed by the value in `options`. */
|
|
714
|
+
option_details: z.array(ClarifyingOptionDetailSchema).nullable().optional(),
|
|
715
|
+
/** Whether more than one option may be picked at once. */
|
|
716
|
+
multiple: z.boolean().optional(),
|
|
717
|
+
/** What leaving the question unanswered does, in the product's words. */
|
|
718
|
+
skip_effect: z.string().nullable().optional()
|
|
523
719
|
});
|
|
524
720
|
var IntentDecisionSchema = z.looseObject({
|
|
525
721
|
intent: z.string().optional(),
|
|
@@ -581,7 +777,9 @@ var PROJECT_PAGE_SIZE = 50;
|
|
|
581
777
|
var ARCHITECTURE_PAGE_SIZE = 100;
|
|
582
778
|
var DesignResolver = class {
|
|
583
779
|
client;
|
|
780
|
+
/** Version summaries per project — enough to choose a version, never to describe one. */
|
|
584
781
|
versions = /* @__PURE__ */ new Map();
|
|
782
|
+
/** Full records from the single-version read, the requirements ledger included. */
|
|
585
783
|
designs = /* @__PURE__ */ new Map();
|
|
586
784
|
constructor(client) {
|
|
587
785
|
this.client = client;
|
|
@@ -610,7 +808,11 @@ var DesignResolver = class {
|
|
|
610
808
|
if (exact.length === 1) return exact[0];
|
|
611
809
|
throw new AmbiguousProjectError(idOrQuery, candidates);
|
|
612
810
|
}
|
|
613
|
-
/**
|
|
811
|
+
/**
|
|
812
|
+
* Every version of a project, newest first. Cached: one call serves a whole session.
|
|
813
|
+
*
|
|
814
|
+
* These are summaries. They seed nothing in `designs` — see the file header for why.
|
|
815
|
+
*/
|
|
614
816
|
async versionsFor(projectId) {
|
|
615
817
|
const cached = this.versions.get(projectId);
|
|
616
818
|
if (cached) return cached;
|
|
@@ -619,7 +821,6 @@ var DesignResolver = class {
|
|
|
619
821
|
});
|
|
620
822
|
const items = [...ArchitecturePageSchema.parse(body).items].sort((a, b) => b.version - a.version);
|
|
621
823
|
this.versions.set(projectId, items);
|
|
622
|
-
for (const item of items) this.designs.set(item.id, item);
|
|
623
824
|
return items;
|
|
624
825
|
}
|
|
625
826
|
/** The version rule. */
|
|
@@ -659,10 +860,10 @@ var DesignResolver = class {
|
|
|
659
860
|
};
|
|
660
861
|
}
|
|
661
862
|
/**
|
|
662
|
-
* The full design for one version.
|
|
863
|
+
* The full design for one version, from the single-version read.
|
|
663
864
|
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
865
|
+
* One request per version a session actually describes, not one per version in the list:
|
|
866
|
+
* an overview and any number of component slices of the same version share it.
|
|
666
867
|
*/
|
|
667
868
|
async design(architectureId) {
|
|
668
869
|
const cached = this.designs.get(architectureId);
|
|
@@ -757,7 +958,9 @@ var input = z2.object({
|
|
|
757
958
|
target: z2.string().max(255),
|
|
758
959
|
protocol: z2.string().max(64).optional()
|
|
759
960
|
})
|
|
760
|
-
).max(500).optional().describe(
|
|
961
|
+
).max(500).optional().describe(
|
|
962
|
+
"Edges in the code the design does not have, each from the caller to the callee: the component that holds the client is the source, including a consumer reading a queue. Endpoints may be component ids or names."
|
|
963
|
+
)
|
|
761
964
|
});
|
|
762
965
|
var refs = z2.array(z2.object({ id: z2.string(), name: z2.string(), type: z2.string() }));
|
|
763
966
|
var output = z2.object({
|
|
@@ -872,7 +1075,7 @@ var input2 = z3.object({
|
|
|
872
1075
|
"The design's domain \u2014 e-commerce, fintech, healthcare, saas, social, iot, gaming, infrastructure, streaming, logistics, ai-ml, search, data-analytics, content-platform, internal-tool. Worth sending: the domain is what selects the compliance frameworks to check against, and without it none apply, so no compliance finding is possible and the score comes back higher than the SkeletIQ app would show for the same design \u2014 by up to 15 points."
|
|
873
1076
|
),
|
|
874
1077
|
secondary_domains: z3.array(z3.string()).max(8).optional().describe(
|
|
875
|
-
"Further domains the design spans, when it spans more than one \u2014 a multi-tenant shop that takes payments is e-commerce plus fintech and saas. These
|
|
1078
|
+
"Further domains the design spans, when it spans more than one \u2014 a multi-tenant shop that takes payments is e-commerce plus fintech and saas. These add the design checks that domain usually needs, as advice; they select no compliance frameworks. Only the primary domain's frameworks and the ones the user named are checked, so name a framework in the prompt when it must apply. frameworks_checked says which were used."
|
|
876
1079
|
),
|
|
877
1080
|
exposure: z3.enum(["public_internet", "private_network", "air_gapped"]).optional().describe(
|
|
878
1081
|
"Where the design runs. Worth sending: four checks \u2014 CDN, WAF, rate limiting and multi-region \u2014 ask whether traffic arriving from the public internet is handled safely, and they only apply to a system that takes any. Left unset, the design is assessed as internet-facing, which is why an air-gapped design comes back told to add a CDN. exposure_assessed says which exposure was actually used."
|
|
@@ -893,7 +1096,10 @@ var output2 = z3.object({
|
|
|
893
1096
|
category: z3.string(),
|
|
894
1097
|
message: z3.string(),
|
|
895
1098
|
affected_components: z3.array(z3.string()),
|
|
896
|
-
remediation: z3.string()
|
|
1099
|
+
remediation: z3.string(),
|
|
1100
|
+
advisory_only: z3.boolean().describe(
|
|
1101
|
+
"True for advice: reported and never charged against the score \u2014 a framework nobody named, or a check too uncertain to count. Not a problem to fix."
|
|
1102
|
+
)
|
|
897
1103
|
})
|
|
898
1104
|
),
|
|
899
1105
|
finding_count: z3.number(),
|
|
@@ -932,7 +1138,9 @@ function registerCritique(server, client) {
|
|
|
932
1138
|
category: finding.category,
|
|
933
1139
|
message: finding.message,
|
|
934
1140
|
affected_components: finding.affected_components ?? [],
|
|
935
|
-
remediation: finding.remediation ?? ""
|
|
1141
|
+
remediation: finding.remediation ?? "",
|
|
1142
|
+
// A server too old to send the flag predates it, and counted every finding.
|
|
1143
|
+
advisory_only: finding.advisory_only ?? false
|
|
936
1144
|
}));
|
|
937
1145
|
const complianceAssessed = critique.compliance_assessed ?? false;
|
|
938
1146
|
const exposureAssessed = critique.exposure_assessed ?? "public_internet";
|
|
@@ -966,8 +1174,12 @@ function registerCritique(server, client) {
|
|
|
966
1174
|
"",
|
|
967
1175
|
findings.length === 0 ? "No findings." : `${findings.length} finding(s):`,
|
|
968
1176
|
...findings.map(
|
|
969
|
-
(finding) =>
|
|
1177
|
+
(finding) => (
|
|
1178
|
+
// Said on the line, because a severity alone reads as a blocker: an
|
|
1179
|
+
// advisory critical is one the score and the app's verdict both ignore.
|
|
1180
|
+
`- [${finding.severity}${finding.advisory_only ? " \xB7 advisory, not scored" : ""}] ${finding.message}` + (finding.affected_components.length ? ` (${finding.affected_components.join(", ")})` : "") + (finding.remediation ? `
|
|
970
1181
|
Fix: ${finding.remediation}` : "")
|
|
1182
|
+
)
|
|
971
1183
|
)
|
|
972
1184
|
];
|
|
973
1185
|
return ok(structured, lines.join("\n"));
|
|
@@ -1041,9 +1253,10 @@ var PROGRESS_LABELS = {
|
|
|
1041
1253
|
refining: "Refining the design"
|
|
1042
1254
|
};
|
|
1043
1255
|
var PROGRESS_TOTAL = Object.keys(PROGRESS_LABELS).length + 1;
|
|
1256
|
+
var LOAD_NOT_SURE = "Not sure yet";
|
|
1044
1257
|
var PROVENANCE_LABELS = {
|
|
1045
|
-
user_supplied: "
|
|
1046
|
-
llm_inferred: "
|
|
1258
|
+
user_supplied: "you set this",
|
|
1259
|
+
llm_inferred: "read from your prompt",
|
|
1047
1260
|
tier_default: "assumed",
|
|
1048
1261
|
guardrail_clamped: "adjusted"
|
|
1049
1262
|
};
|
|
@@ -1054,16 +1267,19 @@ function asText(value) {
|
|
|
1054
1267
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1055
1268
|
return typeof value === "string" && value.trim() && value !== "unknown" ? value.trim() : void 0;
|
|
1056
1269
|
}
|
|
1270
|
+
var BARE_FIGURE = /^~?\d[\d,]*(\.\d+)?$/;
|
|
1057
1271
|
function frameDetail(frame) {
|
|
1058
1272
|
const data = asRecord2(frame.pipeline_step);
|
|
1059
1273
|
if (!data) return void 0;
|
|
1060
1274
|
const parts = [];
|
|
1061
1275
|
const domain = asText(data.domain);
|
|
1062
1276
|
if (domain) parts.push(domain);
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1277
|
+
const rawRps = asText(data.requests_per_second);
|
|
1278
|
+
const zeroRate = rawRps !== void 0 && Number(rawRps.replace(/[~,]/g, "")) === 0;
|
|
1279
|
+
const rps = zeroRate ? void 0 : rawRps;
|
|
1280
|
+
const figure = rps ? BARE_FIGURE.test(rps) ? `${rps}/s` : rps : asText(data.scale);
|
|
1065
1281
|
if (figure) {
|
|
1066
|
-
const source = asText(data.scale_source);
|
|
1282
|
+
const source = zeroRate ? void 0 : asText(data.scale_source);
|
|
1067
1283
|
const label = source ? PROVENANCE_LABELS[source] ?? source : void 0;
|
|
1068
1284
|
parts.push(label ? `${figure} (${label})` : figure);
|
|
1069
1285
|
}
|
|
@@ -1083,11 +1299,21 @@ var constraintsInput = z4.strictObject({
|
|
|
1083
1299
|
preferred_cloud: z4.enum(["aws", "gcp", "azure", "any"]).optional(),
|
|
1084
1300
|
preferred_technologies: z4.array(z4.string()).optional(),
|
|
1085
1301
|
max_components: z4.number().int().positive().optional(),
|
|
1086
|
-
read_write_mix: z4.enum(["read_heavy", "balanced", "write_heavy"]).optional().describe(
|
|
1087
|
-
|
|
1302
|
+
read_write_mix: z4.enum(["read_heavy", "balanced", "write_heavy"]).optional().describe(
|
|
1303
|
+
"Wins over any share stated in the prompt, which the server also reads. Unset and unstated, the design assumes 80% reads. It sizes components and weights read replicas, so on a read-heavy design it changes the instance counts and the cost."
|
|
1304
|
+
),
|
|
1305
|
+
burstiness: z4.enum(["steady", "daily_peaks", "spiky"]).optional().describe(
|
|
1306
|
+
'Wins over a peak the prompt states ("peaking at 2,000 per second"), which the server also reads. Unset and unstated, the design assumes a typical peak for its scale.'
|
|
1307
|
+
),
|
|
1088
1308
|
data_retention: z4.enum(["days", "months", "years", "indefinite"]).optional(),
|
|
1089
1309
|
consistency: z4.enum(["strict", "eventual", "mixed"]).optional(),
|
|
1090
|
-
data_residency: z4.enum(["any", "us", "eu"]).optional()
|
|
1310
|
+
data_residency: z4.enum(["any", "us", "eu"]).optional(),
|
|
1311
|
+
transfer_band: z4.enum(["light", "moderate", "heavy", "very_heavy", "massive"]).optional().describe(
|
|
1312
|
+
"How much data the system sends out to its users a month: light ~100 GB, moderate ~1 TB, heavy ~10 TB, very_heavy ~100 TB, massive ~1 PB. Priced at the cloud's published per-GB rates. Unset, data transfer is left out of the price and the estimate says so. Set this or monthly_transfer_gb, not both."
|
|
1313
|
+
),
|
|
1314
|
+
monthly_transfer_gb: z4.number().min(0).max(1e8).optional().describe(
|
|
1315
|
+
"The same thing as a figure: GB a month sent out to users. Set this or transfer_band, not both."
|
|
1316
|
+
)
|
|
1091
1317
|
});
|
|
1092
1318
|
var generateInput = z4.object({
|
|
1093
1319
|
prompt: z4.string().min(1).max(2e4).describe("What to design. Describe the system, its scale and its constraints in prose."),
|
|
@@ -1096,7 +1322,7 @@ var generateInput = z4.object({
|
|
|
1096
1322
|
"Default true: run inline and return the finished design (this takes several minutes). False queues it as a background job and returns a job_id to poll \u2014 not available on every deployment."
|
|
1097
1323
|
),
|
|
1098
1324
|
clarification_answers: z4.record(z4.string(), z4.string()).optional().describe(
|
|
1099
|
-
'Answers to the questions a previous call returned with status "clarification_required", keyed by their ids. Treated as authoritative requirements, so send what the user told you \u2014 ask them rather than guessing.'
|
|
1325
|
+
'Answers to the questions a previous call returned with status "clarification_required", keyed by their ids. Treated as authoritative requirements, so send what the user told you \u2014 ask them rather than guessing. "Not sure yet" is a real answer to the load question and to the data-sent-out question when they do not know the figure.'
|
|
1100
1326
|
),
|
|
1101
1327
|
constraints: constraintsInput.optional().describe(
|
|
1102
1328
|
"Facts about the design that the prose does not have to carry. Anything omitted is assumed by SkeletIQ, and the design says which values were assumed."
|
|
@@ -1112,6 +1338,12 @@ var generateOutput = z4.object({
|
|
|
1112
1338
|
component_count: z4.number().nullable(),
|
|
1113
1339
|
assistant_message: z4.string().nullable(),
|
|
1114
1340
|
degradations: z4.array(z4.string()),
|
|
1341
|
+
/**
|
|
1342
|
+
* The server's sentence when part of what the model was given had to be cut to fit its input
|
|
1343
|
+
* window — a source left out, or the current design summarised — and null otherwise. The design
|
|
1344
|
+
* is still complete; this says what it was written without.
|
|
1345
|
+
*/
|
|
1346
|
+
prompt_trim_note: z4.string().nullable(),
|
|
1115
1347
|
/**
|
|
1116
1348
|
* Present, and non-empty, only on `status: "clarification_required"`. A refusal is a
|
|
1117
1349
|
* *result* here rather than an error: an error result may carry no `structuredContent` at
|
|
@@ -1159,7 +1391,7 @@ function registerGenerate(server, client, resolver) {
|
|
|
1159
1391
|
"generate_architecture",
|
|
1160
1392
|
{
|
|
1161
1393
|
title: "Generate a SkeletIQ architecture",
|
|
1162
|
-
description:
|
|
1394
|
+
description: `Design a system architecture from a prompt. This spends the account holder's credits and takes several minutes, so do not call it speculatively \u2014 if a design already exists, read it with get_design instead. Which model runs it is the account holder's stored setting; you do not choose it. Describe the system, what it must do, the load it carries and the constraints it runs under. A prompt that names no system to design comes back as clarification_required, and so does one that states no load: a request rate ("about 300 requests a second"), a fleet (how many devices, and how often each one reports) or a volume ("2 million orders a day"). A headcount alone is not a load. Answer with clarification_answers, keyed by the question ids. Ask your human for the load first. If they do not know it, answer the load question "Not sure yet": the design is then sized on a typical load for its kind of system and marked assumed. Never guess a load or pick a band on their behalf \u2014 a design sized on an invented load is priced and judged against a number nobody chose.`,
|
|
1163
1395
|
inputSchema: generateInput,
|
|
1164
1396
|
outputSchema: generateOutput,
|
|
1165
1397
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -1203,7 +1435,7 @@ function registerGenerate(server, client, resolver) {
|
|
|
1203
1435
|
error: job.error ?? null,
|
|
1204
1436
|
queue_state: job.queue_state ?? null,
|
|
1205
1437
|
refusal_code: refusal?.code ?? null,
|
|
1206
|
-
clarifying_questions: questions
|
|
1438
|
+
clarifying_questions: questions.map(structuredQuestion)
|
|
1207
1439
|
};
|
|
1208
1440
|
const lines = refusal ? refusalLines(job.job_id, refusal, questions) : [`Generation ${job.job_id} is ${job.status}.`];
|
|
1209
1441
|
if (job.status === "completed" && job.project_id) {
|
|
@@ -1221,14 +1453,39 @@ function decisionRefusal(progress) {
|
|
|
1221
1453
|
return refusal.success ? refusal.data : void 0;
|
|
1222
1454
|
}
|
|
1223
1455
|
function refusalQuestions(refusal) {
|
|
1224
|
-
return (refusal?.clarifying_questions ?? []).map(
|
|
1456
|
+
return (refusal?.clarifying_questions ?? []).map(offeredQuestion);
|
|
1457
|
+
}
|
|
1458
|
+
function offeredQuestion(question) {
|
|
1459
|
+
const options = answerOptions(question);
|
|
1460
|
+
const details = new Map((question.option_details ?? []).map((detail) => [detail.value, detail]));
|
|
1461
|
+
return {
|
|
1225
1462
|
id: question.id,
|
|
1226
1463
|
question: question.question,
|
|
1227
1464
|
why: question.why ?? "",
|
|
1228
|
-
options
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1465
|
+
options,
|
|
1466
|
+
explained: options.flatMap((option) => {
|
|
1467
|
+
const detail = details.get(option);
|
|
1468
|
+
if (!detail) return [];
|
|
1469
|
+
const said = detail.label && detail.label !== option ? `${option} (${detail.label})` : option;
|
|
1470
|
+
return [detail.detail ? `${said}: ${detail.detail}` : said];
|
|
1471
|
+
}),
|
|
1472
|
+
skipEffect: question.skip_effect ?? ""
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
function structuredQuestion(question) {
|
|
1476
|
+
return { id: question.id, question: question.question, why: question.why, options: question.options };
|
|
1477
|
+
}
|
|
1478
|
+
function questionLines(questions) {
|
|
1479
|
+
return questions.flatMap((question) => [
|
|
1480
|
+
`${question.id}: ${question.question}`,
|
|
1481
|
+
...question.why ? [` Why it matters: ${question.why}`] : [],
|
|
1482
|
+
...question.explained.length > 0 ? [" Answers:", ...question.explained.map((line) => ` - ${line}`)] : question.options.length > 0 ? [` For example: ${question.options.join(" \xB7 ")}`] : [],
|
|
1483
|
+
...question.skipEffect ? [` If it is left unanswered: ${question.skipEffect}`] : []
|
|
1484
|
+
]);
|
|
1485
|
+
}
|
|
1486
|
+
function answerOptions(question) {
|
|
1487
|
+
const excluded = /* @__PURE__ */ new Set([...question.decline_options ?? [], ...question.dismiss_options ?? []]);
|
|
1488
|
+
return (question.options ?? []).filter((option) => !excluded.has(option));
|
|
1232
1489
|
}
|
|
1233
1490
|
function refusalLines(jobId, refusal, questions) {
|
|
1234
1491
|
const preamble = `Generation ${jobId} was declined before it ran. Nothing was generated and nothing was charged.`;
|
|
@@ -1237,11 +1494,7 @@ function refusalLines(jobId, refusal, questions) {
|
|
|
1237
1494
|
return [
|
|
1238
1495
|
preamble,
|
|
1239
1496
|
"",
|
|
1240
|
-
...questions
|
|
1241
|
-
`${question.id}: ${question.question}`,
|
|
1242
|
-
...question.why ? [` Why it matters: ${question.why}`] : [],
|
|
1243
|
-
...question.options.length > 0 ? [` For example: ${question.options.join(" \xB7 ")}`] : []
|
|
1244
|
-
]),
|
|
1497
|
+
...questionLines(questions),
|
|
1245
1498
|
"",
|
|
1246
1499
|
"Call generate_architecture again with the same prompt and clarification_answers keyed by those ids. Take the answers from the user rather than inventing them."
|
|
1247
1500
|
];
|
|
@@ -1266,22 +1519,14 @@ function clarifyingQuestions(error) {
|
|
|
1266
1519
|
return questions && questions.length > 0 ? questions : void 0;
|
|
1267
1520
|
}
|
|
1268
1521
|
function clarificationResult(questions) {
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
question: question.question,
|
|
1272
|
-
why: question.why ?? "",
|
|
1273
|
-
options: (question.options ?? []).filter((option) => !(question.decline_options ?? []).includes(option))
|
|
1274
|
-
}));
|
|
1522
|
+
const offered = questions.map(offeredQuestion);
|
|
1523
|
+
const structured = offered.map(structuredQuestion);
|
|
1275
1524
|
const lines = [
|
|
1276
|
-
"SkeletIQ will not design from this prompt yet \u2014 it
|
|
1525
|
+
"SkeletIQ will not design from this prompt yet \u2014 it is missing something the design would otherwise have to invent, such as the system itself or the load it carries. Nothing was generated and nothing was charged.",
|
|
1277
1526
|
"",
|
|
1278
|
-
...
|
|
1279
|
-
`${question.id}: ${question.question}`,
|
|
1280
|
-
...question.why ? [` Why it matters: ${question.why}`] : [],
|
|
1281
|
-
...question.options.length > 0 ? [` For example: ${question.options.join(" \xB7 ")}`] : []
|
|
1282
|
-
]),
|
|
1527
|
+
...questionLines(offered),
|
|
1283
1528
|
"",
|
|
1284
|
-
"Call generate_architecture again with the same prompt and clarification_answers keyed by those ids. The answers are used as authoritative requirements, so take them from the user rather than inventing them \u2014 and put anything you already know into constraints instead, where it is used as a fact rather than as prose to interpret."
|
|
1529
|
+
"Call generate_architecture again with the same prompt and clarification_answers keyed by those ids. The answers are used as authoritative requirements, so take them from the user rather than inventing them \u2014 and put anything you already know into constraints instead, where it is used as a fact rather than as prose to interpret." + (offered.some((question) => question.options.includes(LOAD_NOT_SURE)) ? ` If they do not know a figure asked for, answer "${LOAD_NOT_SURE}" rather than picking one.` : "")
|
|
1285
1530
|
];
|
|
1286
1531
|
return ok(
|
|
1287
1532
|
{
|
|
@@ -1294,6 +1539,7 @@ function clarificationResult(questions) {
|
|
|
1294
1539
|
component_count: null,
|
|
1295
1540
|
assistant_message: null,
|
|
1296
1541
|
degradations: [],
|
|
1542
|
+
prompt_trim_note: null,
|
|
1297
1543
|
clarifying_questions: structured
|
|
1298
1544
|
},
|
|
1299
1545
|
lines.join("\n")
|
|
@@ -1314,6 +1560,7 @@ async function queueGeneration(client, body) {
|
|
|
1314
1560
|
component_count: null,
|
|
1315
1561
|
assistant_message: null,
|
|
1316
1562
|
degradations: [],
|
|
1563
|
+
prompt_trim_note: null,
|
|
1317
1564
|
clarifying_questions: []
|
|
1318
1565
|
},
|
|
1319
1566
|
`Queued as job ${queued.job_id}. Poll it with get_generation_status.`
|
|
@@ -1362,6 +1609,7 @@ async function streamGeneration(client, body, ctx, resolver) {
|
|
|
1362
1609
|
const architectureId = typeof finished.architecture_id === "string" ? finished.architecture_id : null;
|
|
1363
1610
|
const version = typeof finished.architecture_version === "number" ? finished.architecture_version : null;
|
|
1364
1611
|
const degradations = Array.isArray(finished.degradations) ? finished.degradations.filter((d) => typeof d === "string") : [];
|
|
1612
|
+
const trimNote = asText(asRecord2(finished.run_summary)?.prompt_trim_note) ?? null;
|
|
1365
1613
|
const created = architectureId ? await describeCreated(resolver, architectureId) : void 0;
|
|
1366
1614
|
return ok(
|
|
1367
1615
|
{
|
|
@@ -1374,6 +1622,7 @@ async function streamGeneration(client, body, ctx, resolver) {
|
|
|
1374
1622
|
component_count: created?.componentCount ?? null,
|
|
1375
1623
|
assistant_message: null,
|
|
1376
1624
|
degradations,
|
|
1625
|
+
prompt_trim_note: trimNote,
|
|
1377
1626
|
clarifying_questions: []
|
|
1378
1627
|
},
|
|
1379
1628
|
[
|
|
@@ -1383,7 +1632,8 @@ async function streamGeneration(client, body, ctx, resolver) {
|
|
|
1383
1632
|
'Check get_design(mode: "readiness") before building \u2014 a fresh design is a draft, and',
|
|
1384
1633
|
"its open questions are the ones worth asking now."
|
|
1385
1634
|
] : ["SkeletIQ answered in prose rather than producing a design."],
|
|
1386
|
-
...degradations.map((d) => `Degraded: ${d}`)
|
|
1635
|
+
...degradations.map((d) => `Degraded: ${d}`),
|
|
1636
|
+
...trimNote ? [`Input trimmed: ${trimNote}`] : []
|
|
1387
1637
|
].join("\n")
|
|
1388
1638
|
);
|
|
1389
1639
|
}
|
|
@@ -1416,7 +1666,10 @@ function sliceComponent(design, componentId, ledger) {
|
|
|
1416
1666
|
incoming: connections.filter((c) => c.target === component.id),
|
|
1417
1667
|
related_decisions: related,
|
|
1418
1668
|
assumed_decisions: assumedAmong(design, related),
|
|
1419
|
-
...ledger ? {
|
|
1669
|
+
...ledger ? {
|
|
1670
|
+
cited_requirements: citedRequirements(ledger, component.id),
|
|
1671
|
+
withdrawn_requirements: withdrawnRequirements(ledger, component.id)
|
|
1672
|
+
} : {}
|
|
1420
1673
|
};
|
|
1421
1674
|
}
|
|
1422
1675
|
function citedRequirements(ledger, componentId) {
|
|
@@ -1424,13 +1677,51 @@ function citedRequirements(ledger, componentId) {
|
|
|
1424
1677
|
(ledger.rows ?? []).filter((row) => (row.cited_by_components ?? []).includes(componentId))
|
|
1425
1678
|
);
|
|
1426
1679
|
}
|
|
1427
|
-
|
|
1680
|
+
function withdrawnRequirements(ledger, componentId) {
|
|
1681
|
+
return [...ledger.removed ?? [], ...ledger.superseded ?? []].filter(
|
|
1682
|
+
(row) => (row.cited_by_components ?? []).includes(componentId)
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
var VERDICT_ORDER = ["fails", "unmet", "not_checked", "not_checkable", "unchecked", "no_gap_found", "passes", "cited"];
|
|
1686
|
+
function isCitedRequirement(row) {
|
|
1687
|
+
return (row.cited_by_components?.length ?? 0) > 0 || (row.cited_by_decisions?.length ?? 0) > 0;
|
|
1688
|
+
}
|
|
1689
|
+
var CITATION_ONLY_METHOD = "structure";
|
|
1690
|
+
var MEASURED_METHODS = ["simulation", "cost", "rules"];
|
|
1691
|
+
var UNCONFIRMED_WORDS = {
|
|
1692
|
+
model_check: "unconfirmed (model check)",
|
|
1693
|
+
unstated_input: "unconfirmed (unstated split)",
|
|
1694
|
+
advisory_finding: "unconfirmed (review advice)"
|
|
1695
|
+
};
|
|
1696
|
+
var ASSUMED_LOAD_INPUT = "the load, which was assumed";
|
|
1697
|
+
var ASSUMED_LOAD_WORD = "unconfirmed (assumed load)";
|
|
1698
|
+
var UNCONFIRMED_ADVICE = "verify before building for it";
|
|
1699
|
+
function requirementBlocksHandoff(row) {
|
|
1700
|
+
if (typeof row.blocks_handoff === "boolean") return row.blocks_handoff;
|
|
1701
|
+
return (row.verdict ?? "").trim() === "fails" && MEASURED_METHODS.includes((row.check_method ?? "").trim());
|
|
1702
|
+
}
|
|
1703
|
+
function requirementVerdict(row) {
|
|
1704
|
+
const verdict = (row.verdict ?? row.status ?? "").trim();
|
|
1705
|
+
if (verdict !== "fails" || requirementBlocksHandoff(row)) return verdict;
|
|
1706
|
+
const method = (row.check_method ?? "").trim();
|
|
1707
|
+
if (method === CITATION_ONLY_METHOD) return "";
|
|
1708
|
+
const reason = row.unconfirmed_reason ?? "model_check";
|
|
1709
|
+
if (reason === "unstated_input" && row.unstated_input?.trim() === ASSUMED_LOAD_INPUT) return ASSUMED_LOAD_WORD;
|
|
1710
|
+
return UNCONFIRMED_WORDS[reason] ?? "unconfirmed";
|
|
1711
|
+
}
|
|
1712
|
+
function isUnconfirmedRequirement(row) {
|
|
1713
|
+
return requirementVerdict(row).startsWith("unconfirmed");
|
|
1714
|
+
}
|
|
1428
1715
|
function orderRequirementRows(rows) {
|
|
1429
|
-
return [...rows].sort((a, b) =>
|
|
1716
|
+
return [...rows].sort((a, b) => verdictRank(a) - verdictRank(b));
|
|
1430
1717
|
}
|
|
1431
|
-
function
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1718
|
+
function verdictRank(row) {
|
|
1719
|
+
const verdict = requirementVerdict(row);
|
|
1720
|
+
if (verdict === "fails" || verdict === "unmet") return 0;
|
|
1721
|
+
if (isUnconfirmedRequirement(row)) return 1;
|
|
1722
|
+
if (!isCitedRequirement(row)) return 2;
|
|
1723
|
+
const index = VERDICT_ORDER.indexOf(verdict);
|
|
1724
|
+
return index === -1 ? VERDICT_ORDER.length + 2 : index + 3;
|
|
1434
1725
|
}
|
|
1435
1726
|
function decisionTexts(design) {
|
|
1436
1727
|
return decisionEntries(design).map((entry) => entry.text);
|
|
@@ -1494,11 +1785,11 @@ function normalize(value) {
|
|
|
1494
1785
|
}
|
|
1495
1786
|
|
|
1496
1787
|
// src/tools/get-design.ts
|
|
1497
|
-
var MODES = ["overview", "component", "brief", "readiness", "build_order", "gaps"];
|
|
1788
|
+
var MODES = ["overview", "component", "brief", "todo", "readiness", "build_order", "gaps"];
|
|
1498
1789
|
var input3 = z5.object({
|
|
1499
1790
|
project_id: z5.string().describe("A SkeletIQ project id, or its exact name."),
|
|
1500
1791
|
mode: z5.enum(MODES).describe(
|
|
1501
|
-
"overview: the whole design
|
|
1792
|
+
"overview: the whole design and, where the version records them, the requirements it was built from with what became of each. component: one component, its connections in both directions (the source calls the target), the decisions that mention it, and the requirements the design cites it for. brief: the fenced markdown block to write into AGENTS.md. todo: everything still to do on this version, one card per problem \u2014 what blocks a handoff, what is recommended, and what is waiting to be accepted. readiness: the verdict, gate by gate, on whether this design is worth building yet. build_order: the order to build components in, and why. gaps: the open questions and unconfirmed assumptions in the design."
|
|
1502
1793
|
),
|
|
1503
1794
|
component_id: z5.string().optional().describe('Required for mode "component". The component id, or its name.'),
|
|
1504
1795
|
version: z5.number().int().positive().optional().describe("A specific version. Defaults to the latest released version, or the latest version if none is released.")
|
|
@@ -1526,7 +1817,7 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1526
1817
|
"get_design",
|
|
1527
1818
|
{
|
|
1528
1819
|
title: "Read a SkeletIQ design",
|
|
1529
|
-
description: 'Read a SkeletIQ architecture. Start with mode "brief" to orient \u2014 it returns a fenced block to write into AGENTS.md \u2014 then "build_order" for the sequence and "component" for each piece as you build it. "overview" and "component" also resolve the R-ids the design carries to the requirements they name, and say what became of each. Every answer states which version it came from and whether that version is a release.',
|
|
1820
|
+
description: 'Read a SkeletIQ architecture. Start with mode "brief" to orient \u2014 it returns a fenced block to write into AGENTS.md \u2014 then "todo" for what the design still needs, "build_order" for the sequence and "component" for each piece as you build it. Where a version records its requirements, "overview" and "component" also resolve the R-ids the design carries to the requirements they name, and say what became of each. Every answer states which version it came from and whether that version is a release.',
|
|
1530
1821
|
inputSchema: input3,
|
|
1531
1822
|
outputSchema: output3,
|
|
1532
1823
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }
|
|
@@ -1603,6 +1894,11 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1603
1894
|
// Same order as the overview: what was asked for, then how the design
|
|
1604
1895
|
// answered.
|
|
1605
1896
|
...requirementsServed(slice.cited_requirements),
|
|
1897
|
+
// Said only when the design still carries the id. A component's
|
|
1898
|
+
// `requirement_ids` are never rewritten when a requirement is
|
|
1899
|
+
// withdrawn, so without this the row would leave the answer in
|
|
1900
|
+
// silence and the id on the box would resolve to nothing again.
|
|
1901
|
+
...requirementsWithdrawn(slice.withdrawn_requirements),
|
|
1606
1902
|
// Labelled, and only when there are any. These bullets used to be
|
|
1607
1903
|
// printed bare under "Called by:", so a decision about the component
|
|
1608
1904
|
// read as a continuation of its edge list — and a design with no
|
|
@@ -1649,6 +1945,33 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1649
1945
|
versionLine(facts)
|
|
1650
1946
|
].filter(Boolean).join("\n"));
|
|
1651
1947
|
}
|
|
1948
|
+
case "todo": {
|
|
1949
|
+
const body = await client.request(`/architectures/${resolved.architectureId}/todo`);
|
|
1950
|
+
const todo = TodoSchema.parse(body);
|
|
1951
|
+
const facts = factsFrom(resolved);
|
|
1952
|
+
const groups = todo.groups ?? [];
|
|
1953
|
+
const blocking = groups.find((group) => group.key === "blocks_handoff");
|
|
1954
|
+
const total = groups.reduce((count, group) => count + group.items.length, 0);
|
|
1955
|
+
return ok({ ...base, ...facts, data: todo }, [
|
|
1956
|
+
total === 0 ? "Nothing is outstanding on this version." : `${total} thing(s) still to do on this version` + (blocking?.items.length ? `, ${blocking.items.length} of which block a handoff:` : ", none of which block a handoff:"),
|
|
1957
|
+
...groups.flatMap(todoGroupBlock),
|
|
1958
|
+
// A source that could not be read is stated, never left to look like a
|
|
1959
|
+
// shorter list. An agent acting on "nothing is outstanding" when the
|
|
1960
|
+
// simulation results failed to load is the exact harm this prevents.
|
|
1961
|
+
...todo.unavailable_sources?.length ? [
|
|
1962
|
+
"",
|
|
1963
|
+
"Some of this version could not be read, so the list above is incomplete:",
|
|
1964
|
+
...todo.unavailable_sources.map((entry) => `- ${entry.source}: ${entry.reason}`)
|
|
1965
|
+
] : [],
|
|
1966
|
+
"",
|
|
1967
|
+
// The actions are the person's. An agent that "accepts" an assumption
|
|
1968
|
+
// has decided something on their behalf, which is the one thing this
|
|
1969
|
+
// surface must never do — and the server refuses it anyway, because an
|
|
1970
|
+
// agent token reaches no write route.
|
|
1971
|
+
"Fix what you can in the design. Answering, accepting and dismissing are the person's to do \u2014 ask, do not decide for them.",
|
|
1972
|
+
versionLine(facts)
|
|
1973
|
+
].join("\n"));
|
|
1974
|
+
}
|
|
1652
1975
|
case "readiness": {
|
|
1653
1976
|
const body = await client.request(`/architectures/${resolved.architectureId}/readiness`);
|
|
1654
1977
|
const readiness = ReadinessSchema.parse(body);
|
|
@@ -1656,7 +1979,11 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1656
1979
|
const gates = readiness.rows.filter((row) => row.category === "gate");
|
|
1657
1980
|
const open = gates.filter((row) => row.gating);
|
|
1658
1981
|
const unanswered = gates.filter((row) => row.state === "unknown");
|
|
1659
|
-
const
|
|
1982
|
+
const unconfirmedGates = gates.filter((row) => row.state === "unconfirmed");
|
|
1983
|
+
const accepts = readiness.rows.filter((row) => row.category === "accept");
|
|
1984
|
+
const advisories = readiness.rows.filter(
|
|
1985
|
+
(row) => row.category !== "gate" && row.category !== "accept"
|
|
1986
|
+
);
|
|
1660
1987
|
const unrun = new Set(readiness.unrun_checks ?? []);
|
|
1661
1988
|
const verdict = readiness.verdict ?? (readiness.ready ? "ready" : "outstanding");
|
|
1662
1989
|
const headline = verdict === "ready" ? "Every gate is clear \u2014 this design is ready to build." : verdict === "unanswered" ? `Nothing is undecided, but ${unanswered.length} check(s) could not be answered for this version:` : `${open.length} of ${gates.length} gate(s) still open:`;
|
|
@@ -1664,13 +1991,27 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1664
1991
|
return ok({ ...base, ...facts, data: readiness }, [
|
|
1665
1992
|
headline,
|
|
1666
1993
|
...listed.map((row) => `- ${row.label}: ${describeRow(row)}`),
|
|
1994
|
+
...unconfirmedGates.length ? [
|
|
1995
|
+
"",
|
|
1996
|
+
"Unconfirmed (the check ran, but its answer turns on an input nobody stated; never blocks):",
|
|
1997
|
+
...unconfirmedGates.map((row) => `- ${row.label}: ${describeRow(row)}`)
|
|
1998
|
+
] : [],
|
|
1999
|
+
...accepts.length ? [
|
|
2000
|
+
"",
|
|
2001
|
+
"To accept (the person agrees to these rather than fixing them; accepting is free and never blocks):",
|
|
2002
|
+
...accepts.map((row) => `- ${row.label}: ${describeRow(row)}`)
|
|
2003
|
+
] : [],
|
|
1667
2004
|
"",
|
|
1668
2005
|
"Advisory checks (they never block, and some need a paid plan to clear):",
|
|
1669
|
-
// An advisory
|
|
1670
|
-
//
|
|
1671
|
-
//
|
|
2006
|
+
// An advisory with no result is marked, not counted — "nothing to count
|
|
2007
|
+
// here" is a fact an agent being handed work is entitled to, and it is
|
|
2008
|
+
// invisible in a bare count of open items. The mark claims no more than
|
|
2009
|
+
// that: an advisory can be unanswered because no result is *stored* for
|
|
2010
|
+
// this version (the advisor row since the server stopped writing on
|
|
2011
|
+
// read), not only because nothing ever ran, and the server's own
|
|
2012
|
+
// sentence, when it sends one, says which.
|
|
1672
2013
|
...advisories.map(
|
|
1673
|
-
(row) => `- ${row.label}: ${describeRow(row)}${unrun.has(row.key) ? " (
|
|
2014
|
+
(row) => `- ${row.label}: ${describeRow(row)}${unrun.has(row.key) && !row.detail ? " (no result for this version)" : ""}`
|
|
1674
2015
|
),
|
|
1675
2016
|
// The same list, under the same heading, as the app shows beside its
|
|
1676
2017
|
// Release button. A person clicking Release is told what the release
|
|
@@ -1683,7 +2024,7 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1683
2024
|
...readiness.release_warnings.map((warning) => `- ${warning}`)
|
|
1684
2025
|
] : [],
|
|
1685
2026
|
"",
|
|
1686
|
-
|
|
2027
|
+
'Only a person can clear a gate. Ask; do not decide on their behalf. Use mode "todo" for the same work item by item.',
|
|
1687
2028
|
versionLine(facts)
|
|
1688
2029
|
].join("\n"));
|
|
1689
2030
|
}
|
|
@@ -1715,11 +2056,11 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1715
2056
|
const orphaned = gaps.orphaned_answers ?? [];
|
|
1716
2057
|
return ok({ ...base, ...facts, data: gaps }, [
|
|
1717
2058
|
unresolved.length === 0 ? "Every open question and assumption in this design has been dealt with." : `${unresolved.length} unresolved:`,
|
|
1718
|
-
...unresolved.map((gap) => `- [${gap.kind}] ${gap.text}`),
|
|
2059
|
+
...unresolved.map((gap) => `- [${gap.kind}] ${gap.text}${sizingNote(gap)}`),
|
|
1719
2060
|
...settled.length > 0 ? [
|
|
1720
2061
|
"",
|
|
1721
2062
|
`${settled.length} settled during review \u2014 treat these as decided:`,
|
|
1722
|
-
...settled.map((gap) => `- [${gap.kind}] ${gap.text} \u2192 ${settledAnswer(gap)}`)
|
|
2063
|
+
...settled.map((gap) => `- [${gap.kind}] ${gap.text} \u2192 ${settledAnswer(gap)}${sizingNote(gap)}`)
|
|
1723
2064
|
] : [],
|
|
1724
2065
|
// A gap is identified by a hash of its own text, so a regeneration that
|
|
1725
2066
|
// re-words a question strands the answer that settled it. The server
|
|
@@ -1745,14 +2086,23 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1745
2086
|
function requirementBlock(ledger) {
|
|
1746
2087
|
const rows = orderRequirementRows(ledger?.rows ?? []);
|
|
1747
2088
|
if (!ledger || rows.length === 0) return [];
|
|
1748
|
-
const
|
|
2089
|
+
const unconfirmed = rows.filter(isUnconfirmedRequirement).length;
|
|
2090
|
+
const attention = [
|
|
2091
|
+
[rows.filter((row) => requirementVerdict(row) === "fails").length, "failing"],
|
|
2092
|
+
[unconfirmed, "unconfirmed"],
|
|
2093
|
+
[rows.filter((row) => !isCitedRequirement(row)).length, "cited by nothing"],
|
|
2094
|
+
[rows.filter((row) => requirementVerdict(row) === "not_checked" || requirementVerdict(row) === "not_checkable").length, "unchecked"]
|
|
2095
|
+
].filter(([count]) => count > 0).map(([count, word]) => `${count} ${word}`);
|
|
1749
2096
|
return [
|
|
1750
2097
|
"",
|
|
1751
2098
|
`Requirements \u2014 ${rows.length} total${attention.length ? `; ${attention.join(", ")}` : ""}:`,
|
|
1752
2099
|
...rows.map(requirementLine),
|
|
1753
|
-
"
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
2100
|
+
"Verdicts: `fails` \u2014 a named check measured the design as not meeting it, and says which and why;",
|
|
2101
|
+
"`passes` \u2014 a check measured it against a figure the person stated; `no_gap_found` \u2014 nothing",
|
|
2102
|
+
"contradicted it, which is not the same as proven; `not_checked` / `not_checkable` \u2014 nobody",
|
|
2103
|
+
"established either way, and neither is fine. `cited by nothing` means no component and no",
|
|
2104
|
+
"decision in the design claims to address it.",
|
|
2105
|
+
...unconfirmedLegend(rows),
|
|
1756
2106
|
// Two facts about how far the statuses can be trusted, said only when the server states
|
|
1757
2107
|
// them. An absent `verification_state` is an older server, not a verifier that did not run,
|
|
1758
2108
|
// and claiming the second would be this package inventing a fact about someone's design.
|
|
@@ -1770,20 +2120,65 @@ function requirementBlock(ledger) {
|
|
|
1770
2120
|
...ledger.analysis_degraded ? ["The requirements analysis for this version ran degraded. Treat the list as incomplete."] : []
|
|
1771
2121
|
];
|
|
1772
2122
|
}
|
|
2123
|
+
function unconfirmedLegend(rows) {
|
|
2124
|
+
const words = new Set(rows.filter(isUnconfirmedRequirement).map(requirementVerdict));
|
|
2125
|
+
const reasons = new Set(
|
|
2126
|
+
rows.filter((row) => isUnconfirmedRequirement(row) && requirementVerdict(row) !== "unconfirmed (assumed load)").map((row) => row.unconfirmed_reason ?? "model_check")
|
|
2127
|
+
);
|
|
2128
|
+
return [
|
|
2129
|
+
...reasons.has("model_check") ? [
|
|
2130
|
+
"`unconfirmed (model check)` \u2014 only a model check says the design may not meet it. Nothing",
|
|
2131
|
+
`measured it, so it does not block handoff: ${UNCONFIRMED_ADVICE} rather than adding a part.`
|
|
2132
|
+
] : [],
|
|
2133
|
+
...reasons.has("unstated_input") ? [
|
|
2134
|
+
"`unconfirmed (unstated split)` \u2014 capacity falls short only under an assumption nobody stated",
|
|
2135
|
+
"(the rate split evenly between entrances, or traffic weights that do not add up). Its reason",
|
|
2136
|
+
"names the split the design would need \u2014 judge whether that is realistic. It does not block",
|
|
2137
|
+
"handoff: ask how the traffic divides."
|
|
2138
|
+
] : [],
|
|
2139
|
+
...reasons.has("advisory_finding") ? [
|
|
2140
|
+
"`unconfirmed (review advice)` \u2014 only advice from the design review fails it: the findings it",
|
|
2141
|
+
"rests on are reported, not scored, so it does not block handoff. Read the advice and decide",
|
|
2142
|
+
"whether the design should take it."
|
|
2143
|
+
] : [],
|
|
2144
|
+
...words.has("unconfirmed (assumed load)") ? [
|
|
2145
|
+
"`unconfirmed (assumed load)` \u2014 the verdict rests on the load, which was assumed: nobody said how",
|
|
2146
|
+
"much traffic to expect, so the design was sized and priced on the typical load for this kind of",
|
|
2147
|
+
"system. It does not block handoff: ask your human for their load rather than guessing one."
|
|
2148
|
+
] : []
|
|
2149
|
+
];
|
|
2150
|
+
}
|
|
1773
2151
|
function requirementsServed(rows) {
|
|
1774
2152
|
if (!rows?.length) return [];
|
|
1775
2153
|
return [
|
|
1776
2154
|
"",
|
|
1777
2155
|
"Requirements the design cites this component for:",
|
|
1778
2156
|
...rows.map(requirementLine),
|
|
1779
|
-
"`
|
|
1780
|
-
"
|
|
2157
|
+
"`fails` \u2014 a named check measured the design as not meeting it; `not_checked` \u2014 nobody",
|
|
2158
|
+
"established either way; `no_gap_found` \u2014 nothing contradicted it, which is not the same as proven.",
|
|
2159
|
+
...unconfirmedLegend(rows)
|
|
2160
|
+
];
|
|
2161
|
+
}
|
|
2162
|
+
function requirementsWithdrawn(rows) {
|
|
2163
|
+
if (!rows?.length) return [];
|
|
2164
|
+
return [
|
|
2165
|
+
"",
|
|
2166
|
+
"This component still cites these ids, and they are no longer requirements \u2014 do not build to them:",
|
|
2167
|
+
...rows.map(
|
|
2168
|
+
(row) => `- ${row.id} ${(row.text ?? "").replace(/\s+/g, " ").trim() || "(no text recorded)"} [${row.decision === "removed" ? "removed by a person" : `was an instruction, now ${row.superseded_by_change_request ?? "a change request"}`}]`
|
|
2169
|
+
)
|
|
1781
2170
|
];
|
|
1782
2171
|
}
|
|
1783
2172
|
function requirementLine(row) {
|
|
1784
|
-
const
|
|
2173
|
+
const verdict = requirementVerdict(row);
|
|
2174
|
+
const shown = isUnconfirmedRequirement(row) ? `${verdict} \u2014 ${UNCONFIRMED_ADVICE}` : verdict;
|
|
2175
|
+
const marks = [shown, ...isCitedRequirement(row) ? [] : ["cited by nothing"]].filter(Boolean);
|
|
2176
|
+
const label = marks.length > 0 ? ` [${marks.join(" \xB7 ")}]` : "";
|
|
1785
2177
|
const text = (row.text ?? "").replace(/\s+/g, " ").trim();
|
|
1786
|
-
|
|
2178
|
+
const reason = (row.verdict_reason ?? "").replace(/\s+/g, " ").trim();
|
|
2179
|
+
const by = (row.checked_by ?? "").trim();
|
|
2180
|
+
const because = reason ? ` \u2014 ${reason}${by ? ` (checked by ${by})` : ""}` : by ? ` \u2014 checked by ${by}` : "";
|
|
2181
|
+
return `- ${row.id}${label} ${text || "(no text recorded for this requirement)"}${because}`;
|
|
1787
2182
|
}
|
|
1788
2183
|
function settledAnswer(gap) {
|
|
1789
2184
|
const note = (gap.note ?? "").trim();
|
|
@@ -1795,8 +2190,29 @@ function settledAnswer(gap) {
|
|
|
1795
2190
|
if (gap.action === "confirmed") return "confirmed as it stands";
|
|
1796
2191
|
return "answered, but no wording was recorded \u2014 ask before relying on it";
|
|
1797
2192
|
}
|
|
2193
|
+
function sizingNote(gap) {
|
|
2194
|
+
const conflict = gap.sizing_conflict;
|
|
2195
|
+
return conflict ? ` (this design is sized for ${conflict.sized}, not ${conflict.assumed})` : "";
|
|
2196
|
+
}
|
|
2197
|
+
function todoGroupBlock(group) {
|
|
2198
|
+
if (group.items.length === 0) return [];
|
|
2199
|
+
return ["", `${group.label} (${group.items.length}):`, ...group.items.map(todoLine)];
|
|
2200
|
+
}
|
|
2201
|
+
function todoLine(item) {
|
|
2202
|
+
const severity = item.severity && item.severity !== "none" ? `[${item.severity}] ` : "";
|
|
2203
|
+
const raisedBy = [...new Set((item.sources ?? []).map((source) => source.source))];
|
|
2204
|
+
const raised = raisedBy.length > 0 ? ` \u2014 raised by ${raisedBy.join(", ")}` : "";
|
|
2205
|
+
const carried = item.carried ? item.carried_from_version ? ` \u2014 carried from v${item.carried_from_version}` : " \u2014 carried from an earlier version" : "";
|
|
2206
|
+
const components = item.components?.length ? ` \u2014 affects ${item.components.join(", ")}` : "";
|
|
2207
|
+
return `- ${severity}${item.title.replace(/\s+/g, " ").trim()}${components}${raised}${carried}`;
|
|
2208
|
+
}
|
|
1798
2209
|
function describeRow(row) {
|
|
1799
|
-
if (row.state === "unknown")
|
|
2210
|
+
if (row.state === "unknown") {
|
|
2211
|
+
const clears = row.clears ? ` ${row.clears}` : "";
|
|
2212
|
+
const detail = row.detail ? ` \u2014 ${row.detail}${clears && !/[.!?]$/.test(row.detail) ? "." : ""}` : clears ? " \u2014" : "";
|
|
2213
|
+
return `not checked${detail}${clears}`;
|
|
2214
|
+
}
|
|
2215
|
+
if (row.state === "unconfirmed") return `unconfirmed${row.detail ? ` \u2014 ${row.detail}` : ""}`;
|
|
1800
2216
|
return `${row.count}${row.detail ? ` \u2014 ${row.detail}` : ""}`;
|
|
1801
2217
|
}
|
|
1802
2218
|
|
package/package.json
CHANGED