Hack — 18 Operations for AI Agents
Hack runs some of the largest PHP-descended codebases on earth, with gradual types layered onto web-scale code. act101 navigates classes, functions, and types structurally, so agents move through Hack code at the scale it was built for.
This page is the canonical reference an AI coding agent uses to refactor, query, and analyze Hack code through the act MCP server. 18 operations available: 0 refactor, 18 query, 0 analysis. Each operation is callable from Claude Code, Cursor, Codex, OpenCode, or any MCP-compatible agent host. Click any operation for a stable anchor link suitable for citation.
Worked Hack examples
act101 reads a Hack file's class and interface declarations as their own kinds, named for the declared type, and every method inside them — including a promoted-parameter constructor like __construct — as method. symbols reports the identical set of declarations, in the same order and under the same kinds, but narrows each entry's range from the whole declaration span the skeleton gives down to just the name. The unit of structure in this grammar is the class member: neither query descends further into a method's own parameter list or body, so a promoted constructor property like public string $sku is visible only as part of __construct's own range, never as its own declaration. Each example below is the verbatim output of the command shown, run against the file shown. Query outputs are pretty-printed with the timing block omitted.
Read the reconciler's classes, interface, and methods as a skeleton
InventoryReconciler.hack compares a warehouse's expected stock ledger against a physical count: StockDiscrepancy holds one mismatch, the CountSource interface abstracts the count lookup, and WarehouseReconciler walks the ledger calling it.
$ act query skeleton InventoryReconciler.hack
Before
<?hh
class StockDiscrepancy {
public function __construct(
public string $sku,
public int $expected,
public int $counted,
) {}
public function delta(): int {
return $this->counted - $this->expected;
}
}
interface CountSource {
public function countFor(string $sku): int;
}
class WarehouseReconciler {
private vec<StockDiscrepancy> $discrepancies = vec[];
public function __construct(private CountSource $source) {}
public function reconcile(dict<string, int> $ledger): vec<StockDiscrepancy> {
foreach ($ledger as $sku => $expected) {
$counted = $this->source->countFor($sku);
if ($counted !== $expected) {
$this->discrepancies[] = new StockDiscrepancy($sku, $expected, $counted);
}
}
return $this->discrepancies;
}
}
Output
{
"type": "Skeleton",
"declarations": [
{
"kind": "class",
"name": "StockDiscrepancy",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 3,
"column": 1,
"byte_offset": 6
},
"end": {
"file": "InventoryReconciler.hack",
"line": 12,
"column": 2,
"byte_offset": 251
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 3,
"column": 7,
"byte_offset": 12
},
"end": {
"file": "InventoryReconciler.hack",
"line": 3,
"column": 23,
"byte_offset": 28
}
}
},
{
"kind": "method",
"name": "__construct",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 4,
"column": 5,
"byte_offset": 35
},
"end": {
"file": "InventoryReconciler.hack",
"line": 8,
"column": 9,
"byte_offset": 159
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 4,
"column": 21,
"byte_offset": 51
},
"end": {
"file": "InventoryReconciler.hack",
"line": 4,
"column": 32,
"byte_offset": 62
}
}
},
{
"kind": "method",
"name": "delta",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 9,
"column": 5,
"byte_offset": 164
},
"end": {
"file": "InventoryReconciler.hack",
"line": 11,
"column": 6,
"byte_offset": 249
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 9,
"column": 21,
"byte_offset": 180
},
"end": {
"file": "InventoryReconciler.hack",
"line": 9,
"column": 26,
"byte_offset": 185
}
}
},
{
"kind": "interface",
"name": "CountSource",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 13,
"column": 1,
"byte_offset": 252
},
"end": {
"file": "InventoryReconciler.hack",
"line": 15,
"column": 2,
"byte_offset": 325
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 13,
"column": 11,
"byte_offset": 262
},
"end": {
"file": "InventoryReconciler.hack",
"line": 13,
"column": 22,
"byte_offset": 273
}
}
},
{
"kind": "method",
"name": "countFor",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 5,
"byte_offset": 280
},
"end": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 48,
"byte_offset": 323
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 21,
"byte_offset": 296
},
"end": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 29,
"byte_offset": 304
}
}
},
{
"kind": "class",
"name": "WarehouseReconciler",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 16,
"column": 1,
"byte_offset": 326
},
"end": {
"file": "InventoryReconciler.hack",
"line": 29,
"column": 2,
"byte_offset": 862
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 16,
"column": 7,
"byte_offset": 332
},
"end": {
"file": "InventoryReconciler.hack",
"line": 16,
"column": 26,
"byte_offset": 351
}
}
},
{
"kind": "method",
"name": "__construct",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 5,
"byte_offset": 416
},
"end": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 64,
"byte_offset": 475
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 21,
"byte_offset": 432
},
"end": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 32,
"byte_offset": 443
}
}
},
{
"kind": "method",
"name": "reconcile",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 20,
"column": 5,
"byte_offset": 481
},
"end": {
"file": "InventoryReconciler.hack",
"line": 28,
"column": 6,
"byte_offset": 860
}
},
"name_range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 20,
"column": 21,
"byte_offset": 497
},
"end": {
"file": "InventoryReconciler.hack",
"line": 20,
"column": 30,
"byte_offset": 506
}
}
}
]
}
The skeleton reports eight declarations: two classes (StockDiscrepancy, WarehouseReconciler), one interface (CountSource), and five methods — __construct appears twice, once per class, alongside delta, countFor, and reconcile.
List the same declarations as symbols, narrowed to their names
WarehouseReconciler.reconcile is the method that actually produces the mismatch list, iterating the ledger and calling CountSource.countFor for each SKU.
$ act query symbols InventoryReconciler.hack
Before
<?hh
class StockDiscrepancy {
public function __construct(
public string $sku,
public int $expected,
public int $counted,
) {}
public function delta(): int {
return $this->counted - $this->expected;
}
}
interface CountSource {
public function countFor(string $sku): int;
}
class WarehouseReconciler {
private vec<StockDiscrepancy> $discrepancies = vec[];
public function __construct(private CountSource $source) {}
public function reconcile(dict<string, int> $ledger): vec<StockDiscrepancy> {
foreach ($ledger as $sku => $expected) {
$counted = $this->source->countFor($sku);
if ($counted !== $expected) {
$this->discrepancies[] = new StockDiscrepancy($sku, $expected, $counted);
}
}
return $this->discrepancies;
}
}
Output
{
"type": "Symbols",
"symbols": [
{
"name": "StockDiscrepancy",
"kind": "class",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 3,
"column": 7,
"byte_offset": 12
},
"end": {
"file": "InventoryReconciler.hack",
"line": 3,
"column": 23,
"byte_offset": 28
}
},
"visibility": "unknown"
},
{
"name": "__construct",
"kind": "method",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 4,
"column": 21,
"byte_offset": 51
},
"end": {
"file": "InventoryReconciler.hack",
"line": 4,
"column": 32,
"byte_offset": 62
}
},
"visibility": "unknown"
},
{
"name": "delta",
"kind": "method",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 9,
"column": 21,
"byte_offset": 180
},
"end": {
"file": "InventoryReconciler.hack",
"line": 9,
"column": 26,
"byte_offset": 185
}
},
"visibility": "unknown"
},
{
"name": "CountSource",
"kind": "interface",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 13,
"column": 11,
"byte_offset": 262
},
"end": {
"file": "InventoryReconciler.hack",
"line": 13,
"column": 22,
"byte_offset": 273
}
},
"visibility": "unknown"
},
{
"name": "countFor",
"kind": "method",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 21,
"byte_offset": 296
},
"end": {
"file": "InventoryReconciler.hack",
"line": 14,
"column": 29,
"byte_offset": 304
}
},
"visibility": "unknown"
},
{
"name": "WarehouseReconciler",
"kind": "class",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 16,
"column": 7,
"byte_offset": 332
},
"end": {
"file": "InventoryReconciler.hack",
"line": 16,
"column": 26,
"byte_offset": 351
}
},
"visibility": "unknown"
},
{
"name": "__construct",
"kind": "method",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 21,
"byte_offset": 432
},
"end": {
"file": "InventoryReconciler.hack",
"line": 18,
"column": 32,
"byte_offset": 443
}
},
"visibility": "unknown"
},
{
"name": "reconcile",
"kind": "method",
"range": {
"start": {
"file": "InventoryReconciler.hack",
"line": 20,
"column": 21,
"byte_offset": 497
},
"end": {
"file": "InventoryReconciler.hack",
"line": 20,
"column": 30,
"byte_offset": 506
}
},
"visibility": "unknown"
}
]
}
symbols reports the identical eight declarations under the identical kinds, but each entry's range narrows from the whole class or method body skeleton gave it down to just the name — WarehouseReconciler's range, for example, shrinks from its full multi-line body to the twenty characters of its own name.
Query
18 query tools, the same on every supported language. Descriptions live in the shared reference: /docs/query-tools.
callers control_flow data_flow definition diagnostics effect_closure effect_summary fix_auto get_type graph import_organize interface mutations references repo_outline skeleton symbols symbols_batch