github-issue-tower-defence-management 2.152.14 → 2.153.0

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.
Files changed (116) hide show
  1. package/README.md +9 -3
  2. package/bin/adapter/ci/githubActionsExpressionEvaluator.js +222 -0
  3. package/bin/adapter/ci/githubActionsExpressionEvaluator.js.map +1 -0
  4. package/bin/adapter/entry-points/cli/checkIssueSilentDispatchAllowed.js +69 -0
  5. package/bin/adapter/entry-points/cli/checkIssueSilentDispatchAllowed.js.map +1 -0
  6. package/bin/adapter/entry-points/cli/index.js +49 -0
  7. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  8. package/bin/adapter/entry-points/console/readOnlyTokenRotator.js +1 -0
  9. package/bin/adapter/entry-points/console/readOnlyTokenRotator.js.map +1 -1
  10. package/bin/adapter/entry-points/console/ui-dist/assets/{index-7oBNB4OI.js → index-CA29c3Kd.js} +1 -1
  11. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  12. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -1
  13. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  14. package/bin/adapter/entry-points/handlers/tokenExhaustionHandover.js +3 -1
  15. package/bin/adapter/entry-points/handlers/tokenExhaustionHandover.js.map +1 -1
  16. package/bin/adapter/repositories/GitHubIssueCommentRepository.js +10 -2
  17. package/bin/adapter/repositories/GitHubIssueCommentRepository.js.map +1 -1
  18. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +59 -2
  19. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  20. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +19 -6
  21. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
  22. package/bin/adapter/repositories/issue/githubRateLimitRetry.js +18 -6
  23. package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -1
  24. package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js +0 -3
  25. package/bin/domain/usecases/ChangeStatusByStoryColorUseCase.js.map +1 -1
  26. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +2 -1
  27. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  28. package/bin/domain/usecases/ClearDependedIssueURLUseCase.js +4 -0
  29. package/bin/domain/usecases/ClearDependedIssueURLUseCase.js.map +1 -1
  30. package/bin/domain/usecases/CliErrorReportUseCase.js +3 -8
  31. package/bin/domain/usecases/CliErrorReportUseCase.js.map +1 -1
  32. package/bin/domain/usecases/ConflictedIssueRevertUseCase.js +2 -0
  33. package/bin/domain/usecases/ConflictedIssueRevertUseCase.js.map +1 -1
  34. package/bin/domain/usecases/HandleScheduledEventUseCase.js +52 -20
  35. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  36. package/bin/domain/usecases/NonPreparationWorkerScopeStopUseCase.js +4 -0
  37. package/bin/domain/usecases/NonPreparationWorkerScopeStopUseCase.js.map +1 -1
  38. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +30 -2
  39. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  40. package/bin/domain/usecases/PullRequestProjectItemRemoveUseCase.js +43 -0
  41. package/bin/domain/usecases/PullRequestProjectItemRemoveUseCase.js.map +1 -0
  42. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +4 -1
  43. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  44. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +82 -9
  45. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  46. package/bin/domain/usecases/StaleTaskPullRequestCloseUseCase.js +19 -12
  47. package/bin/domain/usecases/StaleTaskPullRequestCloseUseCase.js.map +1 -1
  48. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js +13 -3
  49. package/bin/domain/usecases/TokenExhaustionHandoverUseCase.js.map +1 -1
  50. package/bin/domain/usecases/adapter-interfaces/ProjectItemRepository.js +3 -0
  51. package/bin/domain/usecases/adapter-interfaces/ProjectItemRepository.js.map +1 -0
  52. package/bin/domain/usecases/autoStatusCheckComments.js +11 -1
  53. package/bin/domain/usecases/autoStatusCheckComments.js.map +1 -1
  54. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +1 -0
  55. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  56. package/bin/domain/usecases/isSilentDispatchAllowedByIssueBody.js +12 -0
  57. package/bin/domain/usecases/isSilentDispatchAllowedByIssueBody.js.map +1 -0
  58. package/bin/domain/usecases/isTransientApiError.js +19 -0
  59. package/bin/domain/usecases/isTransientApiError.js.map +1 -0
  60. package/bin/domain/usecases/resolveNextStepAgentDispatchRepetition.js +16 -4
  61. package/bin/domain/usecases/resolveNextStepAgentDispatchRepetition.js.map +1 -1
  62. package/package.json +2 -2
  63. package/types/adapter/ci/githubActionsExpressionEvaluator.d.ts +5 -0
  64. package/types/adapter/ci/githubActionsExpressionEvaluator.d.ts.map +1 -0
  65. package/types/adapter/entry-points/cli/checkIssueSilentDispatchAllowed.d.ts +10 -0
  66. package/types/adapter/entry-points/cli/checkIssueSilentDispatchAllowed.d.ts.map +1 -0
  67. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  68. package/types/adapter/entry-points/console/readOnlyTokenRotator.d.ts.map +1 -1
  69. package/types/adapter/entry-points/handlers/tokenExhaustionHandover.d.ts +1 -1
  70. package/types/adapter/entry-points/handlers/tokenExhaustionHandover.d.ts.map +1 -1
  71. package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts +5 -0
  72. package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts.map +1 -1
  73. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
  74. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  75. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
  76. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +11 -3
  77. package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -1
  78. package/types/domain/entities/Issue.d.ts +1 -0
  79. package/types/domain/entities/Issue.d.ts.map +1 -1
  80. package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts +0 -1
  81. package/types/domain/usecases/ChangeStatusByStoryColorUseCase.d.ts.map +1 -1
  82. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  83. package/types/domain/usecases/ClearDependedIssueURLUseCase.d.ts.map +1 -1
  84. package/types/domain/usecases/CliErrorReportUseCase.d.ts.map +1 -1
  85. package/types/domain/usecases/ConflictedIssueRevertUseCase.d.ts +1 -1
  86. package/types/domain/usecases/ConflictedIssueRevertUseCase.d.ts.map +1 -1
  87. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +2 -0
  88. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  89. package/types/domain/usecases/NonPreparationWorkerScopeStopUseCase.d.ts +1 -0
  90. package/types/domain/usecases/NonPreparationWorkerScopeStopUseCase.d.ts.map +1 -1
  91. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +3 -1
  92. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  93. package/types/domain/usecases/PullRequestProjectItemRemoveUseCase.d.ts +20 -0
  94. package/types/domain/usecases/PullRequestProjectItemRemoveUseCase.d.ts.map +1 -0
  95. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts +1 -1
  96. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  97. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +5 -2
  98. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  99. package/types/domain/usecases/StaleTaskPullRequestCloseUseCase.d.ts +2 -2
  100. package/types/domain/usecases/StaleTaskPullRequestCloseUseCase.d.ts.map +1 -1
  101. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts +2 -0
  102. package/types/domain/usecases/TokenExhaustionHandoverUseCase.d.ts.map +1 -1
  103. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
  104. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  105. package/types/domain/usecases/adapter-interfaces/ProjectItemRepository.d.ts +14 -0
  106. package/types/domain/usecases/adapter-interfaces/ProjectItemRepository.d.ts.map +1 -0
  107. package/types/domain/usecases/autoStatusCheckComments.d.ts +3 -0
  108. package/types/domain/usecases/autoStatusCheckComments.d.ts.map +1 -1
  109. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +1 -0
  110. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  111. package/types/domain/usecases/isSilentDispatchAllowedByIssueBody.d.ts +3 -0
  112. package/types/domain/usecases/isSilentDispatchAllowedByIssueBody.d.ts.map +1 -0
  113. package/types/domain/usecases/isTransientApiError.d.ts +2 -0
  114. package/types/domain/usecases/isTransientApiError.d.ts.map +1 -0
  115. package/types/domain/usecases/resolveNextStepAgentDispatchRepetition.d.ts +4 -0
  116. package/types/domain/usecases/resolveNextStepAgentDispatchRepetition.d.ts.map +1 -1
package/README.md CHANGED
@@ -30,6 +30,7 @@ Commands:
30
30
  ownerCallFileAppend [options] Append one owner call to the per-session owner call file (writes nothing to stdout)
31
31
  ownerCallFileDelete [options] Delete the per-session owner call file (writes nothing to stdout; an already absent file is not an error)
32
32
  archive-unresumable-session [options] Move aside the conversation record of a session that ended because its prompt exceeded the model context limit, so the next launch starts a fresh conversation
33
+ check-issue-silent-dispatch-allowed [options] Check whether an issue's body allows a dispatch cycle to end with no agent comment (read-only)
33
34
  help [command] display help for command
34
35
 
35
36
  Options for schedule:
@@ -117,6 +118,9 @@ Options for archive-unresumable-session:
117
118
  --log-file <path> Path to the finished run stream-json log (required)
118
119
  --session-dir <path> Directory holding conversation records named <session-id>.jsonl (required)
119
120
  --archive-dir <path> Destination directory the conversation record is moved into; created when it does not exist (required)
121
+
122
+ Options for check-issue-silent-dispatch-allowed:
123
+ --issue-url <url> GitHub issue URL (required)
120
124
  ```
121
125
 
122
126
  The `serveWeb` sub-command starts a local HTTP server that serves the TDPM web surface — the console tabs, the dashboard, and the in-tmux-by-human session list. `serveConsole` remains available as a deprecated alias that maps to the same handler, so existing invokers keep working during rollout; new usage should prefer `serveWeb`. One running instance serves every project: the user opens a per-project URL path `/projects/{pjcode}` (or `/projects/{pjcode}/{workflow-blocker|prs|triage|unread|failed-preparation|todo-by-human}`) and the bundled UI reads the `pjcode` from its own URL path and loads that project's list data. The server serves the bundled single-page-application `index.html` at `/`, `/index.html`, and every `/projects/{pjcode}` and `/projects/{pjcode}/{tab}` app route. Every response is sent with `Cache-Control: no-store`. Any request path containing a segment that begins with a dot (for example `/.git` or `/.env`) is rejected with HTTP 404. The UI bootstrap assets (HTML and JS) are served without authentication; served `*.json` files and `/api/*` paths require an access token supplied either as the `k` query parameter (`?k=<token>`) or the `X-PV-Token` request header. The access token is read from the `consoleAccessToken` config value and never appears on the command line. When the built UI bundle directory (`ui-dist`) is absent the server still starts and serves a minimal placeholder index for `/`, `/index.html`, and the per-project app routes.
@@ -215,6 +219,8 @@ The `ownerCallFileAppend` and `ownerCallFileDelete` sub-commands maintain one te
215
219
 
216
220
  The `archive-unresumable-session` sub-command moves aside the stored conversation record of a session that ended because its prompt exceeded the model context limit, so the next launch for that task starts a fresh conversation instead of resuming a record that can never load again. Without it, a launcher that resumes the same stored conversation on every attempt keeps terminating on the same condition and the task never starts. It reads the finished run's stream-json log named by `--log-file` line by line, parsing each line that is a JSON object and ignoring every line that is not. A record matches when all three of `type` equals `result`, `is_error` is `true`, and `result` equals `Prompt is too long` hold; the session whose record is archived is the `session_id` of that matching record. A session that merely hit a usage limit ends with `terminal_reason` `api_error`, `api_error_status` `429`, and a different `result`, so it does not match and nothing is moved. When no record matches, the sub-command writes `no-op` to stdout, changes nothing, and exits 0. When a record matches, it creates `--archive-dir` if it does not exist, moves `<--session-dir>/<session-id>.jsonl` to `<--archive-dir>/<session-id>.jsonl`, writes `archived <destination path>` to stdout, and exits 0. The conversation record is moved, never deleted, so the conversation stays recoverable. When a record matches but that conversation record file is absent, the sub-command writes a diagnostic naming the expected source path to stderr and exits 1. It only changes where the record is stored; it does not change how a session is chosen for a task.
217
221
 
222
+ The `check-issue-silent-dispatch-allowed` sub-command reports whether the issue named by `--issue-url` declares, in its own body, that a dispatch cycle ending with no agent comment is expected and must not be treated as a missing report. This lets `notifyFinishedIssuePreparation` skip the `NO_REPORT_FROM_AGENT_BOT` rejection for a task whose body already instructs the assigned agent to stay silent during some or all dispatch cycles, a design used for certain recurring, owner-directed tasks. It parses `owner`, `repo`, and the issue number from `--issue-url`, fetches that issue's body from the GitHub REST API using the `GH_TOKEN` environment variable, and evaluates it against the exact literal marker `<!-- TDPM_SILENT_DISPATCH_ALLOWED -->`: the marker must appear as a contiguous substring of the body, so the bare token without its `<!-- -->` wrapper does not count. It writes nothing to stdout. It exits 0 when the marker is present, 1 when it is absent, and 2 with a diagnostic on stderr when `--issue-url` cannot be parsed or the GitHub API call fails.
223
+
218
224
  ## Example 📖
219
225
 
220
226
  Here's a quick example to illustrate its usage:
@@ -403,11 +409,11 @@ inTmuxConsoleToken?: string # Optional: Token embedded in the ?k= query string o
403
409
  inTmuxProjectOrder?: string[] # Optional: Ordered list of project codes used to build the in-tmux-by-human index files. When unset or empty, the index files are skipped
404
410
  newIssueRepo?: string # Optional: Repository name used only for the repo segment of the v4 in-tmux-by-human newIssueUrl. When unset, the newIssueUrl repo segment falls back to workingReport.repo, preserving the existing behavior. Only the newIssueUrl is affected; workingReport.repo and every other use of it are unchanged
405
411
  inTmuxLauncherCommand?: string # Optional: Launcher command that starts an interactive session for an `In Tmux by human` issue. Each schedule cycle, any open assigned `In Tmux by human` issue without a live session is restarted by running this command with the issue URL as its argument inside a new detached tmux session named after the issue URL. When unset, session restarting is skipped
406
- tokenExhaustionHandoverEnabled?: boolean # Optional: When true, the daemon detects Claude sessions whose OAuth token is near/at the rate limit (five-hour free < 0.10, seven-day free < 0.05 for tmux leaders, blocked/rejected, blockedUntilEpoch in the future, or a rejected weekly hard-cap whose resetsAt is still in the future) and, only when a DIFFERENT token is confirmed not-stale-not-exhausted, performs the handover: a checkpoint instruction is sent to tmux leaders via `tmux send-keys` (impl `-p` subagents receive SIGTERM instead) and the session is killed after the grace period. Issue-URL leaders are relaunched by the preparation daemon; bare-name resident leaders are relaunched by this step via `cl <name>` on a fresher token; impl subagents are left for the daemon to re-dispatch. Requires `claudeCodeOauthTokenListJsonPath`. When false, detection and logging still run but no send-keys, kill, or relaunch is performed (dry-run). Default false
412
+ tokenExhaustionHandoverEnabled?: boolean # Optional: When true, the daemon detects Claude sessions whose OAuth token is near/at the rate limit (five-hour free < 0.10, seven-day free < 0.05 for tmux leaders, blocked/rejected, blockedUntilEpoch in the future, or a rejected weekly hard-cap whose resetsAt is still in the future) and, only when a DIFFERENT token is confirmed not-stale-not-exhausted, performs the handover immediately in the same cycle it is detected: a checkpoint instruction is sent to tmux leaders via `tmux send-keys` (impl `-p` subagents receive SIGTERM instead) and the session is killed right away, with no advance-notice message and no pre-action wait. Issue-URL leaders are relaunched by the preparation daemon; bare-name resident leaders are relaunched by this step via `cl <name>` on a fresher token; impl subagents are left for the daemon to re-dispatch. Requires `claudeCodeOauthTokenListJsonPath`. When false, detection and logging still run but no send-keys, kill, or relaunch is performed (dry-run). Default false
407
413
  tokenExhaustionHandoverMessage?: string # Optional: Checkpoint instruction sent to an exhausted issue-URL leader via `tmux send-keys`. When unset, a built-in default that tells the session to checkpoint to its task issue and then self-kill is used
408
414
  tokenExhaustionHandoverBareNameLeaderMessage?: string # Optional: Checkpoint instruction sent to an exhausted bare-name resident leader (a leader started with a bare `--name` and no task issue) via `tmux send-keys`. When unset, a built-in default that tells the leader to checkpoint into each in-flight subagent's own issue and NOT self-kill (this step relaunches it) is used
409
415
  tokenRateLimitSnapshotBaseDir?: string # Optional: Directory path for the per-token rate-limit snapshot JSON files read by the token exhaustion handover step. When unset, defaults to ${XDG_CACHE_HOME:-~/.cache}/tdpm/ratelimit
410
- tokenExhaustionGracePeriodSeconds?: number # Optional: Seconds to wait after sending the handover message before killing the tmux session, giving the session time to shut down gracefully. Default 180
416
+ tokenExhaustionHandoverActionCooldownSeconds?: number # Optional: Minimum number of seconds that must elapse since the last kill/relaunch action taken on a given session before that same session may be actioned again on a later cycle. Does not delay or gate the first action taken on a session. Default 180
411
417
  tokenExhaustionHandoverStateFilePath?: string # Optional: File path for the durable handover grace/debounce state (which sessions were signaled and when), written atomically so grace tracking survives daemon restarts. When unset, defaults to ${XDG_CACHE_HOME:-~/.cache}/tdpm/token-exhaustion-handover-state-tdpm-native.json (a TDPM-native filename kept distinct from any standalone monitor's state file so the two never share state)
412
418
  silentNotificationEnabled?: boolean # Optional: Master switch for the silent live session self-check notification step. The step is a no-op unless this is `true`, the fleet-wide config file named by `--fleetConfigFilePath` (or `TDPM_FLEET_CONFIG`) contains a top-level `silentNotificationEnabled: true`, or the `TDPM_SILENT_NOTIFICATION_ENABLED` environment variable equals the string `true`. Priority order: this per-project config key (highest), fleet config (middle), env var (lowest). Default false
413
419
  outputDegenerationResetEnabled?: boolean # Optional: Master switch for the output-degeneration reset step. When true, the daemon inspects every live interactive Claude Code session named after a github.com issue or pull-request URL and, when the session's most recent assistant turn is a collapsed single-token repeat loop (intra-turn) or the same isolated short trailing token recurs across the majority of recent turns (cross-turn), sends a checkpoint warning to the session via `tmux send-keys`, waits the grace period, and kills the tmux session so it is relaunched fresh. When false, detection and logging still run but no warning or kill is performed (dry-run). Default false
@@ -752,6 +758,7 @@ The following use cases execute on every `schedule` trigger (cadence is determin
752
758
  - `ClearDependedIssueURLUseCase` (items with a closed depended issue, on cycles that do not run the slow sweep) — runs before `StartPreparationUseCase` on the issue list the cycle already fetched and does not fetch issues again. For every open item whose Depended Issue URL lists a closed issue, it removes the closed and Icebox URLs from the field, so a task whose blocker closed is dispatched in the same cycle. It calls GitHub only for an item it changes, with the same field write, comment read and notification comment the slow sweep would make for that item. Items whose depended issues are only open, in Icebox, absent from the issue list or circular are left to the slow sweep. A failure is logged with the failing item URLs and the cycle continues.
753
759
  - `StartPreparationUseCase` — starts preparation for issues ready to be worked on
754
760
  - `NotifyFinishedIssuePreparationUseCase` — checks preparation-status issues and advances them. When the issue carries one or more `change-target:<path>` labels and all auto status checks pass, the related pull request's full file list is fetched (paginated, no 100-file truncation) and the PR is auto-approved before transitioning to Awaiting Owner if every changed file is under at least one of the labeled paths. Matching is boundary-safe (`change-target:foo` matches `foo/bar.ts` but not `foobar/baz.ts`); multiple `change-target:` labels are OR-combined. When no `change-target:` label is present, behavior is unchanged. When the issue carries one or more `change-target-must:<path>` labels, the PR is also rejected with a `CHANGE_TARGET_MUST_PATH_NOT_CHANGED` status when no changed file falls under a required path. The rejection submits a "request changes" review containing an inline comment on the first changed file (or a PR-level comment when the PR has no changed files) that names the required directory. `change-target-must:` paths are also treated as allowed paths for the confinement check, behaving identically to `change-target:` in addition to requiring at least one change under the path. When the last agent comment's first fenced JSON block includes a non-empty `nextStepAgent` string property, the use case adds the label named by that value to the issue (creating the label in the repository if it does not already exist) and transitions the issue to Awaiting Workspace, allowing the next agent identified by the label to pick up the issue automatically.
761
+ - `UpdateIssueStatusByLabelUseCase`
755
762
 
756
763
  ### Slow path (runs at most once per 600 seconds)
757
764
 
@@ -759,7 +766,6 @@ The following use cases run only when at least 600 seconds have elapsed since th
759
766
 
760
767
  - `CreateNewStoryByLabelUseCase`
761
768
  - `ChangeStatusByStoryColorUseCase`
762
- - `UpdateIssueStatusByLabelUseCase`
763
769
  - `SetWorkflowManagementIssueToStoryUseCase`
764
770
  - `SetNoStoryIssueToStoryUseCase`
765
771
  - `ActionAnnouncementUseCase`
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateGitHubActionsExpression = void 0;
4
+ const isWhitespaceCharacter = (character) => character === ' ' ||
5
+ character === '\t' ||
6
+ character === '\n' ||
7
+ character === '\r';
8
+ const isIdentifierStartCharacter = (character) => /[A-Za-z_]/.test(character);
9
+ const isIdentifierBodyCharacter = (character) => /[A-Za-z0-9_.]/.test(character);
10
+ const tokenizeGitHubActionsExpression = (expression) => {
11
+ const tokens = [];
12
+ let position = 0;
13
+ while (position < expression.length) {
14
+ const character = expression[position];
15
+ if (isWhitespaceCharacter(character)) {
16
+ position += 1;
17
+ continue;
18
+ }
19
+ if (character === '(') {
20
+ tokens.push({ kind: 'openParenthesis' });
21
+ position += 1;
22
+ continue;
23
+ }
24
+ if (character === ')') {
25
+ tokens.push({ kind: 'closeParenthesis' });
26
+ position += 1;
27
+ continue;
28
+ }
29
+ if (character === "'") {
30
+ const closingQuoteIndex = expression.indexOf("'", position + 1);
31
+ if (closingQuoteIndex === -1) {
32
+ throw new Error(`unterminated string literal starting at position ${position} in GitHub Actions expression: ${expression}`);
33
+ }
34
+ tokens.push({
35
+ kind: 'stringLiteral',
36
+ value: expression.slice(position + 1, closingQuoteIndex),
37
+ });
38
+ position = closingQuoteIndex + 1;
39
+ continue;
40
+ }
41
+ if (character === '=' && expression[position + 1] === '=') {
42
+ tokens.push({ kind: 'comparisonOperator', operator: '==' });
43
+ position += 2;
44
+ continue;
45
+ }
46
+ if (character === '!' && expression[position + 1] === '=') {
47
+ tokens.push({ kind: 'comparisonOperator', operator: '!=' });
48
+ position += 2;
49
+ continue;
50
+ }
51
+ if (character === '&' && expression[position + 1] === '&') {
52
+ tokens.push({ kind: 'logicalOperator', operator: '&&' });
53
+ position += 2;
54
+ continue;
55
+ }
56
+ if (character === '|' && expression[position + 1] === '|') {
57
+ tokens.push({ kind: 'logicalOperator', operator: '||' });
58
+ position += 2;
59
+ continue;
60
+ }
61
+ if (isIdentifierStartCharacter(character)) {
62
+ let end = position + 1;
63
+ while (end < expression.length &&
64
+ isIdentifierBodyCharacter(expression[end])) {
65
+ end += 1;
66
+ }
67
+ tokens.push({
68
+ kind: 'identifierPath',
69
+ segments: expression.slice(position, end).split('.'),
70
+ });
71
+ position = end;
72
+ continue;
73
+ }
74
+ throw new Error(`unsupported operator or character '${character}' at position ${position} in GitHub Actions expression: ${expression}`);
75
+ }
76
+ return tokens;
77
+ };
78
+ const parseGitHubActionsExpression = (expression) => {
79
+ const tokens = tokenizeGitHubActionsExpression(expression);
80
+ let position = 0;
81
+ function peekToken() {
82
+ return tokens[position];
83
+ }
84
+ function consumeComparisonOperator() {
85
+ const token = peekToken();
86
+ if (token !== undefined && token.kind === 'comparisonOperator') {
87
+ position += 1;
88
+ return token.operator;
89
+ }
90
+ return undefined;
91
+ }
92
+ function consumeLogicalOperator(expected) {
93
+ const token = peekToken();
94
+ if (token !== undefined &&
95
+ token.kind === 'logicalOperator' &&
96
+ token.operator === expected) {
97
+ position += 1;
98
+ return true;
99
+ }
100
+ return false;
101
+ }
102
+ function parsePrimaryExpression() {
103
+ const token = peekToken();
104
+ if (token === undefined) {
105
+ throw new Error(`unexpected end of GitHub Actions expression: ${expression}`);
106
+ }
107
+ if (token.kind === 'openParenthesis') {
108
+ position += 1;
109
+ const inner = parseLogicalOrExpression();
110
+ const closingToken = peekToken();
111
+ if (closingToken === undefined ||
112
+ closingToken.kind !== 'closeParenthesis') {
113
+ throw new Error(`unbalanced parentheses in GitHub Actions expression: ${expression}`);
114
+ }
115
+ position += 1;
116
+ return inner;
117
+ }
118
+ if (token.kind === 'stringLiteral') {
119
+ position += 1;
120
+ return { kind: 'stringLiteral', value: token.value };
121
+ }
122
+ if (token.kind === 'identifierPath') {
123
+ position += 1;
124
+ return { kind: 'identifierPath', segments: token.segments };
125
+ }
126
+ throw new Error(`expected an identifier path, string literal, or parenthesized expression in GitHub Actions expression: ${expression}`);
127
+ }
128
+ function parseEqualityExpression() {
129
+ let node = parsePrimaryExpression();
130
+ let operator = consumeComparisonOperator();
131
+ while (operator !== undefined) {
132
+ const right = parsePrimaryExpression();
133
+ node = { kind: 'comparison', operator, left: node, right };
134
+ operator = consumeComparisonOperator();
135
+ }
136
+ return node;
137
+ }
138
+ function parseLogicalAndExpression() {
139
+ let node = parseEqualityExpression();
140
+ while (consumeLogicalOperator('&&')) {
141
+ const right = parseEqualityExpression();
142
+ node = { kind: 'logical', operator: '&&', left: node, right };
143
+ }
144
+ return node;
145
+ }
146
+ function parseLogicalOrExpression() {
147
+ let node = parseLogicalAndExpression();
148
+ while (consumeLogicalOperator('||')) {
149
+ const right = parseLogicalAndExpression();
150
+ node = { kind: 'logical', operator: '||', left: node, right };
151
+ }
152
+ return node;
153
+ }
154
+ const rootNode = parseLogicalOrExpression();
155
+ if (position !== tokens.length) {
156
+ throw new Error(`unbalanced parentheses or trailing content in GitHub Actions expression: ${expression}`);
157
+ }
158
+ return rootNode;
159
+ };
160
+ const isGitHubActionsContextRecord = (value) => typeof value === 'object' && value !== null;
161
+ const isNullishContextValue = (value) => value === null || value === undefined;
162
+ const resolveIdentifierPath = (segments, context) => {
163
+ let current = context;
164
+ for (const segment of segments) {
165
+ if (!isGitHubActionsContextRecord(current)) {
166
+ return undefined;
167
+ }
168
+ current = current[segment];
169
+ }
170
+ return current;
171
+ };
172
+ const toLowerCasedComparableText = (value) => String(value).toLowerCase();
173
+ const evaluateEqualityComparison = (left, right) => {
174
+ if (isNullishContextValue(left) || isNullishContextValue(right)) {
175
+ return isNullishContextValue(left) && isNullishContextValue(right);
176
+ }
177
+ if (isGitHubActionsContextRecord(left) ||
178
+ isGitHubActionsContextRecord(right)) {
179
+ throw new Error('cannot compare an object value with == or != in a GitHub Actions expression');
180
+ }
181
+ return toLowerCasedComparableText(left) === toLowerCasedComparableText(right);
182
+ };
183
+ const toBooleanExpressionResult = (value) => {
184
+ if (typeof value === 'boolean') {
185
+ return value;
186
+ }
187
+ if (isNullishContextValue(value)) {
188
+ return false;
189
+ }
190
+ if (isGitHubActionsContextRecord(value)) {
191
+ return true;
192
+ }
193
+ if (typeof value === 'number') {
194
+ return value !== 0;
195
+ }
196
+ return value.length > 0;
197
+ };
198
+ const evaluateExpressionNode = (node, context) => {
199
+ if (node.kind === 'stringLiteral') {
200
+ return node.value;
201
+ }
202
+ if (node.kind === 'identifierPath') {
203
+ return resolveIdentifierPath(node.segments, context);
204
+ }
205
+ if (node.kind === 'comparison') {
206
+ const isEqual = evaluateEqualityComparison(evaluateExpressionNode(node.left, context), evaluateExpressionNode(node.right, context));
207
+ return node.operator === '==' ? isEqual : !isEqual;
208
+ }
209
+ const leftBoolean = toBooleanExpressionResult(evaluateExpressionNode(node.left, context));
210
+ if (node.operator === '&&') {
211
+ return (leftBoolean &&
212
+ toBooleanExpressionResult(evaluateExpressionNode(node.right, context)));
213
+ }
214
+ return (leftBoolean ||
215
+ toBooleanExpressionResult(evaluateExpressionNode(node.right, context)));
216
+ };
217
+ const evaluateGitHubActionsExpression = (expression, context) => {
218
+ const rootNode = parseGitHubActionsExpression(expression);
219
+ return toBooleanExpressionResult(evaluateExpressionNode(rootNode, context));
220
+ };
221
+ exports.evaluateGitHubActionsExpression = evaluateGitHubActionsExpression;
222
+ //# sourceMappingURL=githubActionsExpressionEvaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"githubActionsExpressionEvaluator.js","sourceRoot":"","sources":["../../../src/adapter/ci/githubActionsExpressionEvaluator.ts"],"names":[],"mappings":";;;AAsCA,MAAM,qBAAqB,GAAG,CAAC,SAAiB,EAAW,EAAE,CAC3D,SAAS,KAAK,GAAG;IACjB,SAAS,KAAK,IAAI;IAClB,SAAS,KAAK,IAAI;IAClB,SAAS,KAAK,IAAI,CAAC;AAErB,MAAM,0BAA0B,GAAG,CAAC,SAAiB,EAAW,EAAE,CAChE,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE9B,MAAM,yBAAyB,GAAG,CAAC,SAAiB,EAAW,EAAE,CAC/D,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAElC,MAAM,+BAA+B,GAAG,CACtC,UAAkB,EACC,EAAE;IACrB,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,OAAO,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,qBAAqB,CAAC,SAAS,CAAC,EAAE,CAAC;YACrC,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACzC,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC1C,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACtB,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;YAChE,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CACb,oDAAoD,QAAQ,kCAAkC,UAAU,EAAE,CAC3G,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,eAAe;gBACrB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,iBAAiB,CAAC;aACzD,CAAC,CAAC;YACH,QAAQ,GAAG,iBAAiB,GAAG,CAAC,CAAC;YACjC,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,SAAS,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC1D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACzD,QAAQ,IAAI,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,IAAI,0BAA0B,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1C,IAAI,GAAG,GAAG,QAAQ,GAAG,CAAC,CAAC;YACvB,OACE,GAAG,GAAG,UAAU,CAAC,MAAM;gBACvB,yBAAyB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAC1C,CAAC;gBACD,GAAG,IAAI,CAAC,CAAC;YACX,CAAC;YACD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,gBAAgB;gBACtB,QAAQ,EAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;aACrD,CAAC,CAAC;YACH,QAAQ,GAAG,GAAG,CAAC;YACf,SAAS;QACX,CAAC;QACD,MAAM,IAAI,KAAK,CACb,sCAAsC,SAAS,iBAAiB,QAAQ,kCAAkC,UAAU,EAAE,CACvH,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,4BAA4B,GAAG,CAAC,UAAkB,EAAkB,EAAE;IAC1E,MAAM,MAAM,GAAG,+BAA+B,CAAC,UAAU,CAAC,CAAC;IAC3D,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,SAAS,SAAS;QAChB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,SAAS,yBAAyB;QAChC,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAC/D,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,KAAK,CAAC,QAAQ,CAAC;QACxB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,SAAS,sBAAsB,CAAC,QAAyB;QACvD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,IACE,KAAK,KAAK,SAAS;YACnB,KAAK,CAAC,IAAI,KAAK,iBAAiB;YAChC,KAAK,CAAC,QAAQ,KAAK,QAAQ,EAC3B,CAAC;YACD,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS,sBAAsB;QAC7B,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,gDAAgD,UAAU,EAAE,CAC7D,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACrC,QAAQ,IAAI,CAAC,CAAC;YACd,MAAM,KAAK,GAAG,wBAAwB,EAAE,CAAC;YACzC,MAAM,YAAY,GAAG,SAAS,EAAE,CAAC;YACjC,IACE,YAAY,KAAK,SAAS;gBAC1B,YAAY,CAAC,IAAI,KAAK,kBAAkB,EACxC,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,wDAAwD,UAAU,EAAE,CACrE,CAAC;YACJ,CAAC;YACD,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACnC,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACvD,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACpC,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC9D,CAAC;QACD,MAAM,IAAI,KAAK,CACb,0GAA0G,UAAU,EAAE,CACvH,CAAC;IACJ,CAAC;IAED,SAAS,uBAAuB;QAC9B,IAAI,IAAI,GAAG,sBAAsB,EAAE,CAAC;QACpC,IAAI,QAAQ,GAAG,yBAAyB,EAAE,CAAC;QAC3C,OAAO,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,sBAAsB,EAAE,CAAC;YACvC,IAAI,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YAC3D,QAAQ,GAAG,yBAAyB,EAAE,CAAC;QACzC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,yBAAyB;QAChC,IAAI,IAAI,GAAG,uBAAuB,EAAE,CAAC;QACrC,OAAO,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,uBAAuB,EAAE,CAAC;YACxC,IAAI,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,wBAAwB;QAC/B,IAAI,IAAI,GAAG,yBAAyB,EAAE,CAAC;QACvC,OAAO,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,yBAAyB,EAAE,CAAC;YAC1C,IAAI,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,IAAI,QAAQ,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,4EAA4E,UAAU,EAAE,CACzF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,4BAA4B,GAAG,CACnC,KAAgC,EACoB,EAAE,CACtD,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AAE9C,MAAM,qBAAqB,GAAG,CAC5B,KAAgC,EACL,EAAE,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;AAEtE,MAAM,qBAAqB,GAAG,CAC5B,QAA2B,EAC3B,OAAkD,EACvB,EAAE;IAC7B,IAAI,OAAO,GAA8B,OAAO,CAAC;IACjD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,4BAA4B,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3C,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,0BAA0B,GAAG,CAAC,KAAgC,EAAU,EAAE,CAC9E,MAAM,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AAE9B,MAAM,0BAA0B,GAAG,CACjC,IAA+B,EAC/B,KAAgC,EACvB,EAAE;IACX,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACrE,CAAC;IACD,IACE,4BAA4B,CAAC,IAAI,CAAC;QAClC,4BAA4B,CAAC,KAAK,CAAC,EACnC,CAAC;QACD,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IACD,OAAO,0BAA0B,CAAC,IAAI,CAAC,KAAK,0BAA0B,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAAG,CAChC,KAAgC,EACvB,EAAE;IACX,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,4BAA4B,CAAC,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,KAAK,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAC7B,IAAoB,EACpB,OAAkD,EACvB,EAAE;IAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;QACnC,OAAO,qBAAqB,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,0BAA0B,CACxC,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAC1C,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAC5C,CAAC;QACF,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACrD,CAAC;IACD,MAAM,WAAW,GAAG,yBAAyB,CAC3C,sBAAsB,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAC3C,CAAC;IACF,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,CACL,WAAW;YACX,yBAAyB,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,CACL,WAAW;QACX,yBAAyB,CAAC,sBAAsB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CACvE,CAAC;AACJ,CAAC,CAAC;AAEK,MAAM,+BAA+B,GAAG,CAC7C,UAAkB,EAClB,OAAkD,EACzC,EAAE;IACX,MAAM,QAAQ,GAAG,4BAA4B,CAAC,UAAU,CAAC,CAAC;IAC1D,OAAO,yBAAyB,CAAC,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAC9E,CAAC,CAAC;AANW,QAAA,+BAA+B,mCAM1C"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkIssueSilentDispatchAllowed = void 0;
4
+ const isSilentDispatchAllowedByIssueBody_1 = require("../../../domain/usecases/isSilentDispatchAllowedByIssueBody");
5
+ const parseIssueUrl = (issueUrl) => {
6
+ const match = issueUrl.match(/github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/);
7
+ if (!match) {
8
+ return null;
9
+ }
10
+ return {
11
+ owner: match[1],
12
+ repo: match[2],
13
+ issueNumber: parseInt(match[4], 10),
14
+ };
15
+ };
16
+ const checkIssueSilentDispatchAllowed = async (input) => {
17
+ const parsed = parseIssueUrl(input.issueUrl);
18
+ if (!parsed) {
19
+ return {
20
+ stdout: null,
21
+ stderr: `Invalid GitHub issue URL: ${input.issueUrl}`,
22
+ exitCode: 2,
23
+ };
24
+ }
25
+ const { owner, repo, issueNumber } = parsed;
26
+ let response;
27
+ try {
28
+ response = await fetch(`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`, {
29
+ method: 'GET',
30
+ headers: {
31
+ Authorization: `Bearer ${input.ghToken}`,
32
+ Accept: 'application/vnd.github+json',
33
+ },
34
+ });
35
+ }
36
+ catch (error) {
37
+ return {
38
+ stdout: null,
39
+ stderr: `Failed to fetch issue ${input.issueUrl}: ${error instanceof Error ? error.message : String(error)}`,
40
+ exitCode: 2,
41
+ };
42
+ }
43
+ if (!response.ok) {
44
+ return {
45
+ stdout: null,
46
+ stderr: `Failed to fetch issue ${input.issueUrl}: ${response.status} ${response.statusText}`,
47
+ exitCode: 2,
48
+ };
49
+ }
50
+ let data;
51
+ try {
52
+ data = await response.json();
53
+ }
54
+ catch (error) {
55
+ return {
56
+ stdout: null,
57
+ stderr: `Failed to parse issue ${input.issueUrl}: ${error instanceof Error ? error.message : String(error)}`,
58
+ exitCode: 2,
59
+ };
60
+ }
61
+ const rawBody = typeof data === 'object' && data !== null && 'body' in data
62
+ ? data.body
63
+ : undefined;
64
+ const body = typeof rawBody === 'string' ? rawBody : null;
65
+ const isAllowed = (0, isSilentDispatchAllowedByIssueBody_1.isSilentDispatchAllowedByIssueBody)(body);
66
+ return { stdout: null, stderr: null, exitCode: isAllowed ? 0 : 1 };
67
+ };
68
+ exports.checkIssueSilentDispatchAllowed = checkIssueSilentDispatchAllowed;
69
+ //# sourceMappingURL=checkIssueSilentDispatchAllowed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkIssueSilentDispatchAllowed.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/checkIssueSilentDispatchAllowed.ts"],"names":[],"mappings":"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"}
@@ -52,6 +52,7 @@ const ConsoleErrorReportUseCase_1 = require("../../../domain/usecases/ConsoleErr
52
52
  const DashboardProjectCode_1 = require("../../../domain/usecases/dashboard/DashboardProjectCode");
53
53
  const OwnerCallFile_1 = require("../../../domain/usecases/intmux/OwnerCallFile");
54
54
  const NotifyFinishedIssuePreparationUseCase_1 = require("../../../domain/usecases/NotifyFinishedIssuePreparationUseCase");
55
+ const PullRequestProjectItemRemoveUseCase_1 = require("../../../domain/usecases/PullRequestProjectItemRemoveUseCase");
55
56
  const RevertOrphanedPreparationUseCase_1 = require("../../../domain/usecases/RevertOrphanedPreparationUseCase");
56
57
  const StartPreparationUseCase_1 = require("../../../domain/usecases/StartPreparationUseCase");
57
58
  const iso8601UtcDateTimePattern_1 = require("../../../domain/services/iso8601UtcDateTimePattern");
@@ -81,6 +82,7 @@ const consoleGithubTokenResolver_1 = require("../console/consoleGithubTokenResol
81
82
  const readOnlyTokenRotator_1 = require("../console/readOnlyTokenRotator");
82
83
  const githubAppTokenMinter_1 = require("./githubAppTokenMinter");
83
84
  const unresumableSessionArchive_1 = require("./unresumableSessionArchive");
85
+ const checkIssueSilentDispatchAllowed_1 = require("./checkIssueSilentDispatchAllowed");
84
86
  const consoleProjectResolver_1 = require("../console/consoleProjectResolver");
85
87
  const consoleReadApi_1 = require("../console/consoleReadApi");
86
88
  const ensureConsoleRunning_1 = require("../console/ensureConsoleRunning");
@@ -397,6 +399,7 @@ exports.program
397
399
  .option('--sessionErrorLine <line>', 'Exact error line from the session log to include in the task issue body')
398
400
  .option('--deferPreparation', 'Defer the item via the Reactivation Trigger fields (sets nextActionDate to tomorrow) without creating any issue; use for transient upstream failures')
399
401
  .option('--rateLimitRejected', 'Return the item to Awaiting Workspace without incrementing the consecutive-no-report counter; use when the session ended due to an API rate limit rejection')
402
+ .option('--promptTooLongOnResume', 'Return the item to Awaiting Workspace without incrementing the consecutive-no-report counter; use when a resumed session failed immediately because its carried-over conversation exceeded the model prompt length limit')
400
403
  .option('--moveToFailedPreparation', 'Move the item to Failed Preparation status after reaching the consecutive failure threshold')
401
404
  .option('--fleetConfigFilePath <path>', 'Path to fleet config YAML file (also read from TDPM_FLEET_CONFIG env var)')
402
405
  .option('--dispatchStartedAt <timestamp>', "ISO-8601 UTC timestamp (for example 2026-01-31T09:00:00Z) at which the item entered Preparation for the session that just ended; an agent report posted before it is not counted as that session's report, so a session that posts nothing is counted toward the consecutive-no-report threshold")
@@ -503,6 +506,7 @@ exports.program
503
506
  defaultAgentName: config.defaultAgentName ?? null,
504
507
  deferPreparation: options.deferPreparation ?? null,
505
508
  rateLimitRejected: options.rateLimitRejected ?? null,
509
+ promptTooLongOnResume: options.promptTooLongOnResume ?? null,
506
510
  moveToFailedPreparation: options.moveToFailedPreparation ?? null,
507
511
  workflowIssueReporterSettings: notifyWorkflowIssueReporterSettings,
508
512
  tdpmReportingRepository: notifyEffectiveErrorReportingRepo,
@@ -876,6 +880,29 @@ exports.program
876
880
  await tmuxSessionRepository.killSession(options.session);
877
881
  }
878
882
  });
883
+ exports.program
884
+ .command('removePullRequestProjectItems')
885
+ .description('List every item in a GitHub Project V2 board, back up the identifier, URL, Status value, and story value of every item whose content is a pull request to --backupFilePath, then remove each of those pull-request items from the board.')
886
+ .requiredOption('--projectId <projectId>', 'GitHub Project V2 node ID')
887
+ .requiredOption('--backupFilePath <path>', 'File path to write the JSON backup of matched pull-request items before deletion')
888
+ .action(async (options) => {
889
+ const token = process.env.GH_TOKEN;
890
+ if (!token) {
891
+ console.error('GH_TOKEN environment variable is required');
892
+ process.exit(1);
893
+ }
894
+ const localStorageRepository = new LocalStorageRepository_1.LocalStorageRepository();
895
+ const graphqlProjectItemRepository = new GraphqlProjectItemRepository_1.GraphqlProjectItemRepository(localStorageRepository, token);
896
+ const useCase = new PullRequestProjectItemRemoveUseCase_1.PullRequestProjectItemRemoveUseCase(graphqlProjectItemRepository);
897
+ const matchedItems = await useCase.findPullRequestItems(options.projectId);
898
+ localStorageRepository.write(options.backupFilePath, JSON.stringify(matchedItems, null, 2));
899
+ const { succeededUrls, failures } = await useCase.removeItems(options.projectId, matchedItems);
900
+ console.log(`removePullRequestProjectItems: matched ${matchedItems.length}, removed ${succeededUrls.length}, failed ${failures.length}. Backup written to ${options.backupFilePath}`);
901
+ if (failures.length > 0) {
902
+ console.error(`removePullRequestProjectItems: failed to remove ${failures.length} item(s): ${JSON.stringify(failures)}`);
903
+ process.exitCode = 1;
904
+ }
905
+ });
879
906
  exports.program
880
907
  .command('ownerCallFileAppend')
881
908
  .description('Append one owner call as a YAML document to the per-session owner call file. The project the session belongs to is resolved from the in-tmux-by-human data in the data directory, and a session no project lists is written under NA. The file is created when it does not exist, and every later call is appended after the existing documents so the oldest call stays first. Nothing is written to stdout on success.')
@@ -942,6 +969,28 @@ exports.program
942
969
  return process.exit(output.exitCode);
943
970
  }
944
971
  });
972
+ exports.program
973
+ .command('check-issue-silent-dispatch-allowed')
974
+ .description('Parse owner/repo/issue-number from --issue-url, fetch that issue body via the GitHub REST API using the GH_TOKEN environment variable, and evaluate it with isSilentDispatchAllowedByIssueBody. Exits 0 when silent dispatch is allowed for this issue, 1 when it is not, and 2 with a diagnostic on stderr when the URL cannot be parsed or the GitHub API call fails. Writes nothing to stdout for exit codes 0 or 1.')
975
+ .requiredOption('--issue-url <url>', 'GitHub issue URL')
976
+ .action(async (options) => {
977
+ const token = process.env.GH_TOKEN;
978
+ if (!token) {
979
+ console.error('GH_TOKEN environment variable is required');
980
+ return process.exit(2);
981
+ }
982
+ const output = await (0, checkIssueSilentDispatchAllowed_1.checkIssueSilentDispatchAllowed)({
983
+ issueUrl: options.issueUrl,
984
+ ghToken: token,
985
+ });
986
+ if (output.stdout !== null) {
987
+ console.log(output.stdout);
988
+ }
989
+ if (output.stderr !== null) {
990
+ console.error(output.stderr);
991
+ }
992
+ return process.exit(output.exitCode);
993
+ });
945
994
  const reportFatalErrorAndExit = (error) => {
946
995
  console.error((0, errorSanitizer_1.sanitizeErrorForLogging)(error));
947
996
  process.exit(1);