iterate-plugin 3.2.1 → 3.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 +123 -90
- package/README.zh-CN.md +129 -80
- package/dist/approval-gate.js +0 -17
- package/dist/tools/checkpoint.js +22 -3
- package/dist/tools/defense-events.js +4 -1
- package/dist/tools/defense-store.js +9 -3
- package/dist/tools/experience-bank.js +4 -1
- package/dist/tools/experience-store.js +9 -3
- package/dist/tools/quality-gate.js +4 -1
- package/dist/tools/quality-store.js +9 -3
- package/dist/tools/transcript.js +8 -1
- package/dist/transcript.js +10 -0
- package/lib/client.js +549 -88
- package/lib/parse.js +352 -0
- package/package.json +2 -2
- package/src/approval-gate.ts +0 -26
- package/src/client/index.ts +349 -73
- package/src/tools/checkpoint.ts +22 -3
- package/src/tools/defense-events.ts +4 -1
- package/src/tools/defense-store.ts +13 -4
- package/src/tools/experience-bank.ts +4 -1
- package/src/tools/experience-store.ts +13 -4
- package/src/tools/quality-gate.ts +4 -1
- package/src/tools/quality-store.ts +13 -4
- package/src/tools/transcript.ts +8 -1
- package/src/transcript.ts +11 -0
package/lib/parse.js
CHANGED
|
@@ -557,12 +557,15 @@ export function normalizeTranscript(manifest) {
|
|
|
557
557
|
: null
|
|
558
558
|
|
|
559
559
|
const ap = safeGet(src, 'approval')
|
|
560
|
+
const tm = safeGet(src, 'taskMode')
|
|
560
561
|
return {
|
|
561
562
|
version: asNum(safeGet(src, 'version')),
|
|
562
563
|
project: asStr(safeGet(src, 'project')),
|
|
563
564
|
updatedAt: asStr(safeGet(src, 'updatedAt')),
|
|
564
565
|
active: asBool(safeGet(src, 'active')),
|
|
565
566
|
mode: asStr(safeGet(src, 'mode')) || null,
|
|
567
|
+
// v3.0: harness task_mode (code/iterate), tolerated when absent.
|
|
568
|
+
taskMode: tm === 'code' || tm === 'iterate' ? tm : null,
|
|
566
569
|
goal: asStr(safeGet(src, 'goal')),
|
|
567
570
|
phases: asArray(safeGet(src, 'phases')).map((p) => (typeof p === 'string' ? p : '')),
|
|
568
571
|
round: asNum(safeGet(src, 'round')),
|
|
@@ -586,6 +589,355 @@ export function normalizeTranscript(manifest) {
|
|
|
586
589
|
}
|
|
587
590
|
}
|
|
588
591
|
|
|
592
|
+
// ─── Quality command center session scans (v3.1+) ───────────────────────────
|
|
593
|
+
//
|
|
594
|
+
// The F8/F9/F10 observatory tabs render machine-readable data that the model
|
|
595
|
+
// already surfaces through `iterate_quality_gate` / `iterate_experience` /
|
|
596
|
+
// `iterate_defense_events`. Because the client cannot call harness tools, these
|
|
597
|
+
// scanners recover the LATEST result of each tool from the in-memory session
|
|
598
|
+
// stream (same reverse-chronological strategy as scanSessionForTranscript) and
|
|
599
|
+
// normalize it into a JSON-safe shape for rendering. Every field degrades to a
|
|
600
|
+
// safe default so a partial/malformed result never crashes the slot.
|
|
601
|
+
|
|
602
|
+
const DEFENSE_EVENT_TYPES = ['precondition_failed', 'rollback', 'invariant_violated', 'assumption_falsified']
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Check whether `obj` is a QualityGateSnapshot as produced by quality-store.ts.
|
|
606
|
+
* Requires the three gate discriminators (overallStatus + overallScore +
|
|
607
|
+
* dimensions array) so unrelated `{ snapshot: ... }` shapes never collide.
|
|
608
|
+
*
|
|
609
|
+
* @param {unknown} obj
|
|
610
|
+
* @returns {boolean}
|
|
611
|
+
*/
|
|
612
|
+
export function isQualityGateSnapshot(obj) {
|
|
613
|
+
if (!obj || typeof obj !== 'object') return false
|
|
614
|
+
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
615
|
+
const status = safeGet(o, 'overallStatus')
|
|
616
|
+
return (
|
|
617
|
+
(status === 'pass' || status === 'fail' || status === 'pending') &&
|
|
618
|
+
typeof safeGet(o, 'overallScore') === 'number' &&
|
|
619
|
+
Array.isArray(safeGet(o, 'dimensions'))
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Check whether `obj` is an `iterate_experience` result node. `list`/`search`
|
|
625
|
+
* carry an `entries` array; `get`/`add` carry a single `entry` object. Either
|
|
626
|
+
* shape is enough to render the F9 experience bank.
|
|
627
|
+
*
|
|
628
|
+
* @param {unknown} obj
|
|
629
|
+
* @returns {boolean}
|
|
630
|
+
*/
|
|
631
|
+
export function isExperienceBankResult(obj) {
|
|
632
|
+
if (!obj || typeof obj !== 'object') return false
|
|
633
|
+
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
634
|
+
const entry = safeGet(o, 'entry')
|
|
635
|
+
return (
|
|
636
|
+
(Array.isArray(safeGet(o, 'entries')) && typeof safeGet(o, 'count') === 'number') ||
|
|
637
|
+
(!!entry && typeof entry === 'object' && typeof safeGet(o, 'operation') === 'string')
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Check whether `obj` is an `iterate_defense_events` result node. `list` carries
|
|
643
|
+
* an `events` array, `counts` / `record` carry a `counts` map. Either shape is
|
|
644
|
+
* enough to render the F10 defense event stream.
|
|
645
|
+
*
|
|
646
|
+
* @param {unknown} obj
|
|
647
|
+
* @returns {boolean}
|
|
648
|
+
*/
|
|
649
|
+
export function isDefenseEventsResult(obj) {
|
|
650
|
+
if (!obj || typeof obj !== 'object') return false
|
|
651
|
+
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
652
|
+
const counts = safeGet(o, 'counts')
|
|
653
|
+
return (
|
|
654
|
+
(Array.isArray(safeGet(o, 'events')) && typeof safeGet(o, 'count') === 'number') ||
|
|
655
|
+
(!!counts && typeof counts === 'object' && typeof safeGet(o, 'operation') === 'string')
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Shared deep-find: first node in `obj` (circular + depth guarded) satisfying a
|
|
661
|
+
* predicate, with the same traversal semantics as findReportInObject.
|
|
662
|
+
*
|
|
663
|
+
* @param {unknown} obj
|
|
664
|
+
* @param {(o: Record<string, unknown>) => boolean} predicate
|
|
665
|
+
* @param {Set<unknown>} [seen]
|
|
666
|
+
* @param {number} [maxDepth=20]
|
|
667
|
+
* @returns {Record<string, unknown> | null}
|
|
668
|
+
*/
|
|
669
|
+
function findFirstInObject(obj, predicate, seen, maxDepth = 20) {
|
|
670
|
+
if (maxDepth <= 0) return null
|
|
671
|
+
if (!obj || typeof obj !== 'object') return null
|
|
672
|
+
|
|
673
|
+
const s = seen || new Set()
|
|
674
|
+
if (s.has(obj)) return null
|
|
675
|
+
s.add(obj)
|
|
676
|
+
|
|
677
|
+
if (Array.isArray(obj)) {
|
|
678
|
+
for (const item of obj) {
|
|
679
|
+
const found = findFirstInObject(item, predicate, s, maxDepth - 1)
|
|
680
|
+
if (found) return found
|
|
681
|
+
}
|
|
682
|
+
return null
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (typeof obj === 'object') {
|
|
686
|
+
const o = /** @type {Record<string, unknown>} */ (obj)
|
|
687
|
+
if (predicate(o)) return o
|
|
688
|
+
for (const key of safeKeys(o)) {
|
|
689
|
+
const val = safeGet(o, key)
|
|
690
|
+
if (val && typeof val === 'object') {
|
|
691
|
+
const found = findFirstInObject(val, predicate, s, maxDepth - 1)
|
|
692
|
+
if (found) return found
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return null
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** Deep-find the first QualityGateSnapshot inside `obj`. */
|
|
700
|
+
function findQualityGateInObject(obj) {
|
|
701
|
+
return findFirstInObject(obj, (o) => isQualityGateSnapshot(o))
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** Deep-find the first `iterate_experience` result node inside `obj`. */
|
|
705
|
+
function findExperienceResultInObject(obj) {
|
|
706
|
+
return findFirstInObject(obj, (o) => isExperienceBankResult(o))
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Deep-find the first `iterate_defense_events` result node inside `obj`. */
|
|
710
|
+
function findDefenseResultInObject(obj) {
|
|
711
|
+
return findFirstInObject(obj, (o) => isDefenseEventsResult(o))
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Return the raw result/message node of the most recent execution of `toolName`
|
|
716
|
+
* in a session snapshot. Scans `session.toolCalls` (reverse chronological,
|
|
717
|
+
* matching the harness's in-memory stream shape) and falls back to
|
|
718
|
+
* `session.messages[].content` (assistant tool-call blocks). Returns the raw
|
|
719
|
+
* node (object or string) or null.
|
|
720
|
+
*
|
|
721
|
+
* @param {unknown} session
|
|
722
|
+
* @param {string} toolName
|
|
723
|
+
* @returns {unknown}
|
|
724
|
+
*/
|
|
725
|
+
function latestToolResultNode(session, toolName) {
|
|
726
|
+
if (!session || typeof session !== 'object') return null
|
|
727
|
+
|
|
728
|
+
const s = /** @type {Record<string, unknown>} */ (session)
|
|
729
|
+
|
|
730
|
+
const toolCalls = safeGet(s, 'toolCalls')
|
|
731
|
+
if (Array.isArray(toolCalls)) {
|
|
732
|
+
const calls = /** @type {Array<Record<string, unknown>>} */ (toolCalls)
|
|
733
|
+
for (let i = calls.length - 1; i >= 0; i--) {
|
|
734
|
+
const call = calls[i]
|
|
735
|
+
if (!call) continue
|
|
736
|
+
const tool = String(safeGet(call, 'tool') ?? '')
|
|
737
|
+
if (tool !== toolName && !tool.endsWith(toolName)) continue
|
|
738
|
+
const result = safeGet(call, 'result')
|
|
739
|
+
if (result !== undefined && result !== null) return result
|
|
740
|
+
const message = safeGet(call, 'message')
|
|
741
|
+
if (message !== undefined && message !== null) return message
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const messages = safeGet(s, 'messages')
|
|
746
|
+
if (Array.isArray(messages)) {
|
|
747
|
+
const msgs = /** @type {Array<Record<string, unknown>>} */ (messages)
|
|
748
|
+
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
749
|
+
const msg = msgs[i]
|
|
750
|
+
if (!msg) continue
|
|
751
|
+
const content = safeGet(msg, 'content')
|
|
752
|
+
if (content !== undefined && content !== null) return content
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return null
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Normalize a QualityGateSnapshot into a JSON-safe object (see
|
|
761
|
+
* normalizeTranscript for the defensive style contract).
|
|
762
|
+
*
|
|
763
|
+
* @param {Record<string, unknown> | null | undefined} raw
|
|
764
|
+
* @returns {Record<string, unknown>}
|
|
765
|
+
*/
|
|
766
|
+
export function normalizeQualityGateSnapshot(raw) {
|
|
767
|
+
const src = raw && typeof raw === 'object'
|
|
768
|
+
? /** @type {Record<string, unknown>} */ (raw)
|
|
769
|
+
: {}
|
|
770
|
+
const asNum = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0)
|
|
771
|
+
const asStr = (v) => (typeof v === 'string' ? v : '')
|
|
772
|
+
const status = safeGet(src, 'overallStatus')
|
|
773
|
+
const dims = Array.isArray(safeGet(src, 'dimensions')) ? safeGet(src, 'dimensions') : []
|
|
774
|
+
return {
|
|
775
|
+
timestamp: asStr(safeGet(src, 'timestamp')),
|
|
776
|
+
overallStatus: status === 'pass' || status === 'fail' || status === 'pending' ? status : 'pending',
|
|
777
|
+
overallScore: asNum(safeGet(src, 'overallScore')),
|
|
778
|
+
verificationPassRate: asNum(safeGet(src, 'verificationPassRate')),
|
|
779
|
+
totalChecks: asNum(safeGet(src, 'totalChecks')),
|
|
780
|
+
passedChecks: asNum(safeGet(src, 'passedChecks')),
|
|
781
|
+
failedChecks: asNum(safeGet(src, 'failedChecks')),
|
|
782
|
+
failReason: asStr(safeGet(src, 'failReason')) || null,
|
|
783
|
+
totalFindings: asNum(safeGet(src, 'totalFindings')),
|
|
784
|
+
criticalCount: asNum(safeGet(src, 'criticalCount')),
|
|
785
|
+
highCount: asNum(safeGet(src, 'highCount')),
|
|
786
|
+
mediumCount: asNum(safeGet(src, 'mediumCount')),
|
|
787
|
+
lowCount: asNum(safeGet(src, 'lowCount')),
|
|
788
|
+
dimensions: (/** @type {unknown[]} */ (dims)).map((d) => {
|
|
789
|
+
const rec = /** @type {Record<string, unknown>} */ (d && typeof d === 'object' ? d : {})
|
|
790
|
+
const dimStatus = safeGet(rec, 'status')
|
|
791
|
+
return {
|
|
792
|
+
dimension: asStr(safeGet(rec, 'dimension')),
|
|
793
|
+
convergenceRate: asNum(safeGet(rec, 'convergenceRate')),
|
|
794
|
+
findingsCount: asNum(safeGet(rec, 'findingsCount')),
|
|
795
|
+
fixedCount: asNum(safeGet(rec, 'fixedCount')),
|
|
796
|
+
score: asNum(safeGet(rec, 'score')),
|
|
797
|
+
status: dimStatus === 'pass' || dimStatus === 'warn' || dimStatus === 'fail' ? dimStatus : 'warn',
|
|
798
|
+
}
|
|
799
|
+
}),
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Scan a session snapshot for the latest `iterate_quality_gate` result and
|
|
805
|
+
* return its normalized QualityGateSnapshot (or null).
|
|
806
|
+
*
|
|
807
|
+
* @param {unknown} session
|
|
808
|
+
* @returns {Record<string, unknown> | null}
|
|
809
|
+
*/
|
|
810
|
+
export function scanSessionForQualityGate(session) {
|
|
811
|
+
const node = latestToolResultNode(session, 'iterate_quality_gate')
|
|
812
|
+
if (node === null) return null
|
|
813
|
+
const found = findQualityGateInObject(node)
|
|
814
|
+
if (!found) return null
|
|
815
|
+
return normalizeQualityGateSnapshot(found)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Normalize an `iterate_experience` result node into a JSON-safe object.
|
|
820
|
+
* `get`/`add` results (single `entry`) are folded into the `entries` array so
|
|
821
|
+
* the F9 panel has one rendering path regardless of operation.
|
|
822
|
+
*
|
|
823
|
+
* @param {Record<string, unknown> | null | undefined} raw
|
|
824
|
+
* @returns {Record<string, unknown>}
|
|
825
|
+
*/
|
|
826
|
+
export function normalizeExperienceBankResult(raw) {
|
|
827
|
+
const src = raw && typeof raw === 'object'
|
|
828
|
+
? /** @type {Record<string, unknown>} */ (raw)
|
|
829
|
+
: {}
|
|
830
|
+
const asNum = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0)
|
|
831
|
+
const asStr = (v) => (typeof v === 'string' ? v : '')
|
|
832
|
+
const asArr = (v) => (Array.isArray(v) ? /** @type {unknown[]} */ (v) : [])
|
|
833
|
+
const entry = safeGet(src, 'entry')
|
|
834
|
+
const rawEntries = Array.isArray(safeGet(src, 'entries'))
|
|
835
|
+
? /** @type {unknown[]} */ (safeGet(src, 'entries'))
|
|
836
|
+
: (entry && typeof entry === 'object' ? [entry] : [])
|
|
837
|
+
return {
|
|
838
|
+
operation: asStr(safeGet(src, 'operation')),
|
|
839
|
+
count: asNum(safeGet(src, 'count')),
|
|
840
|
+
totalHits: asNum(safeGet(src, 'totalHits')),
|
|
841
|
+
added: safeGet(src, 'added') === true,
|
|
842
|
+
entries: rawEntries.map((e) => {
|
|
843
|
+
const rec = /** @type {Record<string, unknown>} */ (e && typeof e === 'object' ? e : {})
|
|
844
|
+
return {
|
|
845
|
+
id: asStr(safeGet(rec, 'id')),
|
|
846
|
+
timestamp: asStr(safeGet(rec, 'timestamp')),
|
|
847
|
+
dimension: asStr(safeGet(rec, 'dimension')),
|
|
848
|
+
pattern: asStr(safeGet(rec, 'pattern')),
|
|
849
|
+
description: asStr(safeGet(rec, 'description')),
|
|
850
|
+
verifiedFix: asStr(safeGet(rec, 'verifiedFix')),
|
|
851
|
+
findingSummary: asStr(safeGet(rec, 'findingSummary')),
|
|
852
|
+
severity: asStr(safeGet(rec, 'severity')),
|
|
853
|
+
hitCount: asNum(safeGet(rec, 'hitCount')),
|
|
854
|
+
lastHitAt: asStr(safeGet(rec, 'lastHitAt')) || null,
|
|
855
|
+
files: asArr(safeGet(rec, 'files')).map((f) => (typeof f === 'string' ? f : '')),
|
|
856
|
+
tags: asArr(safeGet(rec, 'tags')).map((t) => (typeof t === 'string' ? t : '')),
|
|
857
|
+
}
|
|
858
|
+
}),
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Scan a session snapshot for the latest `iterate_experience` result and return
|
|
864
|
+
* its normalized values (or null when the session has none).
|
|
865
|
+
*
|
|
866
|
+
* @param {unknown} session
|
|
867
|
+
* @returns {Record<string, unknown> | null}
|
|
868
|
+
*/
|
|
869
|
+
export function scanSessionForExperienceBank(session) {
|
|
870
|
+
const node = latestToolResultNode(session, 'iterate_experience')
|
|
871
|
+
if (node === null) return null
|
|
872
|
+
const found = findExperienceResultInObject(node)
|
|
873
|
+
if (!found) return null
|
|
874
|
+
return normalizeExperienceBankResult(found)
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Normalize an `iterate_defense_events` result node into a JSON-safe object.
|
|
879
|
+
* `record` results (single `event` + `counts`) fold into the same shape as
|
|
880
|
+
* `list`/`counts` so the F10 panel has one rendering path.
|
|
881
|
+
*
|
|
882
|
+
* @param {Record<string, unknown> | null | undefined} raw
|
|
883
|
+
* @returns {Record<string, unknown>}
|
|
884
|
+
*/
|
|
885
|
+
export function normalizeDefenseEventsResult(raw) {
|
|
886
|
+
const src = raw && typeof raw === 'object'
|
|
887
|
+
? /** @type {Record<string, unknown>} */ (raw)
|
|
888
|
+
: {}
|
|
889
|
+
const asNum = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0)
|
|
890
|
+
const asStr = (v) => (typeof v === 'string' ? v : '')
|
|
891
|
+
const countsRaw = safeGet(src, 'counts') && typeof safeGet(src, 'counts') === 'object'
|
|
892
|
+
? /** @type {Record<string, unknown>} */ (safeGet(src, 'counts'))
|
|
893
|
+
: {}
|
|
894
|
+
const counts = /** @type {Record<string, number>} */ ({})
|
|
895
|
+
for (const type of DEFENSE_EVENT_TYPES) {
|
|
896
|
+
const n = safeGet(countsRaw, type)
|
|
897
|
+
counts[type] = typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : 0
|
|
898
|
+
}
|
|
899
|
+
const event = safeGet(src, 'event')
|
|
900
|
+
const rawEvents = Array.isArray(safeGet(src, 'events'))
|
|
901
|
+
? /** @type {unknown[]} */ (safeGet(src, 'events'))
|
|
902
|
+
: (event && typeof event === 'object' ? [event] : [])
|
|
903
|
+
return {
|
|
904
|
+
operation: asStr(safeGet(src, 'operation')),
|
|
905
|
+
count: asNum(safeGet(src, 'count')),
|
|
906
|
+
language: asStr(safeGet(src, 'language')),
|
|
907
|
+
counts,
|
|
908
|
+
events: rawEvents.map((e) => {
|
|
909
|
+
const rec = /** @type {Record<string, unknown>} */ (e && typeof e === 'object' ? e : {})
|
|
910
|
+
return {
|
|
911
|
+
id: asStr(safeGet(rec, 'id')),
|
|
912
|
+
timestamp: asStr(safeGet(rec, 'timestamp')),
|
|
913
|
+
round: asNum(safeGet(rec, 'round')),
|
|
914
|
+
type: asStr(safeGet(rec, 'type')),
|
|
915
|
+
description: asStr(safeGet(rec, 'description')),
|
|
916
|
+
defense: asStr(safeGet(rec, 'defense')),
|
|
917
|
+
outcome: asStr(safeGet(rec, 'outcome')),
|
|
918
|
+
file: asStr(safeGet(rec, 'file')) || null,
|
|
919
|
+
line: asNum(safeGet(rec, 'line')) || null,
|
|
920
|
+
severity: asStr(safeGet(rec, 'severity')),
|
|
921
|
+
}
|
|
922
|
+
}),
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Scan a session snapshot for the latest `iterate_defense_events` result and
|
|
928
|
+
* return its normalized values (or null when the session has none).
|
|
929
|
+
*
|
|
930
|
+
* @param {unknown} session
|
|
931
|
+
* @returns {Record<string, unknown> | null}
|
|
932
|
+
*/
|
|
933
|
+
export function scanSessionForDefenseEvents(session) {
|
|
934
|
+
const node = latestToolResultNode(session, 'iterate_defense_events')
|
|
935
|
+
if (node === null) return null
|
|
936
|
+
const found = findDefenseResultInObject(node)
|
|
937
|
+
if (!found) return null
|
|
938
|
+
return normalizeDefenseEventsResult(found)
|
|
939
|
+
}
|
|
940
|
+
|
|
589
941
|
// ─── Run-summary / meta-review verdict detection ─────────────────────────────
|
|
590
942
|
|
|
591
943
|
/**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-plugin",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.3). Features: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus dry-run pure-review mode, quality gate compute/persist, writable experience bank, defense events stream (record + bilingual labels), and native command buttons — with a live quality command center (F8/F9/F10 render real session data), §8 assign-fix instruction, and task_mode indicator wired end to end.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/src/approval-gate.ts
CHANGED
|
@@ -102,30 +102,4 @@ export function decideApproval(
|
|
|
102
102
|
/** True when any destructive iterate tool is listed in a name set. */
|
|
103
103
|
export function isDestructiveIterateTool(name: unknown): boolean {
|
|
104
104
|
return typeof name === 'string' && DESTRUCTIVE_TOOLS.has(name)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Tool-facing gate result: run, or refuse (with an explicit human-approval signal). */
|
|
108
|
-
export type ToolGateResult =
|
|
109
|
-
| { ok: true }
|
|
110
|
-
| { ok: false; requiresApproval: true; reason: string }
|
|
111
|
-
| { ok: false; error: string }
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Evaluate an iterate tool's own boundary gate for a destructive call.
|
|
115
|
-
* `approvedArg` is the caller-supplied `approved: true` flag (human consent
|
|
116
|
-
* already obtained). Returns a run / refuse result without any I/O.
|
|
117
|
-
*/
|
|
118
|
-
export function toolGate(
|
|
119
|
-
policy: 'ask' | 'deny' | 'allow',
|
|
120
|
-
execution: ToolExecutionLike,
|
|
121
|
-
approvedArg?: unknown,
|
|
122
|
-
): ToolGateResult {
|
|
123
|
-
const decision = decideApproval(execution, policy)
|
|
124
|
-
if (decision.kind === 'allow') return { ok: true }
|
|
125
|
-
if (decision.kind === 'deny') {
|
|
126
|
-
return { ok: false, error: `Blocked by observatory approval policy: ${decision.reason}` }
|
|
127
|
-
}
|
|
128
|
-
// ask
|
|
129
|
-
if (approvedArg === true) return { ok: true }
|
|
130
|
-
return { ok: false, requiresApproval: true, reason: decision.reason }
|
|
131
105
|
}
|