@ascenda-one/github-collector 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,11 @@ separately revocable.
52
52
 
53
53
  ## Use in GitHub Actions
54
54
 
55
+ Ready to copy: [`examples/workflow.yml`](./examples/workflow.yml). Both secrets
56
+ come from pairing a collector identity of its own —
57
+ `npx -y @ascenda-one/claude-code-hooks pair --tool-type github_collector` — which
58
+ prints the installation id and names the token file to read.
59
+
55
60
  ```yaml
56
61
  name: ascenda-collaboration
57
62
  on:
package/dist/cli.js CHANGED
@@ -583,7 +583,61 @@ var require_out = __commonJS({
583
583
  "../packages/tool-contract/out/index.js"(exports) {
584
584
  "use strict";
585
585
  Object.defineProperty(exports, "__esModule", { value: true });
586
- exports.backendMetricKeys = exports.METRIC_KEYS = exports.ASCENDA_SEMANTIC_PROVENANCE = exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = exports.ASCENDA_PROVENANCE = exports.ASCENDA_CONSENT_SCOPE = exports.EVENT_WORKLOAD_CATEGORY = exports.TOOL_EVENT_DELIVERED_STATUSES = exports.IDEMPOTENCY_KEY_MAX_LENGTH = exports.EVENT_METADATA_FIELDS = exports.COLLABORATION_EVENT_TYPES = exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = void 0;
586
+ exports.backendMetricKeys = exports.METRIC_KEYS = exports.ASCENDA_SEMANTIC_PROVENANCE = exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = exports.ASCENDA_PROVENANCE = exports.ASCENDA_CONSENT_SCOPE = exports.EVENT_WORKLOAD_CATEGORY = exports.TOOL_EVENT_DELIVERED_STATUSES = exports.IDEMPOTENCY_KEY_MAX_LENGTH = exports.EVENT_METADATA_FIELDS = exports.COLLABORATION_EVENT_TYPES = exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = exports.ASCENDA_ELAPSED_QUANTITIES = exports.ASCENDA_ACTIVE_TIME_QUANTITIES = exports.ASCENDA_TOOL_TYPES = exports.ASCENDA_TELEMETRY_SOURCES = void 0;
587
+ exports.combineDisjointHalves = combineDisjointHalves;
588
+ exports.requireComparableQuantities = requireComparableQuantities;
589
+ exports.ASCENDA_TELEMETRY_SOURCES = [
590
+ "vscode_extension",
591
+ "cursor_mcp",
592
+ "claude_code",
593
+ "copilot_otel",
594
+ "cli_agent",
595
+ "mcp_server",
596
+ "activity_signals",
597
+ "code_forge"
598
+ ];
599
+ exports.ASCENDA_TOOL_TYPES = [
600
+ "vscode_extension",
601
+ "cursor_mcp",
602
+ "claude_code",
603
+ "copilot_otel",
604
+ "cli_agent",
605
+ "mcp_server",
606
+ "github_collector",
607
+ "other"
608
+ ];
609
+ exports.ASCENDA_ACTIVE_TIME_QUANTITIES = [
610
+ "coverage",
611
+ "hands_on",
612
+ "supervising",
613
+ "block_coverage",
614
+ "hands_on_agent_hours",
615
+ "supervising_agent_hours"
616
+ ];
617
+ exports.ASCENDA_ELAPSED_QUANTITIES = {
618
+ coverage: true,
619
+ hands_on: true,
620
+ supervising: true,
621
+ block_coverage: true,
622
+ hands_on_agent_hours: false,
623
+ supervising_agent_hours: false
624
+ };
625
+ var DISJOINT_HALVES = [
626
+ ["hands_on", "supervising", "coverage"],
627
+ ["supervising", "hands_on", "coverage"]
628
+ ];
629
+ function combineDisjointHalves(left, leftQuantity, right, rightQuantity) {
630
+ const pair = DISJOINT_HALVES.find(([a, b]) => a === leftQuantity && b === rightQuantity);
631
+ if (!pair) {
632
+ throw new Error(`'combine-disjoint' combines active-time quantities '${leftQuantity}' and '${rightQuantity}', which do not partition a common whole.`);
633
+ }
634
+ return { total: left + right, quantity: pair[2] };
635
+ }
636
+ function requireComparableQuantities(left, right, operation) {
637
+ if (left !== right) {
638
+ throw new Error(`'${operation}' combines active-time quantities '${left}' and '${right}', which measure different things. See docs/ACTIVE_TIME.md, 'The quantity is the label, not the gap'.`);
639
+ }
640
+ }
587
641
  exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = [
588
642
  "approach_churn_detected",
589
643
  "goal_drift_detected",
@@ -1167,12 +1221,11 @@ var require_stateStore = __commonJS({
1167
1221
  exports.shouldAnnounceFailure = shouldAnnounceFailure;
1168
1222
  exports.markFailureNotified = markFailureNotified;
1169
1223
  var fs = __importStar(__require("fs"));
1170
- var os = __importStar(__require("os"));
1171
1224
  var path = __importStar(__require("path"));
1172
1225
  var tokenStore_1 = require_tokenStore();
1173
1226
  function defaultStateFilePath(toolInstallationId) {
1174
1227
  const dir = process.env.ASCENDA_STATE_DIR?.trim();
1175
- const base2 = dir ? dir : path.join(os.homedir(), ".ascenda", "state");
1228
+ const base2 = dir ? dir : path.join((0, tokenStore_1.ascendaHome)(), "state");
1176
1229
  return path.join(base2, `${(0, tokenStore_1.sanitizeFilePart)(toolInstallationId)}.json`);
1177
1230
  }
1178
1231
  function unresolvedToolInstallationId(toolType) {
@@ -1494,6 +1547,25 @@ var require_outbox = __commonJS({
1494
1547
  }
1495
1548
  });
1496
1549
 
1550
+ // ../packages/tool-kit/out/timeProvider.js
1551
+ var require_timeProvider = __commonJS({
1552
+ "../packages/tool-kit/out/timeProvider.js"(exports) {
1553
+ "use strict";
1554
+ Object.defineProperty(exports, "__esModule", { value: true });
1555
+ exports.systemTimeProvider = void 0;
1556
+ exports.fixedTimeProvider = fixedTimeProvider;
1557
+ exports.systemTimeProvider = {
1558
+ now: () => Date.now()
1559
+ };
1560
+ function fixedTimeProvider(instant) {
1561
+ const ms = typeof instant === "number" ? instant : new Date(instant).valueOf();
1562
+ if (!Number.isFinite(ms))
1563
+ throw new TypeError(`fixedTimeProvider: not a usable instant: ${String(instant)}`);
1564
+ return { now: () => ms };
1565
+ }
1566
+ }
1567
+ });
1568
+
1497
1569
  // ../packages/tool-kit/out/eventSender.js
1498
1570
  var require_eventSender = __commonJS({
1499
1571
  "../packages/tool-kit/out/eventSender.js"(exports) {
@@ -1506,6 +1578,7 @@ var require_eventSender = __commonJS({
1506
1578
  var eventLog_1 = require_eventLog();
1507
1579
  var http_1 = require_http();
1508
1580
  var outbox_1 = require_outbox();
1581
+ var timeProvider_1 = require_timeProvider();
1509
1582
  var tokenStore_1 = require_tokenStore();
1510
1583
  var stateStore_1 = require_stateStore();
1511
1584
  var payload_1 = require_payload();
@@ -1542,9 +1615,12 @@ var require_eventSender = __commonJS({
1542
1615
  lastDrain;
1543
1616
  /** One outbox pass per sender, i.e. per hook process. The hook is on the user's critical path. */
1544
1617
  outboxServiced = false;
1618
+ /** Resolved once, so every clock read in one pass sees the same instant. */
1619
+ time;
1545
1620
  constructor(config) {
1546
1621
  this.config = config;
1547
1622
  this.eventWriteToken = config.eventWriteToken;
1623
+ this.time = config.timeProvider ?? timeProvider_1.systemTimeProvider;
1548
1624
  }
1549
1625
  async send(mapped) {
1550
1626
  return this.post(buildEventPayload(this.config, mapped));
@@ -1695,7 +1771,7 @@ var require_eventSender = __commonJS({
1695
1771
  * journal's detail says so instead of implying it was kept.
1696
1772
  */
1697
1773
  enqueue(payload) {
1698
- return (0, outbox_1.appendToOutbox)(this.outboxFilePath(), payload);
1774
+ return (0, outbox_1.appendToOutbox)(this.outboxFilePath(), payload, new Date(this.time.now()));
1699
1775
  }
1700
1776
  /**
1701
1777
  * One pass over the outbox: claim it, apply the bounds, and — when sending
@@ -1718,7 +1794,7 @@ var require_eventSender = __commonJS({
1718
1794
  return void 0;
1719
1795
  this.outboxServiced = true;
1720
1796
  const sendEnabled = this.config.outboxDrain ?? (0, outbox_1.outboxDrainEnabled)();
1721
- const claimed = (0, outbox_1.claimOutbox)(this.outboxFilePath());
1797
+ const claimed = (0, outbox_1.claimOutbox)(this.outboxFilePath(), this.time.now());
1722
1798
  if (!claimed) {
1723
1799
  this.lastDrain = { found: 0, discarded: 0, delivered: 0, remaining: 0, sendEnabled };
1724
1800
  return void 0;
@@ -1727,7 +1803,7 @@ var require_eventSender = __commonJS({
1727
1803
  const { kept, discarded } = (0, outbox_1.enforceOutboxBounds)(claimed.entries, {
1728
1804
  maxEntries: this.config.outboxMaxEntries ?? outbox_1.DEFAULT_OUTBOX_MAX_ENTRIES,
1729
1805
  maxAgeMs: this.config.outboxMaxAgeMs ?? outbox_1.DEFAULT_OUTBOX_MAX_AGE_MS
1730
- });
1806
+ }, this.time.now());
1731
1807
  if (claimed.unreadable > 0) {
1732
1808
  discarded.count += claimed.unreadable;
1733
1809
  discarded.reasons.unreadable = claimed.unreadable;
@@ -3149,9 +3225,9 @@ var require_turnState = __commonJS({
3149
3225
  exports.recordTurnStart = recordTurnStart;
3150
3226
  exports.consumeTurnDurationMs = consumeTurnDurationMs;
3151
3227
  var fs = __importStar(__require("fs"));
3152
- var os = __importStar(__require("os"));
3153
3228
  var path = __importStar(__require("path"));
3154
- var stateDir = () => process.env.ASCENDA_STATE_DIR ?? path.join(os.homedir(), ".ascenda", "state");
3229
+ var tokenStore_1 = require_tokenStore();
3230
+ var stateDir = () => process.env.ASCENDA_STATE_DIR ?? path.join((0, tokenStore_1.ascendaHome)(), "state");
3155
3231
  function turnFile(agent, sessionId) {
3156
3232
  return path.join(stateDir(), `${sanitize(agent)}-turn-${sanitize(sessionId)}`);
3157
3233
  }
@@ -3318,8 +3394,8 @@ var require_out2 = __commonJS({
3318
3394
  "use strict";
3319
3395
  Object.defineProperty(exports, "__esModule", { value: true });
3320
3396
  exports.consumeTurnDurationMs = exports.writeTopLevelCredentials = exports.writeMachineCredentials = exports.writeHostCredentials = exports.removeHostCredentials = exports.readMachineCredentials = exports.readHostCredentials = exports.credentialsFilePath = exports.writeHookSettings = exports.runCliAgentSetup = exports.isCliAgentManagementCommand = exports.findStaleHookCommands = exports.cliAgentHookBinPath = exports.resolveContextHashes = exports.resolveCliAgentInstallationId = exports.loadCliAgentConfig = exports.deliverHookEvents = exports.MissingInstallationIdError = exports.DEFAULT_API_BASE_URL = exports.resolveEventLogPath = exports.expandUserPath = exports.appendEventLog = exports.EVENT_LOG_ENV_VAR = exports.buildEventPayload = exports.AscendaSemanticEventError = exports.AscendaEventSender = exports.mintIdempotencyKey = exports.looksLikeCorrection = exports.outcomeForHook = exports.inferOutcome = exports.getNestedNumber = exports.getNestedString = exports.getNested = exports.getNumber = exports.getString = exports.localHourAt = exports.utcOffsetMinutesAt = exports.BUSINESS_DAY = exports.isOutsideBusinessHours = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.classifyModelClass = exports.autonomyBand = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
3321
- exports.postToolEvent = exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.recordForgeProjectAlias = exports.forgeFullNameFromConfig = exports.readForgeFullName = exports.parseForgeFullName = exports.forgeProjectHash = exports.workContextRegistryFilePath = exports.readWorkContextRegistry = exports.recordWorkContextAlias = exports.recordWorkContext = exports.readBranchName = exports.normalizeBranchName = exports.deriveBranchHashForCwd = exports.deriveBranchHash = exports.deriveWorkContext = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.enforceOutboxBounds = exports.claimOutbox = exports.readOutboxSummary = exports.appendToOutbox = exports.defaultOutboxFilePath = exports.outboxDrainEnabled = exports.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE = exports.DEFAULT_OUTBOX_MAX_AGE_MS = exports.DEFAULT_OUTBOX_MAX_ENTRIES = exports.OUTBOX_DRAIN_ENV_VAR = exports.recordOutboxDiscard = exports.unresolvedToolInstallationId = exports.unresolvedStateFilePath = exports.markFailureNotified = exports.shouldAnnounceFailure = exports.recordSendOutcome = exports.readCollectorState = exports.defaultStateFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.listPersistedToolInstallationIds = exports.defaultTokenFilePath = exports.ascendaHome = exports.recordTurnStart = void 0;
3322
- exports.isRetryableStatus = exports.parseIngestResponse = exports.postToolEventsBatch = void 0;
3397
+ exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.recordForgeProjectAlias = exports.forgeFullNameFromConfig = exports.readForgeFullName = exports.parseForgeFullName = exports.forgeProjectHash = exports.workContextRegistryFilePath = exports.readWorkContextRegistry = exports.recordWorkContextAlias = exports.recordWorkContext = exports.readBranchName = exports.normalizeBranchName = exports.deriveBranchHashForCwd = exports.deriveBranchHash = exports.deriveWorkContext = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.fixedTimeProvider = exports.systemTimeProvider = exports.enforceOutboxBounds = exports.claimOutbox = exports.readOutboxSummary = exports.appendToOutbox = exports.defaultOutboxFilePath = exports.outboxDrainEnabled = exports.DEFAULT_OUTBOX_DRAIN_BATCH_SIZE = exports.DEFAULT_OUTBOX_MAX_AGE_MS = exports.DEFAULT_OUTBOX_MAX_ENTRIES = exports.OUTBOX_DRAIN_ENV_VAR = exports.recordOutboxDiscard = exports.unresolvedToolInstallationId = exports.unresolvedStateFilePath = exports.markFailureNotified = exports.shouldAnnounceFailure = exports.recordSendOutcome = exports.readCollectorState = exports.defaultStateFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.listPersistedToolInstallationIds = exports.defaultTokenFilePath = exports.ascendaHome = exports.recordTurnStart = void 0;
3398
+ exports.isRetryableStatus = exports.parseIngestResponse = exports.postToolEventsBatch = exports.postToolEvent = exports.renewToolToken = void 0;
3323
3399
  var commandClassifier_1 = require_commandClassifier();
3324
3400
  Object.defineProperty(exports, "classifyCommand", { enumerable: true, get: function() {
3325
3401
  return commandClassifier_1.classifyCommand;
@@ -3559,6 +3635,13 @@ var require_out2 = __commonJS({
3559
3635
  Object.defineProperty(exports, "enforceOutboxBounds", { enumerable: true, get: function() {
3560
3636
  return outbox_1.enforceOutboxBounds;
3561
3637
  } });
3638
+ var timeProvider_1 = require_timeProvider();
3639
+ Object.defineProperty(exports, "systemTimeProvider", { enumerable: true, get: function() {
3640
+ return timeProvider_1.systemTimeProvider;
3641
+ } });
3642
+ Object.defineProperty(exports, "fixedTimeProvider", { enumerable: true, get: function() {
3643
+ return timeProvider_1.fixedTimeProvider;
3644
+ } });
3562
3645
  var salt_1 = require_salt();
3563
3646
  Object.defineProperty(exports, "machineSaltFilePath", { enumerable: true, get: function() {
3564
3647
  return salt_1.machineSaltFilePath;
@@ -0,0 +1,29 @@
1
+ # Collaboration signals — review load and pull-request activity — for one
2
+ # repository. Drop this at .github/workflows/ascenda-collaboration.yml.
3
+ #
4
+ # Both secrets come from pairing a collector identity on your machine:
5
+ # npx -y @ascenda-one/claude-code-hooks pair --tool-type github_collector
6
+ # which prints the installation id and the path of the token file to read.
7
+ #
8
+ # This collector has its own tool type and rides workflow_telemetry, not
9
+ # ide_telemetry: how you work with your team is separately revocable from
10
+ # how you work in your editor.
11
+ name: ascenda-collaboration
12
+
13
+ on:
14
+ pull_request:
15
+ types: [opened, review_requested]
16
+ pull_request_review:
17
+ types: [submitted]
18
+
19
+ jobs:
20
+ collect:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ # Exits 0 on every path that is not a configuration error, including
24
+ # "nothing to emit" — a telemetry step must never turn a build red.
25
+ - run: npx @ascenda-one/github-collector
26
+ env:
27
+ ASCENDA_TOOL_INSTALLATION_ID: ${{ secrets.ASCENDA_TOOL_INSTALLATION_ID }}
28
+ ASCENDA_EVENT_WRITE_TOKEN: ${{ secrets.ASCENDA_EVENT_WRITE_TOKEN }}
29
+ ASCENDA_FORGE_LOGIN: ${{ github.actor }}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "publishConfig": { "access": "public" },
3
3
  "name": "@ascenda-one/github-collector",
4
- "version": "0.1.17",
4
+ "version": "0.1.19",
5
5
  "description": "Code-forge collaboration collector for Ascenda work telemetry — first-person review and pull-request signals.",
6
6
  "repository": {
7
7
  "type": "git",