haansi 0.1.48 → 0.1.50
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/dist/haansi.js +997 -13
- package/package.json +1 -1
package/dist/haansi.js
CHANGED
|
@@ -95,7 +95,7 @@ var require_package = __commonJS({
|
|
|
95
95
|
"package.json"(exports2, module2) {
|
|
96
96
|
module2.exports = {
|
|
97
97
|
name: "haansi",
|
|
98
|
-
version: "0.1.
|
|
98
|
+
version: "0.1.50",
|
|
99
99
|
description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
|
|
100
100
|
bin: {
|
|
101
101
|
haansi: "./dist/haansi.js"
|
|
@@ -651,9 +651,10 @@ OpenAI Codex \u2014 configured in ${CODEX_CONFIG}`);
|
|
|
651
651
|
}
|
|
652
652
|
function refreshClaudeConfigTokens(config6, token) {
|
|
653
653
|
const changed = [];
|
|
654
|
+
const projects = config6.projects ?? {};
|
|
654
655
|
const scopes = [
|
|
655
656
|
config6.mcpServers,
|
|
656
|
-
...Object.values(
|
|
657
|
+
...Object.values(projects).map(
|
|
657
658
|
(p) => p?.mcpServers
|
|
658
659
|
)
|
|
659
660
|
];
|
|
@@ -666,7 +667,7 @@ function refreshClaudeConfigTokens(config6, token) {
|
|
|
666
667
|
const auth = entry2?.headers?.Authorization;
|
|
667
668
|
if (typeof auth !== "string" || !HSK_BEARER.test(auth)) continue;
|
|
668
669
|
const next = `Bearer ${token}`;
|
|
669
|
-
if (auth === next) continue;
|
|
670
|
+
if (auth === next || !entry2?.headers) continue;
|
|
670
671
|
entry2.headers.Authorization = next;
|
|
671
672
|
changed.push(name);
|
|
672
673
|
}
|
|
@@ -17784,6 +17785,78 @@ var init_exclusions = __esm({
|
|
|
17784
17785
|
category: "operational",
|
|
17785
17786
|
reason: "Operator annotation on a ledger row (why a CONCURRENTLY migration was applied outside a transaction, that a row was backfill-stamped). Written by us about our own schema changes, not by or about a tenant."
|
|
17786
17787
|
},
|
|
17788
|
+
// ── integrations ledger and the embedding batch queue, classified 2026-10-25 ──
|
|
17789
|
+
// Both reached main unclassified; the ratchet caught them once CI could build.
|
|
17790
|
+
{
|
|
17791
|
+
database: "haansi_integrations_prod",
|
|
17792
|
+
table: "schema_migrations",
|
|
17793
|
+
column: "id",
|
|
17794
|
+
category: "operational",
|
|
17795
|
+
reason: "The ledger key for one migration file (its group and name). Names our own schema change, never tenant data, and is how the ledger refuses to apply a file twice."
|
|
17796
|
+
},
|
|
17797
|
+
{
|
|
17798
|
+
database: "haansi_integrations_prod",
|
|
17799
|
+
table: "schema_migrations",
|
|
17800
|
+
column: "migration_path",
|
|
17801
|
+
category: "operational",
|
|
17802
|
+
reason: "The repository path of the applied migration file. Ours, not a tenant's, and SOC 2 CC8.1 evidence that must stay readable."
|
|
17803
|
+
},
|
|
17804
|
+
{
|
|
17805
|
+
database: "haansi_integrations_prod",
|
|
17806
|
+
table: "schema_migrations",
|
|
17807
|
+
column: "migration_group",
|
|
17808
|
+
category: "operational",
|
|
17809
|
+
reason: "The migrations/<group> directory a file belongs to: a fixed vocabulary of our own schema areas."
|
|
17810
|
+
},
|
|
17811
|
+
{
|
|
17812
|
+
database: "haansi_integrations_prod",
|
|
17813
|
+
table: "schema_migrations",
|
|
17814
|
+
column: "connection_kind",
|
|
17815
|
+
category: "operational",
|
|
17816
|
+
reason: "Which role applied the file, app or admin (a CHECK-constrained vocabulary). Audit evidence about our own deploys."
|
|
17817
|
+
},
|
|
17818
|
+
{
|
|
17819
|
+
database: "haansi_integrations_prod",
|
|
17820
|
+
table: "schema_migrations",
|
|
17821
|
+
column: "checksum_sha256",
|
|
17822
|
+
category: "operational",
|
|
17823
|
+
reason: "The file's SHA-256, compared on every run to catch a migration edited after it was applied. A hash of our own source; encrypting it would break that comparison."
|
|
17824
|
+
},
|
|
17825
|
+
{
|
|
17826
|
+
database: "haansi_integrations_prod",
|
|
17827
|
+
table: "schema_migrations",
|
|
17828
|
+
column: "applied_by",
|
|
17829
|
+
category: "operational",
|
|
17830
|
+
reason: "The database role that applied the migration (DEFAULT current_user), such as haansi_migrator. Names our deploy role, not a person or a tenant, and is audit evidence."
|
|
17831
|
+
},
|
|
17832
|
+
{
|
|
17833
|
+
database: "haansi_intelligence_prod",
|
|
17834
|
+
table: "embedding_queue",
|
|
17835
|
+
column: "target_table",
|
|
17836
|
+
category: "structural",
|
|
17837
|
+
reason: "Which intelligence table a queued row points at (knowledge_artifacts, exchange_posts). A fixed vocabulary of our own table names; the queue holds pointers only and the embed text never rests here."
|
|
17838
|
+
},
|
|
17839
|
+
{
|
|
17840
|
+
database: "haansi_intelligence_prod",
|
|
17841
|
+
table: "embedding_batch_jobs",
|
|
17842
|
+
column: "provider_job_id",
|
|
17843
|
+
category: "operational",
|
|
17844
|
+
reason: "The embedding provider's id for a submitted batch, used to poll and collect it. Issued for our request; no tenant content (the table has no organization_id by design)."
|
|
17845
|
+
},
|
|
17846
|
+
{
|
|
17847
|
+
database: "haansi_intelligence_prod",
|
|
17848
|
+
table: "embedding_batch_jobs",
|
|
17849
|
+
column: "input_file_id",
|
|
17850
|
+
category: "operational",
|
|
17851
|
+
reason: "The provider's id for the uploaded batch input file: an opaque handle. The file's content is never stored here."
|
|
17852
|
+
},
|
|
17853
|
+
{
|
|
17854
|
+
database: "haansi_intelligence_prod",
|
|
17855
|
+
table: "embedding_batch_jobs",
|
|
17856
|
+
column: "model",
|
|
17857
|
+
category: "not-personal",
|
|
17858
|
+
reason: "The embedding model a batch ran on, a model name we choose. Kept readable so a batch's vectors can be matched to the model that produced them."
|
|
17859
|
+
},
|
|
17787
17860
|
{
|
|
17788
17861
|
database: "haansi_integrations_prod",
|
|
17789
17862
|
table: "source_accounts",
|
|
@@ -18001,6 +18074,20 @@ var init_exclusions = __esm({
|
|
|
18001
18074
|
category: "operational",
|
|
18002
18075
|
reason: "A nine-value CHECK-constrained enum (created, commented, reviewed, \u2026) copied from the canonical activity. No personal data."
|
|
18003
18076
|
},
|
|
18077
|
+
{
|
|
18078
|
+
database: "haansi_intelligence_prod",
|
|
18079
|
+
table: "mining_initial_loads",
|
|
18080
|
+
column: "state",
|
|
18081
|
+
category: "operational",
|
|
18082
|
+
reason: "Closed CHECK vocabulary (holding, released) for the initial-load hold (object-graph/135). No content or personal data."
|
|
18083
|
+
},
|
|
18084
|
+
{
|
|
18085
|
+
database: "haansi_intelligence_prod",
|
|
18086
|
+
table: "mining_initial_loads",
|
|
18087
|
+
column: "reason",
|
|
18088
|
+
category: "operational",
|
|
18089
|
+
reason: "Closed CHECK vocabulary for why the initial-load hold is held or released (initial_sync_pending, sessions_loading, ready, \u2026). No content or personal data."
|
|
18090
|
+
},
|
|
18004
18091
|
{
|
|
18005
18092
|
database: "haansi_intelligence_prod",
|
|
18006
18093
|
table: "organization_mining_config",
|
|
@@ -18879,7 +18966,414 @@ var init_exclusions = __esm({
|
|
|
18879
18966
|
column: "event",
|
|
18880
18967
|
category: "operational",
|
|
18881
18968
|
reason: "Closed CHECK vocabulary (loaded, resource_loaded, synced, applied, helped, not_helped)."
|
|
18882
|
-
}
|
|
18969
|
+
},
|
|
18970
|
+
...exchangeExclusions(INTEL, "change_outcomes", {
|
|
18971
|
+
repository: [
|
|
18972
|
+
"not-personal",
|
|
18973
|
+
"An `owner/repo` name, the key the ledger groups and joins on; plaintext in objects.attributes.repository for the same reason."
|
|
18974
|
+
],
|
|
18975
|
+
head_sha: [
|
|
18976
|
+
"join-key",
|
|
18977
|
+
"A commit hash, matched by equality against CI runs and commits."
|
|
18978
|
+
],
|
|
18979
|
+
merge_commit_sha: [
|
|
18980
|
+
"join-key",
|
|
18981
|
+
"A commit hash, matched by equality against reverts and commits."
|
|
18982
|
+
],
|
|
18983
|
+
head_ref: [
|
|
18984
|
+
"not-personal",
|
|
18985
|
+
"The PR's head branch name; plaintext in objects.attributes.branch_name and in coding_session_change_links.branch."
|
|
18986
|
+
],
|
|
18987
|
+
ci_status: [
|
|
18988
|
+
"operational",
|
|
18989
|
+
"Closed CHECK vocabulary (passed, failed, pending, none)."
|
|
18990
|
+
],
|
|
18991
|
+
main_ci_status: [
|
|
18992
|
+
"operational",
|
|
18993
|
+
"Closed CHECK vocabulary (passed, failed, pending, none)."
|
|
18994
|
+
],
|
|
18995
|
+
reverted_by: [
|
|
18996
|
+
"join-key",
|
|
18997
|
+
"A canonical object id (github:<repo>#<n> or github:commit:<sha>), never text."
|
|
18998
|
+
],
|
|
18999
|
+
fix_forward_by: [
|
|
19000
|
+
"join-key",
|
|
19001
|
+
"A canonical object id (github:<repo>#<n>), never text."
|
|
19002
|
+
],
|
|
19003
|
+
survival_14d: [
|
|
19004
|
+
"operational",
|
|
19005
|
+
"Closed CHECK vocabulary (held, failed, pending)."
|
|
19006
|
+
],
|
|
19007
|
+
survival_30d: [
|
|
19008
|
+
"operational",
|
|
19009
|
+
"Closed CHECK vocabulary (held, failed, pending)."
|
|
19010
|
+
],
|
|
19011
|
+
agent_tool: [
|
|
19012
|
+
"not-personal",
|
|
19013
|
+
"Names a coding agent product from a fixed list in the attribution code."
|
|
19014
|
+
],
|
|
19015
|
+
agent_model: [
|
|
19016
|
+
"not-personal",
|
|
19017
|
+
"Names a model, read from an agent's commit trailer."
|
|
19018
|
+
],
|
|
19019
|
+
attribution_method: [
|
|
19020
|
+
"operational",
|
|
19021
|
+
"Closed CHECK vocabulary (session, trailer, pr_metadata)."
|
|
19022
|
+
]
|
|
19023
|
+
}),
|
|
19024
|
+
...exchangeExclusions(INTEL, "workstream_attempts", {
|
|
19025
|
+
process_id: [
|
|
19026
|
+
"join-key",
|
|
19027
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19028
|
+
],
|
|
19029
|
+
repository: [
|
|
19030
|
+
"not-personal",
|
|
19031
|
+
"An `owner/repo` name; plaintext in change_outcomes.repository."
|
|
19032
|
+
],
|
|
19033
|
+
result: [
|
|
19034
|
+
"operational",
|
|
19035
|
+
"Closed CHECK vocabulary (failed, held, open, superseded)."
|
|
19036
|
+
],
|
|
19037
|
+
failure_signals: [
|
|
19038
|
+
"operational",
|
|
19039
|
+
"Closed CHECK vocabulary (ci_failed, main_ci_failed, reverted, closed_unmerged)."
|
|
19040
|
+
]
|
|
19041
|
+
}),
|
|
19042
|
+
...exchangeExclusions(INTEL, "problem_families", {
|
|
19043
|
+
status: [
|
|
19044
|
+
"operational",
|
|
19045
|
+
"Closed CHECK vocabulary (active, dissolved, merged)."
|
|
19046
|
+
],
|
|
19047
|
+
tier: ["operational", "Closed CHECK vocabulary (recurring, playbook)."],
|
|
19048
|
+
seed_process_id: [
|
|
19049
|
+
"join-key",
|
|
19050
|
+
"The seed workstream's process_registry.process_id, joined by equality."
|
|
19051
|
+
]
|
|
19052
|
+
}),
|
|
19053
|
+
...exchangeExclusions(INTEL, "problem_family_feedback", {
|
|
19054
|
+
action: [
|
|
19055
|
+
"operational",
|
|
19056
|
+
"Closed CHECK vocabulary (rename, merge, split, remove_member)."
|
|
19057
|
+
],
|
|
19058
|
+
process_ids: [
|
|
19059
|
+
"join-key",
|
|
19060
|
+
"Workstream process ids a correction moved, joined by equality."
|
|
19061
|
+
]
|
|
19062
|
+
}),
|
|
19063
|
+
...exchangeExclusions(INTEL, "family_matches", {
|
|
19064
|
+
source: [
|
|
19065
|
+
"operational",
|
|
19066
|
+
"Closed CHECK vocabulary (task, ci_failure, alert)."
|
|
19067
|
+
],
|
|
19068
|
+
source_ref: [
|
|
19069
|
+
"join-key",
|
|
19070
|
+
"owner/repo#n of a failing PR or an incident id; never task text."
|
|
19071
|
+
],
|
|
19072
|
+
signals: [
|
|
19073
|
+
"operational",
|
|
19074
|
+
"Three numeric match signals (text, area, signature); no content."
|
|
19075
|
+
],
|
|
19076
|
+
arm: ["operational", "Closed CHECK vocabulary (served, control)."],
|
|
19077
|
+
session_id: [
|
|
19078
|
+
"join-key",
|
|
19079
|
+
"The coding session's id, joined by equality to coding_session_solutions."
|
|
19080
|
+
]
|
|
19081
|
+
}),
|
|
19082
|
+
...exchangeExclusions(INTEL, "coding_session_link_rejections", {
|
|
19083
|
+
session_id: [
|
|
19084
|
+
"join-key",
|
|
19085
|
+
"The coding session's id, joined by equality to its change links."
|
|
19086
|
+
],
|
|
19087
|
+
repository: [
|
|
19088
|
+
"not-personal",
|
|
19089
|
+
"An owner/repo name, matched to change links by equality; plaintext in coding_session_change_links.repository."
|
|
19090
|
+
]
|
|
19091
|
+
}),
|
|
19092
|
+
...exchangeExclusions(INTEL, "playbook_rollouts", {
|
|
19093
|
+
status: [
|
|
19094
|
+
"operational",
|
|
19095
|
+
"Closed CHECK vocabulary (running, promoted, rolled_back)."
|
|
19096
|
+
]
|
|
19097
|
+
}),
|
|
19098
|
+
...exchangeExclusions(INTEL, "workstream_decisions", {
|
|
19099
|
+
process_id: [
|
|
19100
|
+
"join-key",
|
|
19101
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19102
|
+
],
|
|
19103
|
+
trigger: [
|
|
19104
|
+
"operational",
|
|
19105
|
+
"Closed CHECK vocabulary (concluded, concluding_event)."
|
|
19106
|
+
],
|
|
19107
|
+
status: [
|
|
19108
|
+
"operational",
|
|
19109
|
+
"Closed CHECK vocabulary (draft, confirmed, dismissed)."
|
|
19110
|
+
],
|
|
19111
|
+
outcome: [
|
|
19112
|
+
"operational",
|
|
19113
|
+
"Closed CHECK vocabulary (held, reversed, revisited)."
|
|
19114
|
+
],
|
|
19115
|
+
change_keys: [
|
|
19116
|
+
"join-key",
|
|
19117
|
+
"owner/repo#n of the workstream's PRs, as in workstream_attempts."
|
|
19118
|
+
],
|
|
19119
|
+
model: ["operational", "The model name that drafted it."],
|
|
19120
|
+
prompt_tag: ["operational", "The prompt version that drafted it."]
|
|
19121
|
+
}),
|
|
19122
|
+
...exchangeExclusions(INTEL, "workstream_decision_checks", {
|
|
19123
|
+
process_id: [
|
|
19124
|
+
"join-key",
|
|
19125
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19126
|
+
],
|
|
19127
|
+
trigger: [
|
|
19128
|
+
"operational",
|
|
19129
|
+
"Closed CHECK vocabulary (concluded, concluding_event)."
|
|
19130
|
+
],
|
|
19131
|
+
result: ["operational", "Closed CHECK vocabulary (none, invalid)."]
|
|
19132
|
+
}),
|
|
19133
|
+
...exchangeExclusions(INTEL, "coding_session_haansi_usage", {
|
|
19134
|
+
session_id: [
|
|
19135
|
+
"join-key",
|
|
19136
|
+
"The coding session's id, as in coding_session_setups, joined by equality."
|
|
19137
|
+
],
|
|
19138
|
+
inputs: [
|
|
19139
|
+
"not-personal",
|
|
19140
|
+
"Counts of Haansi MCP calls per input type, and Haansi's own tool names."
|
|
19141
|
+
]
|
|
19142
|
+
}),
|
|
19143
|
+
...exchangeExclusions(INTEL, "readiness_snapshots", {
|
|
19144
|
+
baseline: [
|
|
19145
|
+
"not-personal",
|
|
19146
|
+
"Counts by month, top-level code area (repo paths) and workstream category."
|
|
19147
|
+
],
|
|
19148
|
+
failure_signatures: [
|
|
19149
|
+
"not-personal",
|
|
19150
|
+
"Counts by Actions job and step name, from CI config."
|
|
19151
|
+
]
|
|
19152
|
+
}),
|
|
19153
|
+
...exchangeExclusions(INTEL, "replay_results", {
|
|
19154
|
+
process_id: [
|
|
19155
|
+
"join-key",
|
|
19156
|
+
"The replayed workstream's process_registry.process_id, joined by equality."
|
|
19157
|
+
],
|
|
19158
|
+
arm: ["operational", "Closed CHECK vocabulary (baseline, haansi)."],
|
|
19159
|
+
agent: ["operational", "The agent CLI's name, e.g. claude-code."],
|
|
19160
|
+
model: ["operational", "The model name the replay ran with."],
|
|
19161
|
+
run_ref: ["not-personal", "A link to the customer's CI run."]
|
|
19162
|
+
}),
|
|
19163
|
+
...exchangeExclusions(INTEL, "replay_tasks", {
|
|
19164
|
+
process_id: [
|
|
19165
|
+
"join-key",
|
|
19166
|
+
"The member workstream's process_registry.process_id, joined by equality."
|
|
19167
|
+
],
|
|
19168
|
+
repository: [
|
|
19169
|
+
"join-key",
|
|
19170
|
+
"owner/repo of the workstream's PRs, as in workstream_attempts."
|
|
19171
|
+
],
|
|
19172
|
+
base_sha: ["not-personal", "A commit SHA."],
|
|
19173
|
+
target_sha: ["not-personal", "A commit SHA."],
|
|
19174
|
+
checks: [
|
|
19175
|
+
"not-personal",
|
|
19176
|
+
"Actions workflow names from CI config that passed on the fix."
|
|
19177
|
+
]
|
|
19178
|
+
}),
|
|
19179
|
+
...exchangeExclusions(INTEL, "problem_family_members", {
|
|
19180
|
+
process_id: [
|
|
19181
|
+
"join-key",
|
|
19182
|
+
"A member workstream's process_registry.process_id, joined by equality."
|
|
19183
|
+
],
|
|
19184
|
+
role: ["operational", "Closed CHECK vocabulary (smooth, rough, other)."],
|
|
19185
|
+
areas: [
|
|
19186
|
+
"not-personal",
|
|
19187
|
+
"Repository directories the member's held PRs changed; the same class as changed paths' directories shown on the Families view."
|
|
19188
|
+
],
|
|
19189
|
+
signatures: [
|
|
19190
|
+
"not-personal",
|
|
19191
|
+
'CI job and step names ("job \u203A step"), normalized; no log text.'
|
|
19192
|
+
]
|
|
19193
|
+
}),
|
|
19194
|
+
...exchangeExclusions(INTEL, "workstream_root_causes", {
|
|
19195
|
+
process_id: [
|
|
19196
|
+
"join-key",
|
|
19197
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19198
|
+
],
|
|
19199
|
+
confidence: ["operational", "Closed CHECK vocabulary (high, medium, low)."],
|
|
19200
|
+
input_hash: ["not-personal", "A SHA-256 of the inputs; no content."],
|
|
19201
|
+
input_keys: [
|
|
19202
|
+
"not-personal",
|
|
19203
|
+
"owner/repo#n keys of the PRs the statement was written from."
|
|
19204
|
+
],
|
|
19205
|
+
model: ["not-personal", "The model name that wrote the statement."],
|
|
19206
|
+
prompt_tag: ["operational", "The prompt version tag."]
|
|
19207
|
+
}),
|
|
19208
|
+
...exchangeExclusions(INTEL, "problem_family_findings", {
|
|
19209
|
+
findings: [
|
|
19210
|
+
"operational",
|
|
19211
|
+
"Setup factor labels (action steps, skill names, agent, model, effort, instruction-file path and hash prefix) and pair counts, copied from coding_session_setups, which is itself plaintext; read only by viewers who can see every author in author_ids."
|
|
19212
|
+
],
|
|
19213
|
+
author_ids: [
|
|
19214
|
+
"join-key",
|
|
19215
|
+
"organization_users ids whose sessions the findings drew on, compared with the viewer's visible authors."
|
|
19216
|
+
]
|
|
19217
|
+
}),
|
|
19218
|
+
...exchangeExclusions(INTEL, "pull_request_checks", {
|
|
19219
|
+
repository: [
|
|
19220
|
+
"join-key",
|
|
19221
|
+
"owner/repo of the PR, joined by equality to workstream_attempts.repository."
|
|
19222
|
+
],
|
|
19223
|
+
checks: [
|
|
19224
|
+
"not-personal",
|
|
19225
|
+
"Actions workflow names from the repository's CI config, with closed status/conclusion values and run counts; no logs or output."
|
|
19226
|
+
],
|
|
19227
|
+
failed: [
|
|
19228
|
+
"not-personal",
|
|
19229
|
+
'Normalized "job \u203A step" names from CI config, the same signatures problem families compare; no logs or output.'
|
|
19230
|
+
]
|
|
19231
|
+
}),
|
|
19232
|
+
...exchangeExclusions(INTEL, "attempt_rule_configs", {
|
|
19233
|
+
config: [
|
|
19234
|
+
"operational",
|
|
19235
|
+
"Numeric rule settings per workstream category; no content."
|
|
19236
|
+
],
|
|
19237
|
+
created_by: [
|
|
19238
|
+
"operational",
|
|
19239
|
+
"Closed CHECK vocabulary (tuning, platform_admin, defaults)."
|
|
19240
|
+
],
|
|
19241
|
+
reason: [
|
|
19242
|
+
"operational",
|
|
19243
|
+
"Generated text naming the settings a version changed and the label counts behind it."
|
|
19244
|
+
]
|
|
19245
|
+
}),
|
|
19246
|
+
...exchangeExclusions(INTEL, "attempt_rule_evaluations", {
|
|
19247
|
+
config: [
|
|
19248
|
+
"operational",
|
|
19249
|
+
"The numeric rule settings the run scored; no content."
|
|
19250
|
+
],
|
|
19251
|
+
metrics: ["operational", "Per-category label and hit counts; no content."],
|
|
19252
|
+
decision: [
|
|
19253
|
+
"operational",
|
|
19254
|
+
"Closed CHECK vocabulary (kept, stepped, too_few_labels)."
|
|
19255
|
+
],
|
|
19256
|
+
changes: ["operational", "The settings a run stepped, as numbers."]
|
|
19257
|
+
}),
|
|
19258
|
+
...exchangeExclusions(INTEL, "coding_session_solutions", {
|
|
19259
|
+
segment_branch: [
|
|
19260
|
+
"not-personal",
|
|
19261
|
+
"A git branch name, matched to commit links' branch by equality; plaintext like coding_session_change_links.branch."
|
|
19262
|
+
],
|
|
19263
|
+
outcome: [
|
|
19264
|
+
"operational",
|
|
19265
|
+
"Closed CHECK vocabulary (held, failed) stamped by the outcome ledger from the solution's linked PRs."
|
|
19266
|
+
]
|
|
19267
|
+
}),
|
|
19268
|
+
...exchangeExclusions(INTEL, "coding_session_setups", {
|
|
19269
|
+
repository: [
|
|
19270
|
+
"join-key",
|
|
19271
|
+
"owner/repo from the session's git remote, joined to PRs by equality."
|
|
19272
|
+
],
|
|
19273
|
+
branch: [
|
|
19274
|
+
"join-key",
|
|
19275
|
+
"The git branch the session uploaded on, joined to a PR's head branch; plaintext like coding_session_change_links.branch."
|
|
19276
|
+
],
|
|
19277
|
+
session_id: [
|
|
19278
|
+
"join-key",
|
|
19279
|
+
"The coding session's id, joined by equality to coding_session_solutions."
|
|
19280
|
+
],
|
|
19281
|
+
agent_tool: [
|
|
19282
|
+
"operational",
|
|
19283
|
+
"Names a coding agent product (claude-code, codex) from the parser."
|
|
19284
|
+
],
|
|
19285
|
+
models: ["not-personal", "Model names the agent's transcript recorded."],
|
|
19286
|
+
effort: ["operational", "A reasoning-effort level such as high or medium."],
|
|
19287
|
+
git_commit: [
|
|
19288
|
+
"not-personal",
|
|
19289
|
+
"A commit SHA; plaintext in change_outcomes and coding_session_change_links."
|
|
19290
|
+
],
|
|
19291
|
+
instructions: [
|
|
19292
|
+
"not-personal",
|
|
19293
|
+
"Instruction-file paths with the home directory stripped, a closed kind and a content hash; no file content."
|
|
19294
|
+
],
|
|
19295
|
+
skills: [
|
|
19296
|
+
"not-personal",
|
|
19297
|
+
"Skill names the agent loaded and Haansi skill/revision ids; no skill content."
|
|
19298
|
+
],
|
|
19299
|
+
context: [
|
|
19300
|
+
"not-personal",
|
|
19301
|
+
"Counts of reads, searches, web fetches and subagents, and MCP tool names; no paths, queries or content."
|
|
19302
|
+
],
|
|
19303
|
+
prompt_turn_uuid: [
|
|
19304
|
+
"join-key",
|
|
19305
|
+
"A transcript message uuid: the prompt by reference, never its text."
|
|
19306
|
+
],
|
|
19307
|
+
prompt_fingerprint: [
|
|
19308
|
+
"not-personal",
|
|
19309
|
+
"A 64-bit SimHash of the prompt's words; the text cannot be recovered from it."
|
|
19310
|
+
],
|
|
19311
|
+
actions: ["operational", "Closed CHECK vocabulary of action step types."]
|
|
19312
|
+
}),
|
|
19313
|
+
...exchangeExclusions(INTEL, "workstream_attempt_labels", {
|
|
19314
|
+
process_id: [
|
|
19315
|
+
"join-key",
|
|
19316
|
+
"The workstream's process_registry.process_id, joined by equality."
|
|
19317
|
+
],
|
|
19318
|
+
repository: [
|
|
19319
|
+
"not-personal",
|
|
19320
|
+
"An `owner/repo` name; plaintext in workstream_attempts.repository."
|
|
19321
|
+
],
|
|
19322
|
+
label: [
|
|
19323
|
+
"operational",
|
|
19324
|
+
"Closed CHECK vocabulary (right_fix, wrong_fix, fixed_by, not_failure, not_replaced, cleared)."
|
|
19325
|
+
],
|
|
19326
|
+
rule_result: [
|
|
19327
|
+
"operational",
|
|
19328
|
+
"The rule's result when labeled: workstream_attempts.result vocabulary."
|
|
19329
|
+
]
|
|
19330
|
+
}),
|
|
19331
|
+
...exchangeExclusions(INTEL, "coding_session_lineage", {
|
|
19332
|
+
session_id: [
|
|
19333
|
+
"join-key",
|
|
19334
|
+
"The coding session's id, joined by equality to coding_session_solutions."
|
|
19335
|
+
],
|
|
19336
|
+
root_turn_uuid: [
|
|
19337
|
+
"join-key",
|
|
19338
|
+
"A transcript message uuid, matched by equality to find resumed sessions."
|
|
19339
|
+
],
|
|
19340
|
+
last_turn_uuid: [
|
|
19341
|
+
"join-key",
|
|
19342
|
+
"A transcript message uuid, looked up in a resumed session's messages."
|
|
19343
|
+
],
|
|
19344
|
+
replaced_by_session_id: [
|
|
19345
|
+
"join-key",
|
|
19346
|
+
"The replacing coding session's id, joined by equality when superseding."
|
|
19347
|
+
]
|
|
19348
|
+
}),
|
|
19349
|
+
...exchangeExclusions(INTEL, "coding_session_change_scans", {
|
|
19350
|
+
session_id: [
|
|
19351
|
+
"join-key",
|
|
19352
|
+
"The coding session's id, joined by equality to coding_session_solutions."
|
|
19353
|
+
]
|
|
19354
|
+
}),
|
|
19355
|
+
...exchangeExclusions(INTEL, "coding_session_change_links", {
|
|
19356
|
+
session_id: [
|
|
19357
|
+
"join-key",
|
|
19358
|
+
"The coding session's id, joined by equality to coding_session_solutions."
|
|
19359
|
+
],
|
|
19360
|
+
kind: [
|
|
19361
|
+
"operational",
|
|
19362
|
+
"Closed CHECK vocabulary (commit, pr_created, pr_merged)."
|
|
19363
|
+
],
|
|
19364
|
+
commit_sha: [
|
|
19365
|
+
"join-key",
|
|
19366
|
+
"A commit hash, matched by prefix against PR commits."
|
|
19367
|
+
],
|
|
19368
|
+
branch: [
|
|
19369
|
+
"not-personal",
|
|
19370
|
+
"A git branch name, matched against PR head branches; plaintext in objects.attributes.branch_name for the same reason."
|
|
19371
|
+
],
|
|
19372
|
+
repository: [
|
|
19373
|
+
"not-personal",
|
|
19374
|
+
"An `owner/repo` name, the key links are resolved within; plaintext in change_outcomes.repository."
|
|
19375
|
+
]
|
|
19376
|
+
})
|
|
18883
19377
|
];
|
|
18884
19378
|
}
|
|
18885
19379
|
});
|
|
@@ -25867,7 +26361,132 @@ var init_table_registry = __esm({
|
|
|
25867
26361
|
"artifact_cluster_workstreams",
|
|
25868
26362
|
"direct",
|
|
25869
26363
|
{ scopingColumn: "organization_id" }
|
|
25870
|
-
)
|
|
26364
|
+
),
|
|
26365
|
+
// Outcome ledger v0: derived per merged PR from the org's source_events by
|
|
26366
|
+
// the service-pipeline outcome-ledger tick, on the org's userdata pool.
|
|
26367
|
+
entry("intelligence", INTELLIGENCE_DB, "change_outcomes", "direct", {
|
|
26368
|
+
scopingColumn: "organization_id"
|
|
26369
|
+
}),
|
|
26370
|
+
// Attempt chains: each mined workstream's PRs, failures and fix-forwards.
|
|
26371
|
+
entry("intelligence", INTELLIGENCE_DB, "workstream_attempts", "direct", {
|
|
26372
|
+
scopingColumn: "organization_id"
|
|
26373
|
+
}),
|
|
26374
|
+
// Problem families: resolved workstreams grouped by problem, and runs.
|
|
26375
|
+
entry("intelligence", INTELLIGENCE_DB, "problem_families", "direct", {
|
|
26376
|
+
scopingColumn: "organization_id"
|
|
26377
|
+
}),
|
|
26378
|
+
entry("intelligence", INTELLIGENCE_DB, "problem_family_members", "direct", {
|
|
26379
|
+
scopingColumn: "organization_id"
|
|
26380
|
+
}),
|
|
26381
|
+
entry("intelligence", INTELLIGENCE_DB, "problem_family_runs", "direct", {
|
|
26382
|
+
scopingColumn: "organization_id"
|
|
26383
|
+
}),
|
|
26384
|
+
// Matches of new work to a family (playbooks plan §4.8).
|
|
26385
|
+
entry("intelligence", INTELLIGENCE_DB, "family_matches", "direct", {
|
|
26386
|
+
scopingColumn: "organization_id"
|
|
26387
|
+
}),
|
|
26388
|
+
// Window links a session's author rejected (sessions plan §3.1).
|
|
26389
|
+
entry(
|
|
26390
|
+
"intelligence",
|
|
26391
|
+
INTELLIGENCE_DB,
|
|
26392
|
+
"coding_session_link_rejections",
|
|
26393
|
+
"direct",
|
|
26394
|
+
{
|
|
26395
|
+
scopingColumn: "organization_id"
|
|
26396
|
+
}
|
|
26397
|
+
),
|
|
26398
|
+
// Rollouts of a family's playbook revision (playbooks plan phase 6).
|
|
26399
|
+
entry("intelligence", INTELLIGENCE_DB, "playbook_rollouts", "direct", {
|
|
26400
|
+
scopingColumn: "organization_id"
|
|
26401
|
+
}),
|
|
26402
|
+
// Decisions drafted when a workstream concludes (blueprint, Decisions).
|
|
26403
|
+
entry("intelligence", INTELLIGENCE_DB, "workstream_decisions", "direct", {
|
|
26404
|
+
scopingColumn: "organization_id"
|
|
26405
|
+
}),
|
|
26406
|
+
// Workstreams the ledger found to have no decision (never shown).
|
|
26407
|
+
entry(
|
|
26408
|
+
"intelligence",
|
|
26409
|
+
INTELLIGENCE_DB,
|
|
26410
|
+
"workstream_decision_checks",
|
|
26411
|
+
"direct",
|
|
26412
|
+
{
|
|
26413
|
+
scopingColumn: "organization_id"
|
|
26414
|
+
}
|
|
26415
|
+
),
|
|
26416
|
+
// What Haansi gave each coding session (readiness restructure plan §4.6).
|
|
26417
|
+
entry(
|
|
26418
|
+
"intelligence",
|
|
26419
|
+
INTELLIGENCE_DB,
|
|
26420
|
+
"coding_session_haansi_usage",
|
|
26421
|
+
"direct",
|
|
26422
|
+
{
|
|
26423
|
+
scopingColumn: "organization_id"
|
|
26424
|
+
}
|
|
26425
|
+
),
|
|
26426
|
+
// The readiness report's baseline (readiness restructure plan §4.1).
|
|
26427
|
+
entry("intelligence", INTELLIGENCE_DB, "readiness_snapshots", "direct", {
|
|
26428
|
+
scopingColumn: "organization_id"
|
|
26429
|
+
}),
|
|
26430
|
+
// Replay results reported from the customer's CI (readiness plan §4.6).
|
|
26431
|
+
entry("intelligence", INTELLIGENCE_DB, "replay_results", "direct", {
|
|
26432
|
+
scopingColumn: "organization_id"
|
|
26433
|
+
}),
|
|
26434
|
+
// Replay tasks cut from family members (playbooks plan §4.7).
|
|
26435
|
+
entry("intelligence", INTELLIGENCE_DB, "replay_tasks", "direct", {
|
|
26436
|
+
scopingColumn: "organization_id"
|
|
26437
|
+
}),
|
|
26438
|
+
entry("intelligence", INTELLIGENCE_DB, "problem_family_feedback", "direct", {
|
|
26439
|
+
scopingColumn: "organization_id"
|
|
26440
|
+
}),
|
|
26441
|
+
entry("intelligence", INTELLIGENCE_DB, "workstream_root_causes", "direct", {
|
|
26442
|
+
scopingColumn: "organization_id"
|
|
26443
|
+
}),
|
|
26444
|
+
entry("intelligence", INTELLIGENCE_DB, "problem_family_findings", "direct", {
|
|
26445
|
+
scopingColumn: "organization_id"
|
|
26446
|
+
}),
|
|
26447
|
+
// The checks that ran on each PR's head commit.
|
|
26448
|
+
entry("intelligence", INTELLIGENCE_DB, "pull_request_checks", "direct", {
|
|
26449
|
+
scopingColumn: "organization_id"
|
|
26450
|
+
}),
|
|
26451
|
+
// The attempt rule's per-org settings (versioned) and its scoring runs.
|
|
26452
|
+
entry("intelligence", INTELLIGENCE_DB, "attempt_rule_configs", "direct", {
|
|
26453
|
+
scopingColumn: "organization_id"
|
|
26454
|
+
}),
|
|
26455
|
+
entry("intelligence", INTELLIGENCE_DB, "attempt_rule_evaluations", "direct", {
|
|
26456
|
+
scopingColumn: "organization_id"
|
|
26457
|
+
}),
|
|
26458
|
+
// What each coding session ran with (agent, instructions, skills, actions).
|
|
26459
|
+
entry("intelligence", INTELLIGENCE_DB, "coding_session_setups", "direct", {
|
|
26460
|
+
scopingColumn: "organization_id"
|
|
26461
|
+
}),
|
|
26462
|
+
// People's labels on attempt chains; override the rule, kept for evaluation.
|
|
26463
|
+
entry(
|
|
26464
|
+
"intelligence",
|
|
26465
|
+
INTELLIGENCE_DB,
|
|
26466
|
+
"workstream_attempt_labels",
|
|
26467
|
+
"direct",
|
|
26468
|
+
{ scopingColumn: "organization_id" }
|
|
26469
|
+
),
|
|
26470
|
+
// Commits and PRs named in a coding session's transcript, recorded at mining
|
|
26471
|
+
// time beside the session's solutions.
|
|
26472
|
+
entry(
|
|
26473
|
+
"intelligence",
|
|
26474
|
+
INTELLIGENCE_DB,
|
|
26475
|
+
"coding_session_change_links",
|
|
26476
|
+
"direct",
|
|
26477
|
+
{ scopingColumn: "organization_id" }
|
|
26478
|
+
),
|
|
26479
|
+
entry(
|
|
26480
|
+
"intelligence",
|
|
26481
|
+
INTELLIGENCE_DB,
|
|
26482
|
+
"coding_session_change_scans",
|
|
26483
|
+
"direct",
|
|
26484
|
+
{ scopingColumn: "organization_id" }
|
|
26485
|
+
),
|
|
26486
|
+
// Resumed-session lineage: which session file replaced which.
|
|
26487
|
+
entry("intelligence", INTELLIGENCE_DB, "coding_session_lineage", "direct", {
|
|
26488
|
+
scopingColumn: "organization_id"
|
|
26489
|
+
})
|
|
25871
26490
|
];
|
|
25872
26491
|
INTEGRATIONS_DB = "haansi_integrations_prod";
|
|
25873
26492
|
integrationTables = [
|
|
@@ -76695,10 +77314,10 @@ var require_view = __commonJS({
|
|
|
76695
77314
|
var debug = require_src()("express:view");
|
|
76696
77315
|
var path2 = require("node:path");
|
|
76697
77316
|
var fs = require("node:fs");
|
|
76698
|
-
var
|
|
77317
|
+
var dirname5 = path2.dirname;
|
|
76699
77318
|
var basename4 = path2.basename;
|
|
76700
77319
|
var extname = path2.extname;
|
|
76701
|
-
var
|
|
77320
|
+
var join16 = path2.join;
|
|
76702
77321
|
var resolve5 = path2.resolve;
|
|
76703
77322
|
module2.exports = View;
|
|
76704
77323
|
function View(name, options) {
|
|
@@ -76734,7 +77353,7 @@ var require_view = __commonJS({
|
|
|
76734
77353
|
for (var i = 0; i < roots.length && !path3; i++) {
|
|
76735
77354
|
var root = roots[i];
|
|
76736
77355
|
var loc = resolve5(root, name);
|
|
76737
|
-
var dir =
|
|
77356
|
+
var dir = dirname5(loc);
|
|
76738
77357
|
var file2 = basename4(loc);
|
|
76739
77358
|
path3 = this.resolve(dir, file2);
|
|
76740
77359
|
}
|
|
@@ -76760,12 +77379,12 @@ var require_view = __commonJS({
|
|
|
76760
77379
|
};
|
|
76761
77380
|
View.prototype.resolve = function resolve6(dir, file2) {
|
|
76762
77381
|
var ext = this.ext;
|
|
76763
|
-
var path3 =
|
|
77382
|
+
var path3 = join16(dir, file2);
|
|
76764
77383
|
var stat = tryStat(path3);
|
|
76765
77384
|
if (stat && stat.isFile()) {
|
|
76766
77385
|
return path3;
|
|
76767
77386
|
}
|
|
76768
|
-
path3 =
|
|
77387
|
+
path3 = join16(dir, basename4(file2, ext), "index" + ext);
|
|
76769
77388
|
stat = tryStat(path3);
|
|
76770
77389
|
if (stat && stat.isFile()) {
|
|
76771
77390
|
return path3;
|
|
@@ -80410,7 +81029,7 @@ var require_send = __commonJS({
|
|
|
80410
81029
|
var Stream = require("stream");
|
|
80411
81030
|
var util2 = require("util");
|
|
80412
81031
|
var extname = path2.extname;
|
|
80413
|
-
var
|
|
81032
|
+
var join16 = path2.join;
|
|
80414
81033
|
var normalize = path2.normalize;
|
|
80415
81034
|
var resolve5 = path2.resolve;
|
|
80416
81035
|
var sep2 = path2.sep;
|
|
@@ -80582,7 +81201,7 @@ var require_send = __commonJS({
|
|
|
80582
81201
|
return res;
|
|
80583
81202
|
}
|
|
80584
81203
|
parts = path3.split(sep2);
|
|
80585
|
-
path3 = normalize(
|
|
81204
|
+
path3 = normalize(join16(root, path3));
|
|
80586
81205
|
} else {
|
|
80587
81206
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
80588
81207
|
debug('malicious path "%s"', path3);
|
|
@@ -80715,7 +81334,7 @@ var require_send = __commonJS({
|
|
|
80715
81334
|
if (err2) return self.onStatError(err2);
|
|
80716
81335
|
return self.error(404);
|
|
80717
81336
|
}
|
|
80718
|
-
var p =
|
|
81337
|
+
var p = join16(path3, self._index[i]);
|
|
80719
81338
|
debug('stat "%s"', p);
|
|
80720
81339
|
fs.stat(p, function(err3, stat) {
|
|
80721
81340
|
if (err3) return next(err3);
|
|
@@ -84426,6 +85045,363 @@ var init_skills = __esm({
|
|
|
84426
85045
|
}
|
|
84427
85046
|
});
|
|
84428
85047
|
|
|
85048
|
+
// src/commands/replay.ts
|
|
85049
|
+
var replay_exports = {};
|
|
85050
|
+
__export(replay_exports, {
|
|
85051
|
+
githubRunRef: () => githubRunRef,
|
|
85052
|
+
parseReplayArgs: () => parseReplayArgs,
|
|
85053
|
+
replay: () => replay,
|
|
85054
|
+
replayArm: () => replayArm,
|
|
85055
|
+
replayPrompt: () => replayPrompt,
|
|
85056
|
+
repositoryFromRemote: () => repositoryFromRemote,
|
|
85057
|
+
runCommand: () => runCommand,
|
|
85058
|
+
runReplays: () => runReplays,
|
|
85059
|
+
safeTestFile: () => safeTestFile,
|
|
85060
|
+
skipReason: () => skipReason
|
|
85061
|
+
});
|
|
85062
|
+
function parseReplayArgs(argv) {
|
|
85063
|
+
const options = {
|
|
85064
|
+
familyId: "",
|
|
85065
|
+
agentCmd: "",
|
|
85066
|
+
testCmd: "",
|
|
85067
|
+
attempts: 3,
|
|
85068
|
+
arms: [...ARMS],
|
|
85069
|
+
limit: null,
|
|
85070
|
+
cutoff: null,
|
|
85071
|
+
timeoutMs: 30 * 6e4,
|
|
85072
|
+
agentName: null,
|
|
85073
|
+
model: null,
|
|
85074
|
+
runRef: githubRunRef(process.env),
|
|
85075
|
+
dryRun: false
|
|
85076
|
+
};
|
|
85077
|
+
let sub;
|
|
85078
|
+
const errors = [];
|
|
85079
|
+
for (let i = 0; i < argv.length; i++) {
|
|
85080
|
+
const a = argv[i];
|
|
85081
|
+
const next = () => argv[++i] ?? "";
|
|
85082
|
+
if (a === "--family") options.familyId = next();
|
|
85083
|
+
else if (a === "--agent-cmd") options.agentCmd = next();
|
|
85084
|
+
else if (a === "--test-cmd") options.testCmd = next();
|
|
85085
|
+
else if (a === "--attempts") options.attempts = Number(next());
|
|
85086
|
+
else if (a === "--limit") options.limit = Number(next());
|
|
85087
|
+
else if (a === "--cutoff") options.cutoff = next();
|
|
85088
|
+
else if (a === "--timeout-min") options.timeoutMs = Number(next()) * 6e4;
|
|
85089
|
+
else if (a === "--agent-name") options.agentName = next();
|
|
85090
|
+
else if (a === "--model") options.model = next();
|
|
85091
|
+
else if (a === "--run-ref") options.runRef = next();
|
|
85092
|
+
else if (a === "--dry-run") options.dryRun = true;
|
|
85093
|
+
else if (a === "--arms")
|
|
85094
|
+
options.arms = next().split(",").map((s) => s.trim()).filter(Boolean);
|
|
85095
|
+
else if (!sub && !a.startsWith("-")) sub = a;
|
|
85096
|
+
else errors.push(`Unknown option: ${a}`);
|
|
85097
|
+
}
|
|
85098
|
+
if (!/^[0-9a-f-]{36}$/i.test(options.familyId))
|
|
85099
|
+
errors.push("--family <id> is required (the family's id).");
|
|
85100
|
+
if (!options.agentCmd) errors.push("--agent-cmd is required.");
|
|
85101
|
+
if (!options.testCmd) errors.push("--test-cmd is required.");
|
|
85102
|
+
if (!Number.isInteger(options.attempts) || options.attempts < 1 || options.attempts > 20)
|
|
85103
|
+
errors.push("--attempts must be 1 to 20.");
|
|
85104
|
+
if (options.limit !== null && (!Number.isInteger(options.limit) || options.limit < 1))
|
|
85105
|
+
errors.push("--limit must be a positive whole number.");
|
|
85106
|
+
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0)
|
|
85107
|
+
errors.push("--timeout-min must be a positive number.");
|
|
85108
|
+
if (options.arms.length === 0 || options.arms.some((a) => !ARMS.includes(a)))
|
|
85109
|
+
errors.push("--arms must list baseline and/or haansi.");
|
|
85110
|
+
return { sub, options, errors };
|
|
85111
|
+
}
|
|
85112
|
+
function githubRunRef(env) {
|
|
85113
|
+
const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
|
|
85114
|
+
if (!GITHUB_SERVER_URL?.startsWith("https://") || !GITHUB_REPOSITORY || !GITHUB_RUN_ID)
|
|
85115
|
+
return null;
|
|
85116
|
+
return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
|
|
85117
|
+
}
|
|
85118
|
+
function repositoryFromRemote(remote) {
|
|
85119
|
+
const m = /[:/]([^/:\s]+\/[^/\s]+?)(?:\.git)?\/?\s*$/.exec(remote.trim());
|
|
85120
|
+
return m ? m[1].toLowerCase() : null;
|
|
85121
|
+
}
|
|
85122
|
+
function safeTestFile(path2) {
|
|
85123
|
+
return path2.length > 0 && !path2.startsWith("/") && !path2.startsWith("-") && !path2.split("/").includes("..") && !/[\u0000-\u001f]/.test(path2);
|
|
85124
|
+
}
|
|
85125
|
+
function skipReason(task, repository) {
|
|
85126
|
+
if (!repository || task.repository.toLowerCase() !== repository)
|
|
85127
|
+
return `in ${task.repository}, not this repository`;
|
|
85128
|
+
if (!task.baseSha || !SHA.test(task.baseSha)) return "no starting commit";
|
|
85129
|
+
if (!task.targetSha || !SHA.test(task.targetSha)) return "no fix commit";
|
|
85130
|
+
if (task.testFiles.length === 0) return "the fix changed no test file";
|
|
85131
|
+
if (!task.testFiles.every(safeTestFile)) return "a test path is not usable";
|
|
85132
|
+
return null;
|
|
85133
|
+
}
|
|
85134
|
+
function replayPrompt(task, arm, feedback) {
|
|
85135
|
+
const context = task.context.length ? task.context.map((c) => `- ${c}`).join("\n") : "- (No record of the task before the first attempt was captured.)";
|
|
85136
|
+
return [
|
|
85137
|
+
`# ${task.name ?? "A task in this repository"}`,
|
|
85138
|
+
"",
|
|
85139
|
+
"Solve the problem these describe, in this repository as it is now:",
|
|
85140
|
+
"",
|
|
85141
|
+
context,
|
|
85142
|
+
"",
|
|
85143
|
+
...arm === "haansi" ? [
|
|
85144
|
+
"Your team's playbook for this kind of problem is in .haansi-replay/playbook.md. Read it first.",
|
|
85145
|
+
""
|
|
85146
|
+
] : [],
|
|
85147
|
+
...feedback ? [
|
|
85148
|
+
"Your previous attempt did not pass the tests. Their output ends with:",
|
|
85149
|
+
"",
|
|
85150
|
+
"```",
|
|
85151
|
+
feedback,
|
|
85152
|
+
"```",
|
|
85153
|
+
""
|
|
85154
|
+
] : [],
|
|
85155
|
+
"Do not look at later commits or other branches."
|
|
85156
|
+
].join("\n");
|
|
85157
|
+
}
|
|
85158
|
+
async function ensureCommit(deps, sha) {
|
|
85159
|
+
if ((await deps.git(["cat-file", "-e", `${sha}^{commit}`], deps.repoDir)).code === 0)
|
|
85160
|
+
return true;
|
|
85161
|
+
return (await deps.git(["fetch", "--quiet", "origin", sha], deps.repoDir)).code === 0;
|
|
85162
|
+
}
|
|
85163
|
+
async function replayArm(deps, options, task, arm, playbook) {
|
|
85164
|
+
const started = deps.now();
|
|
85165
|
+
const dir = (0, import_node_path9.join)(
|
|
85166
|
+
deps.workDir(),
|
|
85167
|
+
`${task.processId.replace(/[^a-zA-Z0-9_-]/g, "")}-${arm}`
|
|
85168
|
+
);
|
|
85169
|
+
const files = task.testFiles;
|
|
85170
|
+
const add = await deps.git(
|
|
85171
|
+
["worktree", "add", "--detach", dir, task.baseSha],
|
|
85172
|
+
deps.repoDir
|
|
85173
|
+
);
|
|
85174
|
+
if (add.code !== 0) throw new Error(`could not check out ${task.baseSha}`);
|
|
85175
|
+
try {
|
|
85176
|
+
const promptFile = (0, import_node_path9.join)(dir, ".haansi-replay", "prompt.md");
|
|
85177
|
+
if (arm === "haansi" && playbook)
|
|
85178
|
+
deps.writeFile(
|
|
85179
|
+
(0, import_node_path9.join)(dir, ".haansi-replay", "playbook.md"),
|
|
85180
|
+
playbook.skillMd
|
|
85181
|
+
);
|
|
85182
|
+
let feedback = null;
|
|
85183
|
+
for (let attempt = 1; attempt <= options.attempts; attempt++) {
|
|
85184
|
+
const prompt2 = replayPrompt(task, arm, feedback);
|
|
85185
|
+
deps.writeFile(promptFile, prompt2);
|
|
85186
|
+
await deps.shell(
|
|
85187
|
+
options.agentCmd,
|
|
85188
|
+
dir,
|
|
85189
|
+
{ HAANSI_REPLAY_PROMPT: prompt2, HAANSI_REPLAY_PROMPT_FILE: promptFile },
|
|
85190
|
+
options.timeoutMs
|
|
85191
|
+
);
|
|
85192
|
+
const own = new Map(files.map((f) => [f, deps.readFile((0, import_node_path9.join)(dir, f))]));
|
|
85193
|
+
const checkout = await deps.git(
|
|
85194
|
+
["checkout", task.targetSha, "--", ...files],
|
|
85195
|
+
dir
|
|
85196
|
+
);
|
|
85197
|
+
if (checkout.code !== 0)
|
|
85198
|
+
throw new Error("could not check out the fix's tests");
|
|
85199
|
+
const test = await deps.shell(
|
|
85200
|
+
options.testCmd,
|
|
85201
|
+
dir,
|
|
85202
|
+
{ HAANSI_REPLAY_TEST_FILES: files.join(" ") },
|
|
85203
|
+
options.timeoutMs
|
|
85204
|
+
);
|
|
85205
|
+
if (test.code === 0) {
|
|
85206
|
+
return {
|
|
85207
|
+
processId: task.processId,
|
|
85208
|
+
arm,
|
|
85209
|
+
passed: true,
|
|
85210
|
+
attempts: attempt,
|
|
85211
|
+
maxAttempts: options.attempts,
|
|
85212
|
+
durationMs: deps.now() - started,
|
|
85213
|
+
revisionId: arm === "haansi" ? playbook?.revisionId ?? null : null
|
|
85214
|
+
};
|
|
85215
|
+
}
|
|
85216
|
+
for (const [file2, content] of own) {
|
|
85217
|
+
if (content === null) deps.removeFile((0, import_node_path9.join)(dir, file2));
|
|
85218
|
+
else deps.writeFile((0, import_node_path9.join)(dir, file2), content);
|
|
85219
|
+
}
|
|
85220
|
+
feedback = test.output.slice(-FEEDBACK_CHARS);
|
|
85221
|
+
}
|
|
85222
|
+
return {
|
|
85223
|
+
processId: task.processId,
|
|
85224
|
+
arm,
|
|
85225
|
+
passed: false,
|
|
85226
|
+
attempts: options.attempts,
|
|
85227
|
+
maxAttempts: options.attempts,
|
|
85228
|
+
durationMs: deps.now() - started,
|
|
85229
|
+
revisionId: arm === "haansi" ? playbook?.revisionId ?? null : null
|
|
85230
|
+
};
|
|
85231
|
+
} finally {
|
|
85232
|
+
await deps.git(["worktree", "remove", "--force", dir], deps.repoDir);
|
|
85233
|
+
deps.removeDir(dir);
|
|
85234
|
+
}
|
|
85235
|
+
}
|
|
85236
|
+
async function runReplays(options, deps) {
|
|
85237
|
+
const remote = await deps.git(["remote", "get-url", "origin"], deps.repoDir);
|
|
85238
|
+
const repository = remote.code === 0 ? repositoryFromRemote(remote.output) : null;
|
|
85239
|
+
const tasks = await deps.getTasks(options.familyId, options.cutoff);
|
|
85240
|
+
let arms = options.arms;
|
|
85241
|
+
let playbook = null;
|
|
85242
|
+
if (arms.includes("haansi")) {
|
|
85243
|
+
playbook = await deps.getPlaybook(options.familyId);
|
|
85244
|
+
if (!playbook) {
|
|
85245
|
+
deps.log(
|
|
85246
|
+
"The family has no published playbook you can read: running the baseline arm only."
|
|
85247
|
+
);
|
|
85248
|
+
arms = arms.filter((a) => a !== "haansi");
|
|
85249
|
+
}
|
|
85250
|
+
}
|
|
85251
|
+
const runnable = [];
|
|
85252
|
+
let skipped = 0;
|
|
85253
|
+
for (const task of tasks) {
|
|
85254
|
+
const reason = skipReason(task, repository);
|
|
85255
|
+
if (reason) {
|
|
85256
|
+
skipped += 1;
|
|
85257
|
+
deps.log(`skip ${task.name ?? task.processId}: ${reason}`);
|
|
85258
|
+
} else runnable.push(task);
|
|
85259
|
+
}
|
|
85260
|
+
const chosen = options.limit ? runnable.slice(0, options.limit) : runnable;
|
|
85261
|
+
const results = [];
|
|
85262
|
+
for (const task of chosen) {
|
|
85263
|
+
if (!await ensureCommit(deps, task.baseSha) || !await ensureCommit(deps, task.targetSha)) {
|
|
85264
|
+
skipped += 1;
|
|
85265
|
+
deps.log(
|
|
85266
|
+
`skip ${task.name ?? task.processId}: its commits are not reachable from origin`
|
|
85267
|
+
);
|
|
85268
|
+
continue;
|
|
85269
|
+
}
|
|
85270
|
+
for (const arm of arms) {
|
|
85271
|
+
try {
|
|
85272
|
+
const result = await replayArm(deps, options, task, arm, playbook);
|
|
85273
|
+
results.push(result);
|
|
85274
|
+
deps.log(
|
|
85275
|
+
`${result.passed ? "pass" : "fail"} ${arm.padEnd(8)} ${task.name ?? task.processId} (${result.attempts}/${result.maxAttempts} attempts)`
|
|
85276
|
+
);
|
|
85277
|
+
} catch (err2) {
|
|
85278
|
+
deps.log(
|
|
85279
|
+
`error ${arm} ${task.name ?? task.processId}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
85280
|
+
);
|
|
85281
|
+
}
|
|
85282
|
+
}
|
|
85283
|
+
}
|
|
85284
|
+
if (options.dryRun || results.length === 0) {
|
|
85285
|
+
return { results, skipped, recorded: null };
|
|
85286
|
+
}
|
|
85287
|
+
const reported = await deps.report(options.familyId, {
|
|
85288
|
+
agent: options.agentName,
|
|
85289
|
+
model: options.model,
|
|
85290
|
+
runRef: options.runRef,
|
|
85291
|
+
results
|
|
85292
|
+
});
|
|
85293
|
+
if (reported.refused.length)
|
|
85294
|
+
deps.log(
|
|
85295
|
+
`Haansi did not record ${reported.refused.length} task(s) it no longer lists for this family.`
|
|
85296
|
+
);
|
|
85297
|
+
return { results, skipped, recorded: reported.recorded };
|
|
85298
|
+
}
|
|
85299
|
+
function runCommand(file2, args, cwd, env, timeoutMs) {
|
|
85300
|
+
return new Promise((resolvePromise) => {
|
|
85301
|
+
const child = (0, import_node_child_process4.spawn)(file2, args, {
|
|
85302
|
+
cwd,
|
|
85303
|
+
env: { ...process.env, ...env },
|
|
85304
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
85305
|
+
});
|
|
85306
|
+
let output = "";
|
|
85307
|
+
const keep = (chunk) => {
|
|
85308
|
+
output = (output + chunk.toString()).slice(-2e5);
|
|
85309
|
+
};
|
|
85310
|
+
child.stdout.on("data", keep);
|
|
85311
|
+
child.stderr.on("data", keep);
|
|
85312
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
85313
|
+
child.on("close", (code) => {
|
|
85314
|
+
clearTimeout(timer);
|
|
85315
|
+
resolvePromise({ code: code ?? 1, output });
|
|
85316
|
+
});
|
|
85317
|
+
child.on("error", () => {
|
|
85318
|
+
clearTimeout(timer);
|
|
85319
|
+
resolvePromise({ code: 1, output });
|
|
85320
|
+
});
|
|
85321
|
+
});
|
|
85322
|
+
}
|
|
85323
|
+
async function replay(argv) {
|
|
85324
|
+
const { sub, options, errors } = parseReplayArgs(argv);
|
|
85325
|
+
if (sub !== "run" || errors.length) {
|
|
85326
|
+
for (const e of sub === "run" ? errors : []) console.error(e);
|
|
85327
|
+
console.log(USAGE);
|
|
85328
|
+
if (sub === "run") process.exitCode = 1;
|
|
85329
|
+
return;
|
|
85330
|
+
}
|
|
85331
|
+
const token = resolveToken5();
|
|
85332
|
+
const apiUrl2 = apiUrl();
|
|
85333
|
+
const work = (0, import_node_fs10.mkdtempSync)((0, import_node_path9.join)((0, import_node_os9.tmpdir)(), "haansi-replay-"));
|
|
85334
|
+
try {
|
|
85335
|
+
const { results, skipped, recorded } = await runReplays(options, {
|
|
85336
|
+
getTasks: async (familyId, cutoff) => (await apiGet3(
|
|
85337
|
+
`/memory/processes/families/${familyId}/replay-tasks${cutoff ? `?cutoff=${encodeURIComponent(cutoff)}` : ""}`,
|
|
85338
|
+
token,
|
|
85339
|
+
apiUrl2
|
|
85340
|
+
)).replay,
|
|
85341
|
+
getPlaybook: async (familyId) => (await apiGet3(
|
|
85342
|
+
`/memory/skills/families/${familyId}/playbook`,
|
|
85343
|
+
token,
|
|
85344
|
+
apiUrl2
|
|
85345
|
+
).catch(() => ({ data: { playbook: null } }))).data.playbook,
|
|
85346
|
+
report: async (familyId, body) => await apiPost2(
|
|
85347
|
+
`/memory/processes/families/${familyId}/replay-results`,
|
|
85348
|
+
body,
|
|
85349
|
+
token,
|
|
85350
|
+
apiUrl2
|
|
85351
|
+
),
|
|
85352
|
+
git: (args, cwd) => runCommand("git", args, cwd, {}, 10 * 6e4),
|
|
85353
|
+
shell: (command2, cwd, env, timeoutMs) => runCommand("sh", ["-c", command2], cwd, env, timeoutMs),
|
|
85354
|
+
repoDir: process.cwd(),
|
|
85355
|
+
workDir: () => work,
|
|
85356
|
+
writeFile: (path2, content) => {
|
|
85357
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path9.dirname)(path2), { recursive: true });
|
|
85358
|
+
(0, import_node_fs10.writeFileSync)(path2, content);
|
|
85359
|
+
},
|
|
85360
|
+
readFile: (path2) => (0, import_node_fs10.existsSync)(path2) ? (0, import_node_fs10.readFileSync)(path2, "utf-8") : null,
|
|
85361
|
+
removeFile: (path2) => (0, import_node_fs10.rmSync)(path2, { force: true }),
|
|
85362
|
+
removeDir: (path2) => (0, import_node_fs10.rmSync)(path2, { recursive: true, force: true }),
|
|
85363
|
+
now: () => Date.now(),
|
|
85364
|
+
log: (line) => console.log(line)
|
|
85365
|
+
});
|
|
85366
|
+
const passed = (arm) => results.filter((r) => r.arm === arm && r.passed).length;
|
|
85367
|
+
const ran = (arm) => results.filter((r) => r.arm === arm).length;
|
|
85368
|
+
console.log(
|
|
85369
|
+
`
|
|
85370
|
+
Baseline: ${passed("baseline")}/${ran("baseline")} passed \xB7 With Haansi: ${passed("haansi")}/${ran("haansi")} passed \xB7 ${skipped} skipped`
|
|
85371
|
+
);
|
|
85372
|
+
console.log(
|
|
85373
|
+
recorded === null ? options.dryRun ? "Dry run: nothing reported." : "Nothing to report." : `Reported ${recorded} result(s) to Haansi.`
|
|
85374
|
+
);
|
|
85375
|
+
} finally {
|
|
85376
|
+
(0, import_node_fs10.rmSync)(work, { recursive: true, force: true });
|
|
85377
|
+
}
|
|
85378
|
+
}
|
|
85379
|
+
var import_node_child_process4, import_node_fs10, import_node_os9, import_node_path9, SHA, ARMS, FEEDBACK_CHARS, USAGE;
|
|
85380
|
+
var init_replay = __esm({
|
|
85381
|
+
"src/commands/replay.ts"() {
|
|
85382
|
+
"use strict";
|
|
85383
|
+
import_node_child_process4 = require("node:child_process");
|
|
85384
|
+
import_node_fs10 = require("node:fs");
|
|
85385
|
+
import_node_os9 = require("node:os");
|
|
85386
|
+
import_node_path9 = require("node:path");
|
|
85387
|
+
init_paths();
|
|
85388
|
+
init_api2();
|
|
85389
|
+
SHA = /^[0-9a-f]{7,40}$/i;
|
|
85390
|
+
ARMS = ["baseline", "haansi"];
|
|
85391
|
+
FEEDBACK_CHARS = 6e3;
|
|
85392
|
+
USAGE = `Usage: haansi replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85393
|
+
[--attempts 3] [--arms baseline,haansi] [--limit n]
|
|
85394
|
+
[--cutoff <date>] [--timeout-min 30]
|
|
85395
|
+
[--agent-name <name>] [--model <name>] [--run-ref <url>] [--dry-run]
|
|
85396
|
+
|
|
85397
|
+
Run from a clone of the repository. Example (GitHub Actions):
|
|
85398
|
+
haansi replay run --family "$FAMILY_ID" \\
|
|
85399
|
+
--agent-cmd 'claude -p "$HAANSI_REPLAY_PROMPT" --permission-mode acceptEdits' \\
|
|
85400
|
+
--test-cmd 'npx vitest run $HAANSI_REPLAY_TEST_FILES' \\
|
|
85401
|
+
--agent-name claude-code --model claude-sonnet-5-5`;
|
|
85402
|
+
}
|
|
85403
|
+
});
|
|
85404
|
+
|
|
84429
85405
|
// src/index.ts
|
|
84430
85406
|
init_paths();
|
|
84431
85407
|
var { version: VERSION } = require_package();
|
|
@@ -84598,6 +85574,11 @@ Active selection (${selection.source}): ${selection.agents.join(", ")}`
|
|
|
84598
85574
|
await skills2(process.argv.slice(3));
|
|
84599
85575
|
break;
|
|
84600
85576
|
}
|
|
85577
|
+
case "replay": {
|
|
85578
|
+
const { replay: replay2 } = await Promise.resolve().then(() => (init_replay(), replay_exports));
|
|
85579
|
+
await replay2(process.argv.slice(3));
|
|
85580
|
+
break;
|
|
85581
|
+
}
|
|
84601
85582
|
case "digest": {
|
|
84602
85583
|
const { digest: digest2, parseDigestRange: parseDigestRange2 } = await Promise.resolve().then(() => (init_digest(), digest_exports));
|
|
84603
85584
|
await digest2(parseDigestRange2(process.argv.slice(3)));
|
|
@@ -84635,6 +85616,9 @@ Commands:
|
|
|
84635
85616
|
digest. [--date YYYY-MM-DD] [--workstream <id>]
|
|
84636
85617
|
digest [--week|--month|--since YYYY-MM-DD]
|
|
84637
85618
|
Your weekly digest (default: the latest week)
|
|
85619
|
+
replay run --family <id> --agent-cmd "<cmd>" --test-cmd "<cmd>"
|
|
85620
|
+
Replay a problem family's tasks in your CI, with and
|
|
85621
|
+
without its playbook, and report the results
|
|
84638
85622
|
skills sync [--agent <sel>] [--project] [--dir <path>]
|
|
84639
85623
|
Copy your team's published skills into Claude Code / Codex
|
|
84640
85624
|
|