tink-harness 1.9.3 → 1.9.5
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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,30 @@ All notable changes to Tink are tracked here.
|
|
|
6
6
|
|
|
7
7
|
No unreleased changes yet.
|
|
8
8
|
|
|
9
|
+
## [1.9.5] - 2026-06-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Run timeline entries now show a colored outcome badge, a monospace timestamp, and harness chips instead of a comma list.
|
|
14
|
+
- The Activity tab gained a summary strip: total runs, run window, and completed/blocked/failed/recorded counts.
|
|
15
|
+
- Memory cards now show reference counts and referencing-harness chips derived from `uses_memory` graph edges.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- The lifecycle generator now reads YAML-frontmatter `outcome:` values from run records, so completed runs are counted as successes instead of unknown.
|
|
20
|
+
- Removed a generic "blocked" word match that flagged runs as blocked when the word merely appeared in prose.
|
|
21
|
+
|
|
22
|
+
## [1.9.4] - 2026-06-10
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Harness cards now sort by usage and open with a smooth expand animation showing richer details: last used, success/failure counts, context cost, co-used harness chips, score factors, safe next action, and evidence handles.
|
|
27
|
+
- Added an evaluation & maintenance history section to the Harnesses tab, fed by `.tink/maintenance/ledger.jsonl` (new `maintenance_events` field in the lifecycle summary).
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- The lifecycle generator now strips a UTF-8 BOM before parsing JSONL files, so the first ledger entry is no longer dropped.
|
|
32
|
+
|
|
9
33
|
## [1.9.3] - 2026-06-10
|
|
10
34
|
|
|
11
35
|
### Added
|
package/VERSIONING.md
CHANGED
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ function readJson(filePath, fallback) {
|
|
|
15
15
|
|
|
16
16
|
function readLines(filePath) {
|
|
17
17
|
if (!fs.existsSync(filePath)) return [];
|
|
18
|
-
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
18
|
+
return fs.readFileSync(filePath, 'utf8').replace(/^/, '').split(/\r?\n/).filter(Boolean);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function listFiles(dirPath, suffix) {
|
|
@@ -83,10 +83,13 @@ function extractRefs(text, refs) {
|
|
|
83
83
|
function parseRun(filePath, harnessIds, ruleRefs, memoryRefs) {
|
|
84
84
|
const text = fs.readFileSync(filePath, 'utf8');
|
|
85
85
|
const statusMatch = text.match(/^Status:\s*([A-Za-z_-]+)/mi);
|
|
86
|
-
const
|
|
86
|
+
const outcomeMatch = text.match(/^outcome:\s*"?([A-Za-z_-]+)"?/mi);
|
|
87
|
+
const status = statusMatch
|
|
88
|
+
? statusMatch[1].toLowerCase()
|
|
89
|
+
: (outcomeMatch ? outcomeMatch[1].toLowerCase() : 'unknown');
|
|
87
90
|
const harnesses = extractSelectedHarnesses(text, harnessIds);
|
|
88
91
|
const failed = /check_failed|failed check|required check failed|verification failed/i.test(text);
|
|
89
|
-
const blocked = status === 'blocked' || /check_blocked
|
|
92
|
+
const blocked = status === 'blocked' || /check_blocked/i.test(text);
|
|
90
93
|
const completed = status === 'completed' || status === 'pass' || /npm test passed|verification passed/i.test(text);
|
|
91
94
|
return {
|
|
92
95
|
path: filePath,
|
|
@@ -517,6 +520,28 @@ function summarize(root) {
|
|
|
517
520
|
item.candidate_score = scoreCandidate(item);
|
|
518
521
|
}
|
|
519
522
|
const harnessSummaries = [...summaries.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
523
|
+
|
|
524
|
+
const ledgerPath = path.join(root, '.tink/maintenance/ledger.jsonl');
|
|
525
|
+
const knownHarnessIds = [...summaries.keys()];
|
|
526
|
+
const maintenanceEvents = parseJsonl(ledgerPath)
|
|
527
|
+
.map((entry) => {
|
|
528
|
+
const refs = [...(Array.isArray(entry.files) ? entry.files : []), ...(Array.isArray(entry.evidence) ? entry.evidence : []), String(entry.op_id || '')];
|
|
529
|
+
const related = knownHarnessIds.filter((id) =>
|
|
530
|
+
refs.some((ref) => String(ref).includes(`${id}.md`) || String(ref).includes(`harness:${id}`) || String(ref).includes(`-${id}-`))
|
|
531
|
+
).sort();
|
|
532
|
+
return {
|
|
533
|
+
timestamp: entry.timestamp || '',
|
|
534
|
+
op_id: entry.op_id || '',
|
|
535
|
+
type: entry.type || 'unknown',
|
|
536
|
+
files: (Array.isArray(entry.files) ? entry.files : []).slice(0, 8),
|
|
537
|
+
result: entry.result || 'unknown',
|
|
538
|
+
approval: entry.approval || '',
|
|
539
|
+
harnesses: related
|
|
540
|
+
};
|
|
541
|
+
})
|
|
542
|
+
.sort((a, b) => String(b.timestamp).localeCompare(String(a.timestamp)))
|
|
543
|
+
.slice(0, 60);
|
|
544
|
+
|
|
520
545
|
return {
|
|
521
546
|
generated_at: new Date().toISOString(),
|
|
522
547
|
run_window: {
|
|
@@ -530,11 +555,13 @@ function summarize(root) {
|
|
|
530
555
|
'.tink/memory/*.md',
|
|
531
556
|
'.tink/runs/*.md',
|
|
532
557
|
'.tink/maintenance/weave-queue.json',
|
|
533
|
-
'.tink/maintenance/friction.jsonl'
|
|
558
|
+
'.tink/maintenance/friction.jsonl',
|
|
559
|
+
'.tink/maintenance/ledger.jsonl'
|
|
534
560
|
],
|
|
535
561
|
harnesses: harnessSummaries,
|
|
536
562
|
graph: buildGraph(harnessSummaries),
|
|
537
|
-
timeline: buildTimeline(runs, root)
|
|
563
|
+
timeline: buildTimeline(runs, root),
|
|
564
|
+
maintenance_events: maintenanceEvents
|
|
538
565
|
};
|
|
539
566
|
}
|
|
540
567
|
|
|
@@ -130,6 +130,22 @@ const COPY = {
|
|
|
130
130
|
viewAll: 'View all',
|
|
131
131
|
confidenceShort: 'Confidence',
|
|
132
132
|
routingHelp: 'When cast routes a task to a visible-thinking overlay harness.',
|
|
133
|
+
lastUsed: 'Last used',
|
|
134
|
+
successes: 'Successes',
|
|
135
|
+
failures: 'Failures',
|
|
136
|
+
contextCost: 'Context cost',
|
|
137
|
+
coUsedWith: 'Often used with',
|
|
138
|
+
safeNextAction: 'Safe next action',
|
|
139
|
+
scoreFactors: 'Score factors',
|
|
140
|
+
viewInGraph: 'View in graph',
|
|
141
|
+
historyEyebrow: 'HISTORY',
|
|
142
|
+
historyTitle: 'Evaluation & maintenance history',
|
|
143
|
+
historyHelp: 'Approved reusable-state changes from the maintenance ledger, newest first.',
|
|
144
|
+
historyEmpty: 'No ledger history yet.',
|
|
145
|
+
sortNote: 'Sorted by usage',
|
|
146
|
+
runWindow: 'Run window',
|
|
147
|
+
totalRuns: 'Runs',
|
|
148
|
+
refCount: 'References',
|
|
133
149
|
groups: [
|
|
134
150
|
['keep', 'Healthy harnesses', 'Ready to keep using'],
|
|
135
151
|
['weave', 'Weave candidates', 'Worth improving next'],
|
|
@@ -174,6 +190,22 @@ COPY.ko = {
|
|
|
174
190
|
viewAll: '전체 보기',
|
|
175
191
|
confidenceShort: '신뢰도',
|
|
176
192
|
routingHelp: 'cast가 생각 보조 overlay 하네스로 라우팅하는 기준입니다.',
|
|
193
|
+
lastUsed: '마지막 사용',
|
|
194
|
+
successes: '성공',
|
|
195
|
+
failures: '실패',
|
|
196
|
+
contextCost: '컨텍스트 비용',
|
|
197
|
+
coUsedWith: '함께 쓰인 하네스',
|
|
198
|
+
safeNextAction: '다음 안전 행동',
|
|
199
|
+
scoreFactors: '점수 요인',
|
|
200
|
+
viewInGraph: '그래프에서 보기',
|
|
201
|
+
historyEyebrow: '히스토리',
|
|
202
|
+
historyTitle: '평가·생성 히스토리',
|
|
203
|
+
historyHelp: '유지보수 장부에 기록된 승인 변경 이력을 최신순으로 보여줍니다.',
|
|
204
|
+
historyEmpty: '아직 장부 기록이 없습니다.',
|
|
205
|
+
sortNote: '사용량 순 정렬',
|
|
206
|
+
runWindow: '기록 기간',
|
|
207
|
+
totalRuns: 'Run 수',
|
|
208
|
+
refCount: '참조 횟수',
|
|
177
209
|
navLabel: '탐색',
|
|
178
210
|
operator: '작업자',
|
|
179
211
|
online: 'Tink 온라인',
|
|
@@ -741,15 +773,26 @@ function dedupeTimelineEvents(events = [], harnessIds = null, limit = 8) {
|
|
|
741
773
|
}
|
|
742
774
|
|
|
743
775
|
function renderTimelineItems(items, copy) {
|
|
744
|
-
return items.map((event) =>
|
|
776
|
+
return items.map((event) => {
|
|
777
|
+
const outcome = timelineOutcomeClass(event);
|
|
778
|
+
const chips = (event.harnesses || []).slice(0, 6);
|
|
779
|
+
return `
|
|
745
780
|
<li>
|
|
746
|
-
<span class="dot ${escapeHtml(
|
|
781
|
+
<span class="dot ${escapeHtml(outcome)}"></span>
|
|
747
782
|
<div>
|
|
748
|
-
<
|
|
749
|
-
|
|
783
|
+
<div class="run-row">
|
|
784
|
+
<span class="run-badge ${escapeHtml(outcome)}">${escapeHtml(timelineOutcomeLabel(event, copy))}</span>
|
|
785
|
+
<time>${escapeHtml(shortDate(event.date))}</time>
|
|
786
|
+
</div>
|
|
787
|
+
<div class="run-chips">
|
|
788
|
+
${chips.length
|
|
789
|
+
? chips.map((id) => `<span class="co-chip">${escapeHtml(id)}</span>`).join('')
|
|
790
|
+
: `<span class="run-empty">${escapeHtml(copy.noHarnessRecorded)}</span>`}
|
|
791
|
+
</div>
|
|
750
792
|
</div>
|
|
751
793
|
</li>
|
|
752
|
-
|
|
794
|
+
`;
|
|
795
|
+
}).join('') || `<li><span class="dot observe"></span><div><strong>${escapeHtml(copy.noRunEvents)}</strong><p>${escapeHtml(copy.runRecordsWillAppear)}</p></div></li>`;
|
|
753
796
|
}
|
|
754
797
|
|
|
755
798
|
function renderTimeline(events = [], copy, harnessIds = null, options = {}) {
|
|
@@ -860,26 +903,81 @@ function renderSelectedPanel(harnesses, copy) {
|
|
|
860
903
|
function renderHarness(item, copy) {
|
|
861
904
|
const signals = item.signals || {};
|
|
862
905
|
const score = Number(item.candidate_score?.total || 0);
|
|
906
|
+
const factors = (item.candidate_score?.factors || []).slice(0, 5);
|
|
907
|
+
const coUsed = (signals.co_used_with || []).slice(0, 5);
|
|
908
|
+
const reason = normalizeReason(item.reason, copy);
|
|
863
909
|
return `
|
|
864
|
-
<article class="harness-card ${recommendationClass(item.recommendation)}" data-harness-id="${escapeAttr(item.id)}" data-recommendation="${escapeAttr(item.recommendation || 'unknown')}"
|
|
865
|
-
<
|
|
866
|
-
<
|
|
867
|
-
|
|
910
|
+
<article class="harness-card ${recommendationClass(item.recommendation)}" data-harness-id="${escapeAttr(item.id)}" data-recommendation="${escapeAttr(item.recommendation || 'unknown')}">
|
|
911
|
+
<button class="harness-summary" type="button" aria-expanded="false">
|
|
912
|
+
<div>
|
|
913
|
+
<p class="eyebrow">${escapeHtml(renderCopyValue(item.recommendation, copy))}</p>
|
|
914
|
+
<h3>${escapeHtml(item.id)}</h3>
|
|
915
|
+
</div>
|
|
916
|
+
<div class="harness-mini">
|
|
917
|
+
<span>${escapeHtml(copy.uses)} ${escapeHtml(signals.uses ?? 0)}</span>
|
|
918
|
+
<strong>${escapeHtml(score)}</strong>
|
|
919
|
+
</div>
|
|
920
|
+
<span class="chevron" aria-hidden="true"></span>
|
|
921
|
+
</button>
|
|
922
|
+
<div class="harness-detail">
|
|
923
|
+
<div class="harness-detail-inner">
|
|
924
|
+
${reason ? `<p class="harness-reason">${escapeHtml(reason)}</p>` : ''}
|
|
925
|
+
<dl>
|
|
926
|
+
<div><dt>${escapeHtml(copy.lifecycleState)}</dt><dd>${escapeHtml(renderCopyValue(item.lifecycle_state, copy))}</dd></div>
|
|
927
|
+
<div><dt>${escapeHtml(copy.lastUsed || 'Last used')}</dt><dd>${escapeHtml(signals.last_used ? shortDate(signals.last_used) : renderCopyValue('', copy))}</dd></div>
|
|
928
|
+
<div><dt>${escapeHtml(copy.successes || 'Successes')}</dt><dd>${escapeHtml(signals.successes ?? 0)}</dd></div>
|
|
929
|
+
<div><dt>${escapeHtml(copy.failures || 'Failures')}</dt><dd>${escapeHtml(signals.failures ?? 0)}</dd></div>
|
|
930
|
+
<div><dt>${escapeHtml(copy.blocked)}</dt><dd>${escapeHtml(signals.blocked ?? 0)}</dd></div>
|
|
931
|
+
<div><dt>${escapeHtml(copy.contextCost || 'Context cost')}</dt><dd>${escapeHtml(renderCopyValue(signals.context_cost, copy))}</dd></div>
|
|
932
|
+
</dl>
|
|
933
|
+
${coUsed.length ? `
|
|
934
|
+
<p class="detail-label">${escapeHtml(copy.coUsedWith || 'Often used with')}</p>
|
|
935
|
+
<div class="co-used-chips">${coUsed.map((related) => `<span class="co-chip">${escapeHtml(related.id)} ×${escapeHtml(related.count)}</span>`).join('')}</div>
|
|
936
|
+
` : ''}
|
|
937
|
+
${factors.length ? `
|
|
938
|
+
<p class="detail-label">${escapeHtml(copy.scoreFactors || 'Score factors')}</p>
|
|
939
|
+
<ul class="factor-list">${factors.map((factor) => `<li><span>${escapeHtml(factor.name)}</span><strong>${escapeHtml(factor.points ?? factor.value ?? '')}</strong></li>`).join('')}</ul>
|
|
940
|
+
` : ''}
|
|
941
|
+
${item.safe_next_action ? `
|
|
942
|
+
<p class="detail-label">${escapeHtml(copy.safeNextAction || 'Safe next action')}</p>
|
|
943
|
+
<p class="harness-next">${escapeHtml(item.safe_next_action)}</p>
|
|
944
|
+
` : ''}
|
|
945
|
+
<p class="detail-label">${escapeHtml(copy.evidenceHandles)} (${escapeHtml(String((item.evidence_handles || []).length))})</p>
|
|
946
|
+
<ul class="evidence-list">${renderEvidence(item.evidence_handles, copy)}</ul>
|
|
947
|
+
<button class="link-button" type="button" data-select-harness="${escapeAttr(item.id)}">${escapeHtml(copy.viewInGraph || 'View in graph')} →</button>
|
|
948
|
+
</div>
|
|
868
949
|
</div>
|
|
869
|
-
<strong>${escapeHtml(score)}</strong>
|
|
870
|
-
<dl>
|
|
871
|
-
<div><dt>${escapeHtml(copy.lifecycleState)}</dt><dd>${escapeHtml(renderCopyValue(item.lifecycle_state, copy))}</dd></div>
|
|
872
|
-
<div><dt>${escapeHtml(copy.uses)}</dt><dd>${escapeHtml(signals.uses ?? 0)}</dd></div>
|
|
873
|
-
<div><dt>${escapeHtml(copy.blocked)}</dt><dd>${escapeHtml(signals.blocked ?? 0)}</dd></div>
|
|
874
|
-
</dl>
|
|
875
|
-
<details>
|
|
876
|
-
<summary>${escapeHtml(copy.evidenceHandles)} (${escapeHtml(String((item.evidence_handles || []).length))})</summary>
|
|
877
|
-
<ul>${renderEvidence(item.evidence_handles, copy)}</ul>
|
|
878
|
-
</details>
|
|
879
950
|
</article>
|
|
880
951
|
`;
|
|
881
952
|
}
|
|
882
953
|
|
|
954
|
+
function renderHistorySection(events = [], copy) {
|
|
955
|
+
const items = Array.isArray(events) ? events.slice(0, 30) : [];
|
|
956
|
+
return `
|
|
957
|
+
<section class="history-section">
|
|
958
|
+
<div class="panel-title">
|
|
959
|
+
<p class="eyebrow">${escapeHtml(copy.historyEyebrow || 'HISTORY')}</p>
|
|
960
|
+
<h2>${escapeHtml(copy.historyTitle || 'Evaluation & maintenance history')}</h2>
|
|
961
|
+
<p>${escapeHtml(copy.historyHelp || '')}</p>
|
|
962
|
+
</div>
|
|
963
|
+
${items.length ? `
|
|
964
|
+
<ol class="history-feed">
|
|
965
|
+
${items.map((event) => `
|
|
966
|
+
<li>
|
|
967
|
+
<span class="history-type ${escapeAttr(String(event.type || 'unknown').replace(/[^a-z0-9_-]/gi, '-'))}">${escapeHtml(event.type || 'unknown')}</span>
|
|
968
|
+
<div>
|
|
969
|
+
<strong>${escapeHtml(shortDate(event.timestamp))} · ${escapeHtml(event.result || '')}</strong>
|
|
970
|
+
${event.harnesses?.length ? `<p class="history-harnesses">${event.harnesses.map((id) => escapeHtml(id)).join(', ')}</p>` : ''}
|
|
971
|
+
${event.files?.length ? `<p class="history-files">${event.files.slice(0, 4).map((file) => `<code>${escapeHtml(normalizePath(file).replace(/^.*[\\/]/, ''))}</code>`).join(' ')}</p>` : ''}
|
|
972
|
+
</div>
|
|
973
|
+
</li>
|
|
974
|
+
`).join('')}
|
|
975
|
+
</ol>
|
|
976
|
+
` : `<p class="empty-note">${escapeHtml(copy.historyEmpty || 'No ledger history yet.')}</p>`}
|
|
977
|
+
</section>
|
|
978
|
+
`;
|
|
979
|
+
}
|
|
980
|
+
|
|
883
981
|
function renderGraphOverview(graph = {}, copy) {
|
|
884
982
|
const stats = graphStats(graph);
|
|
885
983
|
const nodeCounts = new Map(stats.nodeCounts);
|
|
@@ -989,19 +1087,26 @@ function renderHomePage(summary, copy, harnesses, harnessIds) {
|
|
|
989
1087
|
function renderMemoryPage(summary, copy) {
|
|
990
1088
|
const harnesses = getVisibleHarnesses(Array.isArray(summary.harnesses) ? summary.harnesses : []);
|
|
991
1089
|
const refs = new Map();
|
|
1090
|
+
const ensureRef = (key) => {
|
|
1091
|
+
if (!refs.has(key)) refs.set(key, { users: new Set(), count: 0 });
|
|
1092
|
+
return refs.get(key);
|
|
1093
|
+
};
|
|
992
1094
|
for (const harness of harnesses) {
|
|
993
1095
|
for (const ref of harness.signals?.memory_refs || []) {
|
|
994
|
-
|
|
995
|
-
if (!refs.has(key)) refs.set(key, new Set());
|
|
996
|
-
refs.get(key).add(harness.id);
|
|
1096
|
+
ensureRef(normalizePath(ref)).users.add(harness.id);
|
|
997
1097
|
}
|
|
998
1098
|
}
|
|
1099
|
+
for (const edge of getRenderableEdges(summary.graph?.edges || [])) {
|
|
1100
|
+
if (edge.type !== 'uses_memory') continue;
|
|
1101
|
+
const entry = ensureRef(normalizePath(String(edge.target).replace(/^memory:/, '')));
|
|
1102
|
+
entry.users.add(shortLabel(edge.source));
|
|
1103
|
+
entry.count += Number(edge.count || 1);
|
|
1104
|
+
}
|
|
999
1105
|
for (const node of getRenderableNodes(summary.graph?.nodes || [])) {
|
|
1000
1106
|
if (node.type !== 'memory') continue;
|
|
1001
|
-
|
|
1002
|
-
if (!refs.has(key)) refs.set(key, new Set());
|
|
1107
|
+
ensureRef(normalizePath(String(node.id).replace(/^memory:/, '')));
|
|
1003
1108
|
}
|
|
1004
|
-
const entries = [...refs.entries()].sort(([a], [b]) => a.
|
|
1109
|
+
const entries = [...refs.entries()].sort(([, a], [, b]) => b.count - a.count || b.users.size - a.users.size);
|
|
1005
1110
|
return `
|
|
1006
1111
|
<section class="page-head">
|
|
1007
1112
|
<p class="eyebrow">${escapeHtml(copy.memoryEyebrow || 'MEMORY')}</p>
|
|
@@ -1010,15 +1115,21 @@ function renderMemoryPage(summary, copy) {
|
|
|
1010
1115
|
</section>
|
|
1011
1116
|
${entries.length ? `
|
|
1012
1117
|
<div class="memory-grid">
|
|
1013
|
-
${entries.map(([file,
|
|
1118
|
+
${entries.map(([file, info]) => `
|
|
1014
1119
|
<article class="insight-card memory-card">
|
|
1015
1120
|
<h3><code>${escapeHtml(file)}</code></h3>
|
|
1016
1121
|
<dl>
|
|
1017
1122
|
<div>
|
|
1018
|
-
<dt>${escapeHtml(copy.
|
|
1019
|
-
<dd>${
|
|
1123
|
+
<dt>${escapeHtml(copy.refCount || 'References')}</dt>
|
|
1124
|
+
<dd>${escapeHtml(formatNumber(Math.max(info.count, info.users.size)))}</dd>
|
|
1020
1125
|
</div>
|
|
1021
1126
|
</dl>
|
|
1127
|
+
<p class="detail-label">${escapeHtml(copy.referencedBy || 'Referenced by')}</p>
|
|
1128
|
+
<div class="co-used-chips">
|
|
1129
|
+
${[...info.users].length
|
|
1130
|
+
? [...info.users].sort().map((id) => `<span class="co-chip">${escapeHtml(id)}</span>`).join('')
|
|
1131
|
+
: `<span class="run-empty">${escapeHtml(copy.none || 'None')}</span>`}
|
|
1132
|
+
</div>
|
|
1022
1133
|
</article>
|
|
1023
1134
|
`).join('')}
|
|
1024
1135
|
</div>
|
|
@@ -1028,12 +1139,35 @@ function renderMemoryPage(summary, copy) {
|
|
|
1028
1139
|
|
|
1029
1140
|
function renderActivityPage(summary, copy, harnessIds) {
|
|
1030
1141
|
const items = dedupeTimelineEvents(summary.timeline || [], harnessIds, 30);
|
|
1142
|
+
const all = Array.isArray(summary.timeline) ? summary.timeline : [];
|
|
1143
|
+
const counts = { success: 0, blocked: 0, failed: 0, recorded: 0 };
|
|
1144
|
+
for (const event of all) {
|
|
1145
|
+
const key = timelineOutcomeClass(event);
|
|
1146
|
+
counts[key in counts ? key : 'recorded'] += 1;
|
|
1147
|
+
}
|
|
1148
|
+
const window = summary.run_window || {};
|
|
1149
|
+
const windowText = window.from && window.to
|
|
1150
|
+
? `${shortDate(window.from)} ~ ${shortDate(window.to)}`
|
|
1151
|
+
: renderCopyValue('', copy);
|
|
1152
|
+
const summaryCells = [
|
|
1153
|
+
[copy.totalRuns || 'Runs', formatNumber(window.run_count || all.length)],
|
|
1154
|
+
[copy.runWindow || 'Run window', windowText],
|
|
1155
|
+
[copy.timelineCompleted || 'Completed', formatNumber(counts.success)],
|
|
1156
|
+
[copy.timelineBlocked || 'Blocked', formatNumber(counts.blocked)],
|
|
1157
|
+
[copy.timelineFailed || 'Failed', formatNumber(counts.failed)],
|
|
1158
|
+
[copy.timelineRecorded || 'Recorded', formatNumber(counts.recorded)]
|
|
1159
|
+
];
|
|
1031
1160
|
return `
|
|
1032
1161
|
<section class="page-head">
|
|
1033
1162
|
<p class="eyebrow">${escapeHtml(copy.activityEyebrow || 'ACTIVITY')}</p>
|
|
1034
1163
|
<h1>${escapeHtml(copy.activityTitle || 'Run activity')}</h1>
|
|
1035
1164
|
<p>${escapeHtml(copy.activityHelp || '')}</p>
|
|
1036
1165
|
</section>
|
|
1166
|
+
<section class="activity-summary">
|
|
1167
|
+
${summaryCells.map(([label, value]) => `
|
|
1168
|
+
<article><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong></article>
|
|
1169
|
+
`).join('')}
|
|
1170
|
+
</section>
|
|
1037
1171
|
<section class="timeline activity-feed">
|
|
1038
1172
|
<ol>
|
|
1039
1173
|
${renderTimelineItems(items, copy)}
|
|
@@ -1267,13 +1401,11 @@ function renderScript(harnesses, copy) {
|
|
|
1267
1401
|
selectHarness(button.dataset.selectHarness);
|
|
1268
1402
|
});
|
|
1269
1403
|
});
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
selectHarness(card.dataset.harnessId);
|
|
1276
|
-
}
|
|
1404
|
+
document.querySelectorAll('.harness-summary').forEach((button) => {
|
|
1405
|
+
button.addEventListener('click', () => {
|
|
1406
|
+
const card = button.closest('.harness-card');
|
|
1407
|
+
const expanded = card.classList.toggle('is-expanded');
|
|
1408
|
+
button.setAttribute('aria-expanded', expanded ? 'true' : 'false');
|
|
1277
1409
|
});
|
|
1278
1410
|
});
|
|
1279
1411
|
document.querySelectorAll('[data-filter-rec]').forEach((button) => {
|
|
@@ -1999,24 +2131,26 @@ function renderStyles() {
|
|
|
1999
2131
|
|
|
2000
2132
|
.harness-grid {
|
|
2001
2133
|
display: grid;
|
|
2002
|
-
grid-template-columns: repeat(
|
|
2003
|
-
gap: var(--space-
|
|
2134
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
2135
|
+
gap: var(--space-2);
|
|
2136
|
+
align-items: start;
|
|
2004
2137
|
}
|
|
2005
2138
|
|
|
2006
2139
|
.harness-card {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
gap: var(--space-2);
|
|
2140
|
+
transition: opacity 160ms ease, border-color 160ms ease;
|
|
2141
|
+
padding: 0;
|
|
2142
|
+
overflow: hidden;
|
|
2011
2143
|
}
|
|
2012
2144
|
|
|
2013
2145
|
.harness-card:hover,
|
|
2014
|
-
.harness-card
|
|
2015
|
-
.harness-card.is-
|
|
2146
|
+
.harness-card.is-selected,
|
|
2147
|
+
.harness-card.is-expanded {
|
|
2016
2148
|
border-color: var(--border-hover);
|
|
2017
2149
|
outline: none;
|
|
2018
2150
|
}
|
|
2019
2151
|
|
|
2152
|
+
.harness-card.is-expanded { border-color: var(--border-strong); }
|
|
2153
|
+
|
|
2020
2154
|
.harness-card.is-filtered-out { display: none; }
|
|
2021
2155
|
|
|
2022
2156
|
.harness-card.keep { border-top: 2px solid var(--success); }
|
|
@@ -2025,32 +2159,206 @@ function renderStyles() {
|
|
|
2025
2159
|
.harness-card.merge_candidate { border-top: 2px solid var(--accent); }
|
|
2026
2160
|
.harness-card.observe { border-top: 2px solid var(--text-secondary); }
|
|
2027
2161
|
|
|
2028
|
-
.harness-
|
|
2162
|
+
.harness-summary {
|
|
2163
|
+
width: 100%;
|
|
2164
|
+
display: grid;
|
|
2165
|
+
grid-template-columns: 1fr auto 14px;
|
|
2166
|
+
align-items: center;
|
|
2167
|
+
gap: var(--space-2);
|
|
2168
|
+
padding: var(--space-3);
|
|
2169
|
+
border: 0;
|
|
2170
|
+
background: transparent;
|
|
2171
|
+
color: var(--text-primary);
|
|
2172
|
+
font-family: var(--font-ui);
|
|
2173
|
+
text-align: left;
|
|
2174
|
+
cursor: pointer;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
.harness-summary:hover { background: var(--bg-hover); }
|
|
2029
2178
|
|
|
2030
|
-
.harness-
|
|
2179
|
+
.harness-summary .eyebrow { margin: 0 0 2px; font-size: 10px; }
|
|
2180
|
+
|
|
2181
|
+
.harness-summary h3 {
|
|
2031
2182
|
margin: 0;
|
|
2032
|
-
font-size:
|
|
2183
|
+
font-size: 14px;
|
|
2033
2184
|
line-height: 1.25;
|
|
2034
2185
|
font-weight: 600;
|
|
2186
|
+
overflow-wrap: anywhere;
|
|
2035
2187
|
}
|
|
2036
2188
|
|
|
2037
|
-
.harness-
|
|
2038
|
-
|
|
2039
|
-
|
|
2189
|
+
.harness-mini {
|
|
2190
|
+
display: grid;
|
|
2191
|
+
gap: 2px;
|
|
2192
|
+
justify-items: end;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
.harness-mini span {
|
|
2196
|
+
color: var(--text-secondary);
|
|
2197
|
+
font-size: 11px;
|
|
2198
|
+
white-space: nowrap;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
.harness-mini strong {
|
|
2202
|
+
font-size: 18px;
|
|
2040
2203
|
line-height: 1;
|
|
2041
2204
|
font-family: var(--font-mono);
|
|
2042
2205
|
font-weight: 600;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
.chevron {
|
|
2209
|
+
width: 7px;
|
|
2210
|
+
height: 7px;
|
|
2211
|
+
border-right: 1.5px solid var(--text-secondary);
|
|
2212
|
+
border-bottom: 1.5px solid var(--text-secondary);
|
|
2213
|
+
transform: rotate(45deg);
|
|
2214
|
+
transition: transform 240ms ease;
|
|
2215
|
+
justify-self: center;
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
.harness-card.is-expanded .chevron { transform: rotate(225deg); }
|
|
2219
|
+
|
|
2220
|
+
.harness-detail {
|
|
2221
|
+
display: grid;
|
|
2222
|
+
grid-template-rows: 0fr;
|
|
2223
|
+
transition: grid-template-rows 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
.harness-card.is-expanded .harness-detail { grid-template-rows: 1fr; }
|
|
2227
|
+
|
|
2228
|
+
.harness-detail-inner {
|
|
2229
|
+
overflow: hidden;
|
|
2230
|
+
min-height: 0;
|
|
2231
|
+
padding: 0 var(--space-3);
|
|
2232
|
+
display: grid;
|
|
2233
|
+
gap: var(--space-2);
|
|
2234
|
+
opacity: 0;
|
|
2235
|
+
transition: opacity 240ms ease 60ms, padding 320ms ease;
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
.harness-card.is-expanded .harness-detail-inner {
|
|
2239
|
+
opacity: 1;
|
|
2240
|
+
padding: var(--space-1) var(--space-3) var(--space-3);
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
.harness-reason {
|
|
2244
|
+
margin: 0;
|
|
2245
|
+
color: var(--text-secondary);
|
|
2246
|
+
font-size: 12px;
|
|
2247
|
+
line-height: 1.5;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
.detail-label {
|
|
2251
|
+
margin: var(--space-1) 0 0;
|
|
2252
|
+
color: var(--text-secondary);
|
|
2253
|
+
font-size: 10px;
|
|
2254
|
+
text-transform: uppercase;
|
|
2255
|
+
letter-spacing: 0.06em;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
.co-used-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
2259
|
+
|
|
2260
|
+
.co-chip {
|
|
2261
|
+
border: 1px solid var(--border-default);
|
|
2262
|
+
background: var(--bg-hover);
|
|
2263
|
+
border-radius: var(--radius-sm);
|
|
2264
|
+
padding: 2px 6px;
|
|
2265
|
+
font-size: 11px;
|
|
2266
|
+
font-family: var(--font-mono);
|
|
2267
|
+
color: var(--text-secondary);
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
.factor-list {
|
|
2271
|
+
margin: 0;
|
|
2272
|
+
padding: 0;
|
|
2273
|
+
list-style: none;
|
|
2274
|
+
display: grid;
|
|
2275
|
+
gap: 4px;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
.factor-list li {
|
|
2279
|
+
display: flex;
|
|
2280
|
+
justify-content: space-between;
|
|
2281
|
+
gap: var(--space-2);
|
|
2282
|
+
font-size: 12px;
|
|
2283
|
+
color: var(--text-secondary);
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
.factor-list strong { font-family: var(--font-mono); color: var(--text-primary); font-weight: 500; }
|
|
2287
|
+
|
|
2288
|
+
.harness-next {
|
|
2289
|
+
margin: 0;
|
|
2043
2290
|
color: var(--text-primary);
|
|
2044
|
-
|
|
2291
|
+
font-size: 12px;
|
|
2292
|
+
line-height: 1.5;
|
|
2045
2293
|
}
|
|
2046
2294
|
|
|
2047
|
-
.
|
|
2295
|
+
.evidence-list {
|
|
2048
2296
|
margin: 0;
|
|
2297
|
+
padding-left: 16px;
|
|
2049
2298
|
color: var(--text-secondary);
|
|
2050
|
-
font-size:
|
|
2051
|
-
|
|
2299
|
+
font-size: 11px;
|
|
2300
|
+
display: grid;
|
|
2301
|
+
gap: 3px;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
.harness-card .link-button { margin-top: var(--space-1); justify-self: start; }
|
|
2305
|
+
|
|
2306
|
+
.history-section {
|
|
2307
|
+
margin-top: var(--space-6);
|
|
2308
|
+
border-top: 1px solid var(--border-default);
|
|
2309
|
+
padding-top: var(--space-4);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
.history-feed {
|
|
2313
|
+
margin: var(--space-3) 0 0;
|
|
2314
|
+
padding: 0;
|
|
2315
|
+
list-style: none;
|
|
2316
|
+
display: grid;
|
|
2317
|
+
gap: var(--space-2);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
.history-feed li {
|
|
2321
|
+
display: grid;
|
|
2322
|
+
grid-template-columns: 110px 1fr;
|
|
2323
|
+
gap: var(--space-3);
|
|
2324
|
+
align-items: start;
|
|
2325
|
+
border: 1px solid var(--border-default);
|
|
2326
|
+
border-radius: var(--radius-md);
|
|
2327
|
+
background: var(--bg-card);
|
|
2328
|
+
padding: var(--space-2) var(--space-3);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
.history-type {
|
|
2332
|
+
display: inline-block;
|
|
2333
|
+
font-family: var(--font-mono);
|
|
2334
|
+
font-size: 10px;
|
|
2335
|
+
text-transform: uppercase;
|
|
2336
|
+
letter-spacing: 0.05em;
|
|
2337
|
+
color: var(--text-secondary);
|
|
2338
|
+
border: 1px solid var(--border-default);
|
|
2339
|
+
border-radius: var(--radius-sm);
|
|
2340
|
+
padding: 3px 6px;
|
|
2341
|
+
margin-top: 2px;
|
|
2342
|
+
text-align: center;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.history-type.memory { color: var(--accent-text); border-color: var(--accent-dim); }
|
|
2346
|
+
.history-type.weave { color: var(--warning); border-color: var(--warning-dim); }
|
|
2347
|
+
.history-type.frog { color: var(--danger); border-color: var(--danger-dim); }
|
|
2348
|
+
.history-type.harness-create,
|
|
2349
|
+
.history-type.harness-edit { color: var(--success); border-color: var(--success-dim); }
|
|
2350
|
+
|
|
2351
|
+
.history-feed strong { font-size: 12px; font-weight: 500; }
|
|
2352
|
+
|
|
2353
|
+
.history-harnesses {
|
|
2354
|
+
margin: 2px 0 0;
|
|
2355
|
+
color: var(--text-secondary);
|
|
2356
|
+
font-size: 11px;
|
|
2357
|
+
font-family: var(--font-mono);
|
|
2052
2358
|
}
|
|
2053
2359
|
|
|
2360
|
+
.history-files { margin: 4px 0 0; display: flex; flex-wrap: wrap; gap: 4px; }
|
|
2361
|
+
|
|
2054
2362
|
.harness-card dl,
|
|
2055
2363
|
.selected dl,
|
|
2056
2364
|
.graph-overview dl {
|
|
@@ -2348,6 +2656,79 @@ function renderStyles() {
|
|
|
2348
2656
|
.activity-feed { padding: var(--space-4); }
|
|
2349
2657
|
.activity-feed ol { gap: var(--space-3); }
|
|
2350
2658
|
|
|
2659
|
+
.run-row {
|
|
2660
|
+
display: flex;
|
|
2661
|
+
align-items: center;
|
|
2662
|
+
gap: var(--space-2);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.run-row time {
|
|
2666
|
+
color: var(--text-secondary);
|
|
2667
|
+
font-size: 11px;
|
|
2668
|
+
font-family: var(--font-mono);
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
.run-badge {
|
|
2672
|
+
display: inline-block;
|
|
2673
|
+
font-size: 10px;
|
|
2674
|
+
font-weight: 500;
|
|
2675
|
+
text-transform: uppercase;
|
|
2676
|
+
letter-spacing: 0.05em;
|
|
2677
|
+
border: 1px solid var(--border-default);
|
|
2678
|
+
border-radius: var(--radius-sm);
|
|
2679
|
+
padding: 2px 6px;
|
|
2680
|
+
color: var(--text-secondary);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
.run-badge.success { color: var(--success); border-color: var(--success-dim); background: var(--success-dim); }
|
|
2684
|
+
.run-badge.blocked { color: var(--warning); border-color: var(--warning-dim); background: var(--warning-dim); }
|
|
2685
|
+
.run-badge.failed { color: var(--danger); border-color: var(--danger-dim); background: var(--danger-dim); }
|
|
2686
|
+
.run-badge.recorded { background: var(--bg-hover); }
|
|
2687
|
+
|
|
2688
|
+
.run-chips {
|
|
2689
|
+
display: flex;
|
|
2690
|
+
flex-wrap: wrap;
|
|
2691
|
+
gap: 4px;
|
|
2692
|
+
margin-top: 5px;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
.run-empty {
|
|
2696
|
+
color: var(--text-muted);
|
|
2697
|
+
font-size: 11px;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
.activity-summary {
|
|
2701
|
+
display: grid;
|
|
2702
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
2703
|
+
gap: var(--space-2);
|
|
2704
|
+
margin-bottom: var(--space-3);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
.activity-summary article {
|
|
2708
|
+
border: 1px solid var(--border-default);
|
|
2709
|
+
border-radius: var(--radius-lg);
|
|
2710
|
+
background: var(--bg-card);
|
|
2711
|
+
padding: var(--space-3);
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
.activity-summary span {
|
|
2715
|
+
display: block;
|
|
2716
|
+
color: var(--text-secondary);
|
|
2717
|
+
font-size: 11px;
|
|
2718
|
+
letter-spacing: 0.06em;
|
|
2719
|
+
text-transform: uppercase;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
.activity-summary strong {
|
|
2723
|
+
display: block;
|
|
2724
|
+
margin-top: var(--space-1);
|
|
2725
|
+
font-size: 16px;
|
|
2726
|
+
font-family: var(--font-mono);
|
|
2727
|
+
font-weight: 600;
|
|
2728
|
+
letter-spacing: -0.02em;
|
|
2729
|
+
overflow-wrap: anywhere;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2351
2732
|
@media (max-width: 1180px) {
|
|
2352
2733
|
.app-shell { grid-template-columns: 200px minmax(0, 1fr); }
|
|
2353
2734
|
.right-rail {
|
|
@@ -2359,10 +2740,11 @@ function renderStyles() {
|
|
|
2359
2740
|
}
|
|
2360
2741
|
.hero-sidebar { width: 100%; }
|
|
2361
2742
|
.project-strip-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2362
|
-
.harness-grid,
|
|
2743
|
+
.harness-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2363
2744
|
.stats-grid { grid-template-columns: 1fr; }
|
|
2364
2745
|
.home-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
2365
2746
|
.home-columns { grid-template-columns: 1fr; }
|
|
2747
|
+
.activity-summary { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
2366
2748
|
}
|
|
2367
2749
|
|
|
2368
2750
|
@media (max-width: 760px) {
|
|
@@ -2384,6 +2766,8 @@ function renderStyles() {
|
|
|
2384
2766
|
.stats-grid { grid-template-columns: 1fr; }
|
|
2385
2767
|
.home-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2386
2768
|
.memory-grid { grid-template-columns: 1fr; }
|
|
2769
|
+
.activity-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2770
|
+
.harness-grid { grid-template-columns: 1fr; }
|
|
2387
2771
|
.map-head { display: block; }
|
|
2388
2772
|
.map-controls { margin-top: var(--space-2); width: max-content; }
|
|
2389
2773
|
.right-rail { padding: var(--space-4); }
|
|
@@ -2439,13 +2823,19 @@ function renderReport(summary) {
|
|
|
2439
2823
|
<section class="page-head">
|
|
2440
2824
|
<p class="eyebrow">${escapeHtml(copy.harnessesEyebrow || 'HARNESSES')}</p>
|
|
2441
2825
|
<h1>${escapeHtml(copy.harnessCards)}</h1>
|
|
2442
|
-
<p>${escapeHtml(copy.harnessesHelp || '')}</p>
|
|
2826
|
+
<p>${escapeHtml(copy.harnessesHelp || '')} · ${escapeHtml(copy.sortNote || 'Sorted by usage')}</p>
|
|
2443
2827
|
</section>
|
|
2444
2828
|
<section class="harness-section">
|
|
2445
2829
|
<div class="harness-grid">
|
|
2446
|
-
${harnesses
|
|
2830
|
+
${[...harnesses]
|
|
2831
|
+
.sort((a, b) =>
|
|
2832
|
+
Number(b.signals?.uses || 0) - Number(a.signals?.uses || 0) ||
|
|
2833
|
+
Number(b.candidate_score?.total || 0) - Number(a.candidate_score?.total || 0) ||
|
|
2834
|
+
a.id.localeCompare(b.id))
|
|
2835
|
+
.map((item) => renderHarness(item, copy)).join('\n')}
|
|
2447
2836
|
</div>
|
|
2448
2837
|
</section>
|
|
2838
|
+
${renderHistorySection(summary.maintenance_events, copy)}
|
|
2449
2839
|
</section>
|
|
2450
2840
|
<section class="page" data-page="memory">
|
|
2451
2841
|
${renderMemoryPage(summary, copy)}
|