haansi 0.1.49 → 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 +994 -11
- 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"
|
|
@@ -17785,6 +17785,78 @@ var init_exclusions = __esm({
|
|
|
17785
17785
|
category: "operational",
|
|
17786
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."
|
|
17787
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
|
+
},
|
|
17788
17860
|
{
|
|
17789
17861
|
database: "haansi_integrations_prod",
|
|
17790
17862
|
table: "source_accounts",
|
|
@@ -18002,6 +18074,20 @@ var init_exclusions = __esm({
|
|
|
18002
18074
|
category: "operational",
|
|
18003
18075
|
reason: "A nine-value CHECK-constrained enum (created, commented, reviewed, \u2026) copied from the canonical activity. No personal data."
|
|
18004
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
|
+
},
|
|
18005
18091
|
{
|
|
18006
18092
|
database: "haansi_intelligence_prod",
|
|
18007
18093
|
table: "organization_mining_config",
|
|
@@ -18880,7 +18966,414 @@ var init_exclusions = __esm({
|
|
|
18880
18966
|
column: "event",
|
|
18881
18967
|
category: "operational",
|
|
18882
18968
|
reason: "Closed CHECK vocabulary (loaded, resource_loaded, synced, applied, helped, not_helped)."
|
|
18883
|
-
}
|
|
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
|
+
})
|
|
18884
19377
|
];
|
|
18885
19378
|
}
|
|
18886
19379
|
});
|
|
@@ -25868,7 +26361,132 @@ var init_table_registry = __esm({
|
|
|
25868
26361
|
"artifact_cluster_workstreams",
|
|
25869
26362
|
"direct",
|
|
25870
26363
|
{ scopingColumn: "organization_id" }
|
|
25871
|
-
)
|
|
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
|
+
})
|
|
25872
26490
|
];
|
|
25873
26491
|
INTEGRATIONS_DB = "haansi_integrations_prod";
|
|
25874
26492
|
integrationTables = [
|
|
@@ -76696,10 +77314,10 @@ var require_view = __commonJS({
|
|
|
76696
77314
|
var debug = require_src()("express:view");
|
|
76697
77315
|
var path2 = require("node:path");
|
|
76698
77316
|
var fs = require("node:fs");
|
|
76699
|
-
var
|
|
77317
|
+
var dirname5 = path2.dirname;
|
|
76700
77318
|
var basename4 = path2.basename;
|
|
76701
77319
|
var extname = path2.extname;
|
|
76702
|
-
var
|
|
77320
|
+
var join16 = path2.join;
|
|
76703
77321
|
var resolve5 = path2.resolve;
|
|
76704
77322
|
module2.exports = View;
|
|
76705
77323
|
function View(name, options) {
|
|
@@ -76735,7 +77353,7 @@ var require_view = __commonJS({
|
|
|
76735
77353
|
for (var i = 0; i < roots.length && !path3; i++) {
|
|
76736
77354
|
var root = roots[i];
|
|
76737
77355
|
var loc = resolve5(root, name);
|
|
76738
|
-
var dir =
|
|
77356
|
+
var dir = dirname5(loc);
|
|
76739
77357
|
var file2 = basename4(loc);
|
|
76740
77358
|
path3 = this.resolve(dir, file2);
|
|
76741
77359
|
}
|
|
@@ -76761,12 +77379,12 @@ var require_view = __commonJS({
|
|
|
76761
77379
|
};
|
|
76762
77380
|
View.prototype.resolve = function resolve6(dir, file2) {
|
|
76763
77381
|
var ext = this.ext;
|
|
76764
|
-
var path3 =
|
|
77382
|
+
var path3 = join16(dir, file2);
|
|
76765
77383
|
var stat = tryStat(path3);
|
|
76766
77384
|
if (stat && stat.isFile()) {
|
|
76767
77385
|
return path3;
|
|
76768
77386
|
}
|
|
76769
|
-
path3 =
|
|
77387
|
+
path3 = join16(dir, basename4(file2, ext), "index" + ext);
|
|
76770
77388
|
stat = tryStat(path3);
|
|
76771
77389
|
if (stat && stat.isFile()) {
|
|
76772
77390
|
return path3;
|
|
@@ -80411,7 +81029,7 @@ var require_send = __commonJS({
|
|
|
80411
81029
|
var Stream = require("stream");
|
|
80412
81030
|
var util2 = require("util");
|
|
80413
81031
|
var extname = path2.extname;
|
|
80414
|
-
var
|
|
81032
|
+
var join16 = path2.join;
|
|
80415
81033
|
var normalize = path2.normalize;
|
|
80416
81034
|
var resolve5 = path2.resolve;
|
|
80417
81035
|
var sep2 = path2.sep;
|
|
@@ -80583,7 +81201,7 @@ var require_send = __commonJS({
|
|
|
80583
81201
|
return res;
|
|
80584
81202
|
}
|
|
80585
81203
|
parts = path3.split(sep2);
|
|
80586
|
-
path3 = normalize(
|
|
81204
|
+
path3 = normalize(join16(root, path3));
|
|
80587
81205
|
} else {
|
|
80588
81206
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
80589
81207
|
debug('malicious path "%s"', path3);
|
|
@@ -80716,7 +81334,7 @@ var require_send = __commonJS({
|
|
|
80716
81334
|
if (err2) return self.onStatError(err2);
|
|
80717
81335
|
return self.error(404);
|
|
80718
81336
|
}
|
|
80719
|
-
var p =
|
|
81337
|
+
var p = join16(path3, self._index[i]);
|
|
80720
81338
|
debug('stat "%s"', p);
|
|
80721
81339
|
fs.stat(p, function(err3, stat) {
|
|
80722
81340
|
if (err3) return next(err3);
|
|
@@ -84427,6 +85045,363 @@ var init_skills = __esm({
|
|
|
84427
85045
|
}
|
|
84428
85046
|
});
|
|
84429
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
|
+
|
|
84430
85405
|
// src/index.ts
|
|
84431
85406
|
init_paths();
|
|
84432
85407
|
var { version: VERSION } = require_package();
|
|
@@ -84599,6 +85574,11 @@ Active selection (${selection.source}): ${selection.agents.join(", ")}`
|
|
|
84599
85574
|
await skills2(process.argv.slice(3));
|
|
84600
85575
|
break;
|
|
84601
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
|
+
}
|
|
84602
85582
|
case "digest": {
|
|
84603
85583
|
const { digest: digest2, parseDigestRange: parseDigestRange2 } = await Promise.resolve().then(() => (init_digest(), digest_exports));
|
|
84604
85584
|
await digest2(parseDigestRange2(process.argv.slice(3)));
|
|
@@ -84636,6 +85616,9 @@ Commands:
|
|
|
84636
85616
|
digest. [--date YYYY-MM-DD] [--workstream <id>]
|
|
84637
85617
|
digest [--week|--month|--since YYYY-MM-DD]
|
|
84638
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
|
|
84639
85622
|
skills sync [--agent <sel>] [--project] [--dir <path>]
|
|
84640
85623
|
Copy your team's published skills into Claude Code / Codex
|
|
84641
85624
|
|