@ascenda-one/github-collector 0.1.18 → 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 +5 -0
- package/dist/cli.js +93 -10
- package/examples/workflow.yml +29 -0
- package/package.json +1 -1
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(
|
|
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
|
|
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.
|
|
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.
|
|
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",
|