@skeletiq/mcp 0.2.0 → 0.3.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 +1 -1
- package/dist/index.js +329 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ 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. |
|
|
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
89
|
| `generate_architecture` | Design a system from a prompt. Spends credits and takes minutes. |
|
|
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`. |
|
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.3.0";
|
|
9
9
|
var DEFAULT_API_URL = "https://api.skeletiq.com";
|
|
10
10
|
var TOKEN_PREFIX = "skq_";
|
|
11
11
|
var ConfigError = class extends Error {
|
|
@@ -331,10 +331,35 @@ var RequirementLedgerRowSchema = z.looseObject({
|
|
|
331
331
|
* value added server-side must not stop an older connector parsing the row. */
|
|
332
332
|
status: z.string().optional(),
|
|
333
333
|
verifier_checked: z.boolean().optional(),
|
|
334
|
+
/** Which check answered this line: `simulation` | `cost` | `rules` | `structure` | `verifier`.
|
|
335
|
+
* Four are deterministic; `verifier` is the model check, and it only owns a row when it gave a
|
|
336
|
+
* reason. A string rather than a union, for the reason `status` is one. Absent from older APIs. */
|
|
337
|
+
check_method: z.string().nullable().optional(),
|
|
338
|
+
/** What that check said: `fails` | `passes` | `no_gap_found` | `not_checkable` | `not_checked`.
|
|
339
|
+
* `passes` is only ever a measurement; `no_gap_found` is the weaker "nothing contradicted it".
|
|
340
|
+
* Reported beside `status`, which stays populated. Absent from older APIs. */
|
|
341
|
+
verdict: z.string().nullable().optional(),
|
|
342
|
+
/** Why, in one sentence, with the figure or the finding behind it. */
|
|
343
|
+
verdict_reason: z.string().nullable().optional(),
|
|
344
|
+
/** The model behind a `verifier` verdict. Null on every deterministic row — naming a model
|
|
345
|
+
* beside a number the server computed would be a false attribution. */
|
|
346
|
+
checked_by: z.string().nullable().optional(),
|
|
334
347
|
cited_by_components: z.array(z.string()).nullable().optional(),
|
|
335
348
|
/** Indices into `architecture_json.grounded_decisions`, the raw array — blanks are not compacted. */
|
|
336
349
|
cited_by_decisions: z.array(z.number()).nullable().optional(),
|
|
337
|
-
added_in_version: z.number().nullable().optional()
|
|
350
|
+
added_in_version: z.number().nullable().optional(),
|
|
351
|
+
/** Set only on a row under `RequirementLedger.superseded`: an instruction an earlier analysis
|
|
352
|
+
* recorded as a requirement, now the change request it names (`CR5`). Absent from older APIs. */
|
|
353
|
+
superseded_by_change_request: z.string().nullable().optional(),
|
|
354
|
+
superseded_in_version: z.number().nullable().optional(),
|
|
355
|
+
/** What a person said about this line: `confirmed` ("yes, that one is real") or `removed`
|
|
356
|
+
* ("that is not a requirement"). Null or absent means nobody has said, which is neither. A
|
|
357
|
+
* `removed` row is listed under `RequirementLedger.removed` and never in `rows`, so a design
|
|
358
|
+
* is not held to it; it keeps its id so a citation naming it still resolves. `confirmed`
|
|
359
|
+
* changes no count and no verdict. Absent from older APIs. */
|
|
360
|
+
decision: z.string().nullable().optional(),
|
|
361
|
+
/** The version that was on screen when that decision was recorded. */
|
|
362
|
+
decided_in_version: z.number().nullable().optional()
|
|
338
363
|
});
|
|
339
364
|
var RequirementLedgerCountsSchema = z.looseObject({
|
|
340
365
|
total: z.number().optional(),
|
|
@@ -343,10 +368,33 @@ var RequirementLedgerCountsSchema = z.looseObject({
|
|
|
343
368
|
unmet: z.number().optional(),
|
|
344
369
|
uncited: z.number().optional(),
|
|
345
370
|
unchecked: z.number().optional(),
|
|
346
|
-
cited: z.number().optional()
|
|
371
|
+
cited: z.number().optional(),
|
|
372
|
+
/** Rows citing no component and no decision (overlaps `unmet`). Absent from older APIs. */
|
|
373
|
+
untraced: z.number().optional(),
|
|
374
|
+
/** The verdict partition, which sums to `total` exactly as the status partition above does —
|
|
375
|
+
* two readings of one set of rows, not two sets. Absent from older APIs. */
|
|
376
|
+
fails: z.number().optional(),
|
|
377
|
+
passes: z.number().optional(),
|
|
378
|
+
no_gap_found: z.number().optional(),
|
|
379
|
+
not_checkable: z.number().optional(),
|
|
380
|
+
not_checked: z.number().optional(),
|
|
381
|
+
/** Rows a person has confirmed are real requirements. In no partition — it overlaps all of
|
|
382
|
+
* them, because confirming a line says nothing about whether the design meets it. */
|
|
383
|
+
confirmed: z.number().optional(),
|
|
384
|
+
/** Ids a person said are **not** requirements. Not part of `total`: those rows are not in
|
|
385
|
+
* `rows` at all. Absent from older APIs. */
|
|
386
|
+
removed: z.number().optional()
|
|
347
387
|
});
|
|
348
388
|
var RequirementLedgerSchema = z.looseObject({
|
|
349
389
|
rows: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
390
|
+
/** Ids that were instructions, not requirements. Counted nowhere; listed so an id a component
|
|
391
|
+
* or decision still cites resolves to what became of it. Absent from older APIs. */
|
|
392
|
+
superseded: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
393
|
+
/** Ids a person said are not requirements, so the design is not held to them. Kept apart from
|
|
394
|
+
* `superseded` because it is a different statement by a different author. Counted nowhere;
|
|
395
|
+
* listed for the same reason, so a citation naming one still resolves. Absent from older
|
|
396
|
+
* APIs. */
|
|
397
|
+
removed: z.array(RequirementLedgerRowSchema).nullable().optional(),
|
|
350
398
|
analysis_version: z.number().nullable().optional(),
|
|
351
399
|
analysis_degraded: z.boolean().optional(),
|
|
352
400
|
/** `grounded` | `not_grounded` | `diverged`. */
|
|
@@ -400,6 +448,12 @@ var BuildOrderSchema = z.looseObject({
|
|
|
400
448
|
steps: z.array(BuildStepSchema),
|
|
401
449
|
release: ReleaseFactsSchema
|
|
402
450
|
});
|
|
451
|
+
var UnavailableSourceSchema = z.looseObject({
|
|
452
|
+
source: z.string(),
|
|
453
|
+
reason: z.string(),
|
|
454
|
+
/** `failed` | `missing`. A string, not a union: a value added server-side must still parse. */
|
|
455
|
+
kind: z.string().optional()
|
|
456
|
+
});
|
|
403
457
|
var ReadinessRowSchema = z.looseObject({
|
|
404
458
|
key: z.string(),
|
|
405
459
|
label: z.string(),
|
|
@@ -438,7 +492,16 @@ var ReadinessSchema = z.looseObject({
|
|
|
438
492
|
* Optional here only so a server that predates the field still parses — it is required on the
|
|
439
493
|
* wire, and an empty array is the server saying it looked and found nothing.
|
|
440
494
|
*/
|
|
441
|
-
release_warnings: z.array(z.string()).optional()
|
|
495
|
+
release_warnings: z.array(z.string()).optional(),
|
|
496
|
+
/**
|
|
497
|
+
* Sources this checklist could not speak for, exactly as `/todo` reports them.
|
|
498
|
+
*
|
|
499
|
+
* A failed read reaches each row builder as that source's own empty value — no questions, no
|
|
500
|
+
* decisions, no ledger — and every one of those empties reads as *clear*, so an outage could
|
|
501
|
+
* answer `ready: true` with `release_warnings: []`. Each one also has a row of its own now;
|
|
502
|
+
* this is the same fact in the shape a caller showing both lists can join on.
|
|
503
|
+
*/
|
|
504
|
+
unavailable_sources: z.array(UnavailableSourceSchema).optional()
|
|
442
505
|
});
|
|
443
506
|
var DesignGapSchema = z.looseObject({
|
|
444
507
|
gap_id: z.string(),
|
|
@@ -447,7 +510,13 @@ var DesignGapSchema = z.looseObject({
|
|
|
447
510
|
resolved: z.boolean(),
|
|
448
511
|
action: z.string().nullable().optional(),
|
|
449
512
|
note: z.string().nullable().optional(),
|
|
450
|
-
adr_id: z.string().nullable().optional()
|
|
513
|
+
adr_id: z.string().nullable().optional(),
|
|
514
|
+
/** `gap:{gap_id}` — the identity the review, the advisor and the decision suggestions share,
|
|
515
|
+
* and the key a dismissal is recorded against. Absent from servers before 0.3.0. */
|
|
516
|
+
problem_key: z.string().optional(),
|
|
517
|
+
/** An answer is recorded and this version was not built with it, so iterating the design
|
|
518
|
+
* would change something. False once a generation has been handed the answer. */
|
|
519
|
+
answer_pending: z.boolean().optional()
|
|
451
520
|
});
|
|
452
521
|
var OrphanedAnswerSchema = z.looseObject({
|
|
453
522
|
gap_id: z.string(),
|
|
@@ -465,29 +534,6 @@ var DesignGapListSchema = z.looseObject({
|
|
|
465
534
|
/** Answers whose question is not in this version — recorded, not silently dropped. */
|
|
466
535
|
orphaned_answers: z.array(OrphanedAnswerSchema).optional()
|
|
467
536
|
});
|
|
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
537
|
var CritiqueFindingSchema = z.looseObject({
|
|
492
538
|
message: z.string(),
|
|
493
539
|
pattern_id: z.string(),
|
|
@@ -495,7 +541,17 @@ var CritiqueFindingSchema = z.looseObject({
|
|
|
495
541
|
severity: z.string().optional(),
|
|
496
542
|
affected_components: z.array(z.string()).optional(),
|
|
497
543
|
evidence: z.string().optional(),
|
|
498
|
-
remediation: z.string().optional()
|
|
544
|
+
remediation: z.string().optional(),
|
|
545
|
+
/** Reported in full and never charged against the score — advice, not a problem to fix. */
|
|
546
|
+
advisory_only: z.boolean().optional(),
|
|
547
|
+
/**
|
|
548
|
+
* The problem this finding is about. Stable across rewording, and shared with the advisor
|
|
549
|
+
* suggestion and decision record raised by the same problem — the key a dismissal is recorded
|
|
550
|
+
* against.
|
|
551
|
+
*/
|
|
552
|
+
problem_key: z.string().optional(),
|
|
553
|
+
/** The project set this problem aside, with a reason. Still reported; never set on an unsaved payload. */
|
|
554
|
+
dismissed: z.boolean().optional()
|
|
499
555
|
});
|
|
500
556
|
var PayloadCritiqueSchema = z.looseObject({
|
|
501
557
|
architecture_score: z.number(),
|
|
@@ -513,6 +569,96 @@ var PayloadCritiqueSchema = z.looseObject({
|
|
|
513
569
|
compliance_note: z.string().nullable().optional(),
|
|
514
570
|
exposure_assessed: z.string().optional()
|
|
515
571
|
});
|
|
572
|
+
var TodoDecisionContextSchema = z.looseObject({
|
|
573
|
+
context: z.string().nullable().optional(),
|
|
574
|
+
consequences: z.string().nullable().optional(),
|
|
575
|
+
alternatives: z.string().nullable().optional(),
|
|
576
|
+
/** `records` | `open` | `tradeoff`. A string, not a union: a value added server-side must still parse. */
|
|
577
|
+
disposition: z.string(),
|
|
578
|
+
supersedes_adr_id: z.string().nullable().optional(),
|
|
579
|
+
source_version: z.number()
|
|
580
|
+
});
|
|
581
|
+
var TodoSourceSchema = z.looseObject({
|
|
582
|
+
source: z.string(),
|
|
583
|
+
message: z.string(),
|
|
584
|
+
detail: z.string().nullable().optional(),
|
|
585
|
+
/** A handle to act on through that source: a suggestion id, a gap id, an R-id. */
|
|
586
|
+
reference_id: z.string().nullable().optional(),
|
|
587
|
+
/** Present on a `critique` or `compliance` source; null on every other kind. */
|
|
588
|
+
finding: CritiqueFindingSchema.nullable().optional(),
|
|
589
|
+
/** Present on a `decisions` source; null on every other kind. */
|
|
590
|
+
decision: TodoDecisionContextSchema.nullable().optional()
|
|
591
|
+
});
|
|
592
|
+
var TodoActionSchema = z.looseObject({
|
|
593
|
+
action: z.string(),
|
|
594
|
+
available: z.boolean(),
|
|
595
|
+
reason: z.string().nullable().optional(),
|
|
596
|
+
reference_id: z.string().nullable().optional()
|
|
597
|
+
});
|
|
598
|
+
var TodoItemSchema = z.looseObject({
|
|
599
|
+
problem_key: z.string(),
|
|
600
|
+
group: z.string(),
|
|
601
|
+
/** `problem` | `question` | `assumption` | `decision` | `verdict`. A string, not a union: a
|
|
602
|
+
* value added server-side must not stop an older connector parsing the card. */
|
|
603
|
+
kind: z.string(),
|
|
604
|
+
title: z.string(),
|
|
605
|
+
severity: z.string(),
|
|
606
|
+
detail: z.string().nullable().optional(),
|
|
607
|
+
components: z.array(z.string()).optional(),
|
|
608
|
+
sources: z.array(TodoSourceSchema).optional(),
|
|
609
|
+
actions: z.array(TodoActionSchema).optional(),
|
|
610
|
+
carried: z.boolean().optional(),
|
|
611
|
+
/** The oldest earlier version known to have had this problem. Null with `carried: true` means
|
|
612
|
+
* it goes back further than the server looked — "an earlier version", never version zero. */
|
|
613
|
+
carried_from_version: z.number().nullable().optional(),
|
|
614
|
+
dismissed: z.boolean().optional(),
|
|
615
|
+
dismissed_reason: z.string().nullable().optional()
|
|
616
|
+
});
|
|
617
|
+
var TodoGroupSchema = z.looseObject({
|
|
618
|
+
key: z.string(),
|
|
619
|
+
label: z.string(),
|
|
620
|
+
items: z.array(TodoItemSchema),
|
|
621
|
+
bulk_action: TodoActionSchema.nullable().optional()
|
|
622
|
+
});
|
|
623
|
+
var TodoSchema = z.looseObject({
|
|
624
|
+
architecture_id: z.string(),
|
|
625
|
+
version: z.number(),
|
|
626
|
+
groups: z.array(TodoGroupSchema),
|
|
627
|
+
dismissed: z.array(TodoItemSchema).optional(),
|
|
628
|
+
counts: z.record(z.string(), z.number()).optional(),
|
|
629
|
+
/** Rows the sources raised before grouping. The gap between this and `counts.total` is the
|
|
630
|
+
* duplication the endpoint removes, reported so the claim stays checkable. */
|
|
631
|
+
raw_row_count: z.number().optional(),
|
|
632
|
+
unavailable_sources: z.array(UnavailableSourceSchema).optional(),
|
|
633
|
+
generation_running: z.boolean().optional(),
|
|
634
|
+
can_edit: z.boolean().optional(),
|
|
635
|
+
/** The project's newest version. Above `version` when this list is for an older one — a
|
|
636
|
+
* release resolved behind newer drafts, or a version asked for by number. Null when unread. */
|
|
637
|
+
latest_version: z.number().nullable().optional()
|
|
638
|
+
});
|
|
639
|
+
var ComponentRefSchema = z.looseObject({
|
|
640
|
+
id: z.string(),
|
|
641
|
+
name: z.string(),
|
|
642
|
+
type: z.string()
|
|
643
|
+
});
|
|
644
|
+
var UnknownIdSchema = z.looseObject({
|
|
645
|
+
id: z.string(),
|
|
646
|
+
suggestions: z.array(ComponentRefSchema).optional()
|
|
647
|
+
});
|
|
648
|
+
var DriftSchema = z.looseObject({
|
|
649
|
+
architecture_id: z.string(),
|
|
650
|
+
version: z.number(),
|
|
651
|
+
in_sync: z.boolean(),
|
|
652
|
+
missing: z.array(ComponentRefSchema),
|
|
653
|
+
unreported: z.array(ComponentRefSchema),
|
|
654
|
+
partial: z.array(ComponentRefSchema),
|
|
655
|
+
elsewhere: z.array(ComponentRefSchema),
|
|
656
|
+
unknown_ids: z.array(UnknownIdSchema),
|
|
657
|
+
extra_components: z.array(z.looseObject({ name: z.string() })),
|
|
658
|
+
extra_connections: z.array(z.looseObject({ source: z.string(), target: z.string() })),
|
|
659
|
+
counts: z.record(z.string(), z.number()),
|
|
660
|
+
release: ReleaseFactsSchema
|
|
661
|
+
});
|
|
516
662
|
var ClarifyingQuestionSchema = z.looseObject({
|
|
517
663
|
id: z.string(),
|
|
518
664
|
question: z.string(),
|
|
@@ -581,7 +727,9 @@ var PROJECT_PAGE_SIZE = 50;
|
|
|
581
727
|
var ARCHITECTURE_PAGE_SIZE = 100;
|
|
582
728
|
var DesignResolver = class {
|
|
583
729
|
client;
|
|
730
|
+
/** Version summaries per project — enough to choose a version, never to describe one. */
|
|
584
731
|
versions = /* @__PURE__ */ new Map();
|
|
732
|
+
/** Full records from the single-version read, the requirements ledger included. */
|
|
585
733
|
designs = /* @__PURE__ */ new Map();
|
|
586
734
|
constructor(client) {
|
|
587
735
|
this.client = client;
|
|
@@ -610,7 +758,11 @@ var DesignResolver = class {
|
|
|
610
758
|
if (exact.length === 1) return exact[0];
|
|
611
759
|
throw new AmbiguousProjectError(idOrQuery, candidates);
|
|
612
760
|
}
|
|
613
|
-
/**
|
|
761
|
+
/**
|
|
762
|
+
* Every version of a project, newest first. Cached: one call serves a whole session.
|
|
763
|
+
*
|
|
764
|
+
* These are summaries. They seed nothing in `designs` — see the file header for why.
|
|
765
|
+
*/
|
|
614
766
|
async versionsFor(projectId) {
|
|
615
767
|
const cached = this.versions.get(projectId);
|
|
616
768
|
if (cached) return cached;
|
|
@@ -619,7 +771,6 @@ var DesignResolver = class {
|
|
|
619
771
|
});
|
|
620
772
|
const items = [...ArchitecturePageSchema.parse(body).items].sort((a, b) => b.version - a.version);
|
|
621
773
|
this.versions.set(projectId, items);
|
|
622
|
-
for (const item of items) this.designs.set(item.id, item);
|
|
623
774
|
return items;
|
|
624
775
|
}
|
|
625
776
|
/** The version rule. */
|
|
@@ -659,10 +810,10 @@ var DesignResolver = class {
|
|
|
659
810
|
};
|
|
660
811
|
}
|
|
661
812
|
/**
|
|
662
|
-
* The full design for one version.
|
|
813
|
+
* The full design for one version, from the single-version read.
|
|
663
814
|
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
815
|
+
* One request per version a session actually describes, not one per version in the list:
|
|
816
|
+
* an overview and any number of component slices of the same version share it.
|
|
666
817
|
*/
|
|
667
818
|
async design(architectureId) {
|
|
668
819
|
const cached = this.designs.get(architectureId);
|
|
@@ -757,7 +908,9 @@ var input = z2.object({
|
|
|
757
908
|
target: z2.string().max(255),
|
|
758
909
|
protocol: z2.string().max(64).optional()
|
|
759
910
|
})
|
|
760
|
-
).max(500).optional().describe(
|
|
911
|
+
).max(500).optional().describe(
|
|
912
|
+
"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."
|
|
913
|
+
)
|
|
761
914
|
});
|
|
762
915
|
var refs = z2.array(z2.object({ id: z2.string(), name: z2.string(), type: z2.string() }));
|
|
763
916
|
var output = z2.object({
|
|
@@ -893,7 +1046,10 @@ var output2 = z3.object({
|
|
|
893
1046
|
category: z3.string(),
|
|
894
1047
|
message: z3.string(),
|
|
895
1048
|
affected_components: z3.array(z3.string()),
|
|
896
|
-
remediation: z3.string()
|
|
1049
|
+
remediation: z3.string(),
|
|
1050
|
+
advisory_only: z3.boolean().describe(
|
|
1051
|
+
"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."
|
|
1052
|
+
)
|
|
897
1053
|
})
|
|
898
1054
|
),
|
|
899
1055
|
finding_count: z3.number(),
|
|
@@ -932,7 +1088,9 @@ function registerCritique(server, client) {
|
|
|
932
1088
|
category: finding.category,
|
|
933
1089
|
message: finding.message,
|
|
934
1090
|
affected_components: finding.affected_components ?? [],
|
|
935
|
-
remediation: finding.remediation ?? ""
|
|
1091
|
+
remediation: finding.remediation ?? "",
|
|
1092
|
+
// A server too old to send the flag predates it, and counted every finding.
|
|
1093
|
+
advisory_only: finding.advisory_only ?? false
|
|
936
1094
|
}));
|
|
937
1095
|
const complianceAssessed = critique.compliance_assessed ?? false;
|
|
938
1096
|
const exposureAssessed = critique.exposure_assessed ?? "public_internet";
|
|
@@ -966,8 +1124,12 @@ function registerCritique(server, client) {
|
|
|
966
1124
|
"",
|
|
967
1125
|
findings.length === 0 ? "No findings." : `${findings.length} finding(s):`,
|
|
968
1126
|
...findings.map(
|
|
969
|
-
(finding) =>
|
|
1127
|
+
(finding) => (
|
|
1128
|
+
// Said on the line, because a severity alone reads as a blocker: an
|
|
1129
|
+
// advisory critical is one the score and the app's verdict both ignore.
|
|
1130
|
+
`- [${finding.severity}${finding.advisory_only ? " \xB7 advisory, not scored" : ""}] ${finding.message}` + (finding.affected_components.length ? ` (${finding.affected_components.join(", ")})` : "") + (finding.remediation ? `
|
|
970
1131
|
Fix: ${finding.remediation}` : "")
|
|
1132
|
+
)
|
|
971
1133
|
)
|
|
972
1134
|
];
|
|
973
1135
|
return ok(structured, lines.join("\n"));
|
|
@@ -1083,8 +1245,12 @@ var constraintsInput = z4.strictObject({
|
|
|
1083
1245
|
preferred_cloud: z4.enum(["aws", "gcp", "azure", "any"]).optional(),
|
|
1084
1246
|
preferred_technologies: z4.array(z4.string()).optional(),
|
|
1085
1247
|
max_components: z4.number().int().positive().optional(),
|
|
1086
|
-
read_write_mix: z4.enum(["read_heavy", "balanced", "write_heavy"]).optional().describe(
|
|
1087
|
-
|
|
1248
|
+
read_write_mix: z4.enum(["read_heavy", "balanced", "write_heavy"]).optional().describe(
|
|
1249
|
+
"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."
|
|
1250
|
+
),
|
|
1251
|
+
burstiness: z4.enum(["steady", "daily_peaks", "spiky"]).optional().describe(
|
|
1252
|
+
'Wins over a peak the prompt states ("peaking at 2,000 per second"), which the server also reads. Unset and unstated, the design assumes the tier default peak.'
|
|
1253
|
+
),
|
|
1088
1254
|
data_retention: z4.enum(["days", "months", "years", "indefinite"]).optional(),
|
|
1089
1255
|
consistency: z4.enum(["strict", "eventual", "mixed"]).optional(),
|
|
1090
1256
|
data_residency: z4.enum(["any", "us", "eu"]).optional()
|
|
@@ -1159,7 +1325,7 @@ function registerGenerate(server, client, resolver) {
|
|
|
1159
1325
|
"generate_architecture",
|
|
1160
1326
|
{
|
|
1161
1327
|
title: "Generate a SkeletIQ architecture",
|
|
1162
|
-
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, and the scale and constraints it runs under
|
|
1328
|
+
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, and the scale and constraints it runs under. A prompt that names no system to design comes back as clarification_required, and so does one that names a system but no scale to build it for \u2014 how many users or requests it has to serve. Answer with clarification_answers; there is no way to skip the questions, because a design built on an invented scale is sized, priced and judged against a number nobody chose.",
|
|
1163
1329
|
inputSchema: generateInput,
|
|
1164
1330
|
outputSchema: generateOutput,
|
|
1165
1331
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
@@ -1273,7 +1439,7 @@ function clarificationResult(questions) {
|
|
|
1273
1439
|
options: (question.options ?? []).filter((option) => !(question.decline_options ?? []).includes(option))
|
|
1274
1440
|
}));
|
|
1275
1441
|
const lines = [
|
|
1276
|
-
"SkeletIQ will not design from this prompt yet \u2014 it
|
|
1442
|
+
"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 scale it runs at. Nothing was generated and nothing was charged.",
|
|
1277
1443
|
"",
|
|
1278
1444
|
...structured.flatMap((question) => [
|
|
1279
1445
|
`${question.id}: ${question.question}`,
|
|
@@ -1416,7 +1582,10 @@ function sliceComponent(design, componentId, ledger) {
|
|
|
1416
1582
|
incoming: connections.filter((c) => c.target === component.id),
|
|
1417
1583
|
related_decisions: related,
|
|
1418
1584
|
assumed_decisions: assumedAmong(design, related),
|
|
1419
|
-
...ledger ? {
|
|
1585
|
+
...ledger ? {
|
|
1586
|
+
cited_requirements: citedRequirements(ledger, component.id),
|
|
1587
|
+
withdrawn_requirements: withdrawnRequirements(ledger, component.id)
|
|
1588
|
+
} : {}
|
|
1420
1589
|
};
|
|
1421
1590
|
}
|
|
1422
1591
|
function citedRequirements(ledger, componentId) {
|
|
@@ -1424,13 +1593,30 @@ function citedRequirements(ledger, componentId) {
|
|
|
1424
1593
|
(ledger.rows ?? []).filter((row) => (row.cited_by_components ?? []).includes(componentId))
|
|
1425
1594
|
);
|
|
1426
1595
|
}
|
|
1427
|
-
|
|
1596
|
+
function withdrawnRequirements(ledger, componentId) {
|
|
1597
|
+
return [...ledger.removed ?? [], ...ledger.superseded ?? []].filter(
|
|
1598
|
+
(row) => (row.cited_by_components ?? []).includes(componentId)
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
var VERDICT_ORDER = ["fails", "unmet", "not_checked", "not_checkable", "unchecked", "no_gap_found", "passes", "cited"];
|
|
1602
|
+
function isCitedRequirement(row) {
|
|
1603
|
+
return (row.cited_by_components?.length ?? 0) > 0 || (row.cited_by_decisions?.length ?? 0) > 0;
|
|
1604
|
+
}
|
|
1605
|
+
var CITATION_ONLY_METHOD = "structure";
|
|
1606
|
+
function requirementVerdict(row) {
|
|
1607
|
+
const verdict = (row.verdict ?? row.status ?? "").trim();
|
|
1608
|
+
if (verdict === "fails" && (row.check_method ?? "").trim() === CITATION_ONLY_METHOD) return "";
|
|
1609
|
+
return verdict;
|
|
1610
|
+
}
|
|
1428
1611
|
function orderRequirementRows(rows) {
|
|
1429
|
-
return [...rows].sort((a, b) =>
|
|
1612
|
+
return [...rows].sort((a, b) => verdictRank(a) - verdictRank(b));
|
|
1430
1613
|
}
|
|
1431
|
-
function
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1614
|
+
function verdictRank(row) {
|
|
1615
|
+
const verdict = requirementVerdict(row);
|
|
1616
|
+
if (verdict === "fails" || verdict === "unmet") return 0;
|
|
1617
|
+
if (!isCitedRequirement(row)) return 1;
|
|
1618
|
+
const index = VERDICT_ORDER.indexOf(verdict);
|
|
1619
|
+
return index === -1 ? VERDICT_ORDER.length + 1 : index + 2;
|
|
1434
1620
|
}
|
|
1435
1621
|
function decisionTexts(design) {
|
|
1436
1622
|
return decisionEntries(design).map((entry) => entry.text);
|
|
@@ -1494,11 +1680,11 @@ function normalize(value) {
|
|
|
1494
1680
|
}
|
|
1495
1681
|
|
|
1496
1682
|
// src/tools/get-design.ts
|
|
1497
|
-
var MODES = ["overview", "component", "brief", "readiness", "build_order", "gaps"];
|
|
1683
|
+
var MODES = ["overview", "component", "brief", "todo", "readiness", "build_order", "gaps"];
|
|
1498
1684
|
var input3 = z5.object({
|
|
1499
1685
|
project_id: z5.string().describe("A SkeletIQ project id, or its exact name."),
|
|
1500
1686
|
mode: z5.enum(MODES).describe(
|
|
1501
|
-
"overview: the whole design
|
|
1687
|
+
"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
1688
|
),
|
|
1503
1689
|
component_id: z5.string().optional().describe('Required for mode "component". The component id, or its name.'),
|
|
1504
1690
|
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 +1712,7 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1526
1712
|
"get_design",
|
|
1527
1713
|
{
|
|
1528
1714
|
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.',
|
|
1715
|
+
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
1716
|
inputSchema: input3,
|
|
1531
1717
|
outputSchema: output3,
|
|
1532
1718
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }
|
|
@@ -1603,6 +1789,11 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1603
1789
|
// Same order as the overview: what was asked for, then how the design
|
|
1604
1790
|
// answered.
|
|
1605
1791
|
...requirementsServed(slice.cited_requirements),
|
|
1792
|
+
// Said only when the design still carries the id. A component's
|
|
1793
|
+
// `requirement_ids` are never rewritten when a requirement is
|
|
1794
|
+
// withdrawn, so without this the row would leave the answer in
|
|
1795
|
+
// silence and the id on the box would resolve to nothing again.
|
|
1796
|
+
...requirementsWithdrawn(slice.withdrawn_requirements),
|
|
1606
1797
|
// Labelled, and only when there are any. These bullets used to be
|
|
1607
1798
|
// printed bare under "Called by:", so a decision about the component
|
|
1608
1799
|
// read as a continuation of its edge list — and a design with no
|
|
@@ -1649,6 +1840,33 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1649
1840
|
versionLine(facts)
|
|
1650
1841
|
].filter(Boolean).join("\n"));
|
|
1651
1842
|
}
|
|
1843
|
+
case "todo": {
|
|
1844
|
+
const body = await client.request(`/architectures/${resolved.architectureId}/todo`);
|
|
1845
|
+
const todo = TodoSchema.parse(body);
|
|
1846
|
+
const facts = factsFrom(resolved);
|
|
1847
|
+
const groups = todo.groups ?? [];
|
|
1848
|
+
const blocking = groups.find((group) => group.key === "blocks_handoff");
|
|
1849
|
+
const total = groups.reduce((count, group) => count + group.items.length, 0);
|
|
1850
|
+
return ok({ ...base, ...facts, data: todo }, [
|
|
1851
|
+
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:"),
|
|
1852
|
+
...groups.flatMap(todoGroupBlock),
|
|
1853
|
+
// A source that could not be read is stated, never left to look like a
|
|
1854
|
+
// shorter list. An agent acting on "nothing is outstanding" when the
|
|
1855
|
+
// simulation results failed to load is the exact harm this prevents.
|
|
1856
|
+
...todo.unavailable_sources?.length ? [
|
|
1857
|
+
"",
|
|
1858
|
+
"Some of this version could not be read, so the list above is incomplete:",
|
|
1859
|
+
...todo.unavailable_sources.map((entry) => `- ${entry.source}: ${entry.reason}`)
|
|
1860
|
+
] : [],
|
|
1861
|
+
"",
|
|
1862
|
+
// The actions are the person's. An agent that "accepts" an assumption
|
|
1863
|
+
// has decided something on their behalf, which is the one thing this
|
|
1864
|
+
// surface must never do — and the server refuses it anyway, because an
|
|
1865
|
+
// agent token reaches no write route.
|
|
1866
|
+
"Fix what you can in the design. Answering, accepting and dismissing are the person's to do \u2014 ask, do not decide for them.",
|
|
1867
|
+
versionLine(facts)
|
|
1868
|
+
].join("\n"));
|
|
1869
|
+
}
|
|
1652
1870
|
case "readiness": {
|
|
1653
1871
|
const body = await client.request(`/architectures/${resolved.architectureId}/readiness`);
|
|
1654
1872
|
const readiness = ReadinessSchema.parse(body);
|
|
@@ -1656,7 +1874,10 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1656
1874
|
const gates = readiness.rows.filter((row) => row.category === "gate");
|
|
1657
1875
|
const open = gates.filter((row) => row.gating);
|
|
1658
1876
|
const unanswered = gates.filter((row) => row.state === "unknown");
|
|
1659
|
-
const
|
|
1877
|
+
const accepts = readiness.rows.filter((row) => row.category === "accept");
|
|
1878
|
+
const advisories = readiness.rows.filter(
|
|
1879
|
+
(row) => row.category !== "gate" && row.category !== "accept"
|
|
1880
|
+
);
|
|
1660
1881
|
const unrun = new Set(readiness.unrun_checks ?? []);
|
|
1661
1882
|
const verdict = readiness.verdict ?? (readiness.ready ? "ready" : "outstanding");
|
|
1662
1883
|
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 +1885,22 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1664
1885
|
return ok({ ...base, ...facts, data: readiness }, [
|
|
1665
1886
|
headline,
|
|
1666
1887
|
...listed.map((row) => `- ${row.label}: ${describeRow(row)}`),
|
|
1888
|
+
...accepts.length ? [
|
|
1889
|
+
"",
|
|
1890
|
+
"To accept (the person agrees to these rather than fixing them; accepting is free and never blocks):",
|
|
1891
|
+
...accepts.map((row) => `- ${row.label}: ${describeRow(row)}`)
|
|
1892
|
+
] : [],
|
|
1667
1893
|
"",
|
|
1668
1894
|
"Advisory checks (they never block, and some need a paid plan to clear):",
|
|
1669
|
-
// An advisory
|
|
1670
|
-
//
|
|
1671
|
-
//
|
|
1895
|
+
// An advisory with no result is marked, not counted — "nothing to count
|
|
1896
|
+
// here" is a fact an agent being handed work is entitled to, and it is
|
|
1897
|
+
// invisible in a bare count of open items. The mark claims no more than
|
|
1898
|
+
// that: an advisory can be unanswered because no result is *stored* for
|
|
1899
|
+
// this version (the advisor row since the server stopped writing on
|
|
1900
|
+
// read), not only because nothing ever ran, and the server's own
|
|
1901
|
+
// sentence, when it sends one, says which.
|
|
1672
1902
|
...advisories.map(
|
|
1673
|
-
(row) => `- ${row.label}: ${describeRow(row)}${unrun.has(row.key) ? " (
|
|
1903
|
+
(row) => `- ${row.label}: ${describeRow(row)}${unrun.has(row.key) && !row.detail ? " (no result for this version)" : ""}`
|
|
1674
1904
|
),
|
|
1675
1905
|
// The same list, under the same heading, as the app shows beside its
|
|
1676
1906
|
// Release button. A person clicking Release is told what the release
|
|
@@ -1683,7 +1913,7 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1683
1913
|
...readiness.release_warnings.map((warning) => `- ${warning}`)
|
|
1684
1914
|
] : [],
|
|
1685
1915
|
"",
|
|
1686
|
-
|
|
1916
|
+
'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
1917
|
versionLine(facts)
|
|
1688
1918
|
].join("\n"));
|
|
1689
1919
|
}
|
|
@@ -1745,14 +1975,20 @@ function registerGetDesign(server, client, resolver) {
|
|
|
1745
1975
|
function requirementBlock(ledger) {
|
|
1746
1976
|
const rows = orderRequirementRows(ledger?.rows ?? []);
|
|
1747
1977
|
if (!ledger || rows.length === 0) return [];
|
|
1748
|
-
const attention = [
|
|
1978
|
+
const attention = [
|
|
1979
|
+
[rows.filter((row) => requirementVerdict(row) === "fails").length, "failing"],
|
|
1980
|
+
[rows.filter((row) => !isCitedRequirement(row)).length, "cited by nothing"],
|
|
1981
|
+
[rows.filter((row) => requirementVerdict(row) === "not_checked" || requirementVerdict(row) === "not_checkable").length, "unchecked"]
|
|
1982
|
+
].filter(([count]) => count > 0).map(([count, word]) => `${count} ${word}`);
|
|
1749
1983
|
return [
|
|
1750
1984
|
"",
|
|
1751
1985
|
`Requirements \u2014 ${rows.length} total${attention.length ? `; ${attention.join(", ")}` : ""}:`,
|
|
1752
1986
|
...rows.map(requirementLine),
|
|
1753
|
-
"
|
|
1754
|
-
"
|
|
1755
|
-
"
|
|
1987
|
+
"Verdicts: `fails` \u2014 a named check measured the design as not meeting it, and says which and why;",
|
|
1988
|
+
"`passes` \u2014 a check measured it against a figure the person stated; `no_gap_found` \u2014 nothing",
|
|
1989
|
+
"contradicted it, which is not the same as proven; `not_checked` / `not_checkable` \u2014 nobody",
|
|
1990
|
+
"established either way, and neither is fine. `cited by nothing` means no component and no",
|
|
1991
|
+
"decision in the design claims to address it.",
|
|
1756
1992
|
// Two facts about how far the statuses can be trusted, said only when the server states
|
|
1757
1993
|
// them. An absent `verification_state` is an older server, not a verifier that did not run,
|
|
1758
1994
|
// and claiming the second would be this package inventing a fact about someone's design.
|
|
@@ -1776,14 +2012,29 @@ function requirementsServed(rows) {
|
|
|
1776
2012
|
"",
|
|
1777
2013
|
"Requirements the design cites this component for:",
|
|
1778
2014
|
...rows.map(requirementLine),
|
|
1779
|
-
"`
|
|
1780
|
-
"
|
|
2015
|
+
"`fails` \u2014 a named check measured the design as not meeting it; `not_checked` \u2014 nobody",
|
|
2016
|
+
"established either way; `no_gap_found` \u2014 nothing contradicted it, which is not the same as proven."
|
|
2017
|
+
];
|
|
2018
|
+
}
|
|
2019
|
+
function requirementsWithdrawn(rows) {
|
|
2020
|
+
if (!rows?.length) return [];
|
|
2021
|
+
return [
|
|
2022
|
+
"",
|
|
2023
|
+
"This component still cites these ids, and they are no longer requirements \u2014 do not build to them:",
|
|
2024
|
+
...rows.map(
|
|
2025
|
+
(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"}`}]`
|
|
2026
|
+
)
|
|
1781
2027
|
];
|
|
1782
2028
|
}
|
|
1783
2029
|
function requirementLine(row) {
|
|
1784
|
-
const
|
|
2030
|
+
const verdict = requirementVerdict(row);
|
|
2031
|
+
const marks = [verdict, ...isCitedRequirement(row) ? [] : ["cited by nothing"]].filter(Boolean);
|
|
2032
|
+
const label = marks.length > 0 ? ` [${marks.join(" \xB7 ")}]` : "";
|
|
1785
2033
|
const text = (row.text ?? "").replace(/\s+/g, " ").trim();
|
|
1786
|
-
|
|
2034
|
+
const reason = (row.verdict_reason ?? "").replace(/\s+/g, " ").trim();
|
|
2035
|
+
const by = (row.checked_by ?? "").trim();
|
|
2036
|
+
const because = reason ? ` \u2014 ${reason}${by ? ` (checked by ${by})` : ""}` : by ? ` \u2014 checked by ${by}` : "";
|
|
2037
|
+
return `- ${row.id}${label} ${text || "(no text recorded for this requirement)"}${because}`;
|
|
1787
2038
|
}
|
|
1788
2039
|
function settledAnswer(gap) {
|
|
1789
2040
|
const note = (gap.note ?? "").trim();
|
|
@@ -1795,8 +2046,20 @@ function settledAnswer(gap) {
|
|
|
1795
2046
|
if (gap.action === "confirmed") return "confirmed as it stands";
|
|
1796
2047
|
return "answered, but no wording was recorded \u2014 ask before relying on it";
|
|
1797
2048
|
}
|
|
2049
|
+
function todoGroupBlock(group) {
|
|
2050
|
+
if (group.items.length === 0) return [];
|
|
2051
|
+
return ["", `${group.label} (${group.items.length}):`, ...group.items.map(todoLine)];
|
|
2052
|
+
}
|
|
2053
|
+
function todoLine(item) {
|
|
2054
|
+
const severity = item.severity && item.severity !== "none" ? `[${item.severity}] ` : "";
|
|
2055
|
+
const raisedBy = [...new Set((item.sources ?? []).map((source) => source.source))];
|
|
2056
|
+
const raised = raisedBy.length > 0 ? ` \u2014 raised by ${raisedBy.join(", ")}` : "";
|
|
2057
|
+
const carried = item.carried ? item.carried_from_version ? ` \u2014 carried from v${item.carried_from_version}` : " \u2014 carried from an earlier version" : "";
|
|
2058
|
+
const components = item.components?.length ? ` \u2014 affects ${item.components.join(", ")}` : "";
|
|
2059
|
+
return `- ${severity}${item.title.replace(/\s+/g, " ").trim()}${components}${raised}${carried}`;
|
|
2060
|
+
}
|
|
1798
2061
|
function describeRow(row) {
|
|
1799
|
-
if (row.state === "unknown") return
|
|
2062
|
+
if (row.state === "unknown") return `not checked${row.detail ? ` \u2014 ${row.detail}` : ""}`;
|
|
1800
2063
|
return `${row.count}${row.detail ? ` \u2014 ${row.detail}` : ""}`;
|
|
1801
2064
|
}
|
|
1802
2065
|
|
package/package.json
CHANGED