Sections
On this page
map-components
Use when a Flownix project has no system components yet, or has them and the accumulated work is still unlinked — triggers on "map the components", "what does this system consist of", "link the tasks to services", "what stack do we use", "w
Map the System into Components
Turn a project that has been tracking work as a flat list into one that knows the shape of the system it is building — its code, and what that code runs on. Prerequisite: read flownix-basics, section System components.
The rule this skill is built around
Never link silently. A pass over an existing project by heuristics WILL contain mistakes. A missed link is visible — the component's section is empty, and the "No component" filter lists it. A wrong link is visible to nobody: it quietly moves a task under a service it has nothing to do with, and the progress of that service is wrong from then on.
So: propose, explain the ground for each proposal, and apply only what a human confirmed.
Workflow
0. Load project context
cat .flownix 2>/dev/null || cat .ai-flowUse project_id in every call below. If neither file exists, run flownix-init first.
1. Inventory the code
Read the structure, not the names. Walk:
services/*,apps/*,packages/*,cmd/*- top-level applications:
web,admin,landing,mobile - shared and infrastructural directories:
pkg,lib,migrations,deploy,k8s,infra - build and orchestration manifests:
docker-compose.yml,go.work,package.jsonworkspaces,Cargo.toml,pyproject.toml,Makefiletargets, CI workflow files
Cross-check the directories against the manifests. A directory that no manifest builds, deploys or references is probably not a component — it is dead code or a scratch folder, and naming it a component invites people to file work against it.
Invent nothing. A component that does not exist in the repository is worse than one you missed: the missed one nobody links to, the invented one collects work.
2. Inventory the stack and the infrastructure
Half the work in a real project is not "a task about gateway" — it is a migration, an index rebuild, a broken pipeline, a version bump. That work has no owner among the code components, and it is exactly the work people later search for: "what have we done to the database", "everything that touched the deploy". So the inventory has a second half.
Read the declarations, not your memory of the ecosystem:
| Where | What it declares |
|---|---|
docker-compose.yml, *.dev.yml | the datastores and brokers the system actually runs against |
migrations/, schema.sql, ORM config | the database, and the fact that schema work is a stream of its own |
k3s/, k8s/, helm/, terraform/, Dockerfile* | how it is deployed, and by what |
.github/workflows/, .gitlab-ci.yml, Makefile | the pipeline — the thing that breaks on Fridays |
go.mod, package.json, pyproject.toml, Cargo.toml | frameworks and clients: the driver names the datastore |
.env.example, config structs, secrets manifests | external services: object storage, LLM providers, payments, mail |
Evidence, not ecosystem knowledge. A Redis client in go.mod and a redis service in
compose is evidence. "Projects like this usually cache in Redis" is not — an invented infra
component collects work that then describes something the system does not have.
Two layers come out of this, and they are not interchangeable:
The stack of a component — the languages, frameworks and libraries it is written in. This
is not a component: it is tags plus one line in the description (step 3). A component per
library gives you a hundred leaves nobody links to.
A shared dependency — a datastore, broker, platform or external service that several components use, and against which work is filed on its own. This is a component:
component_kind: "infra"— what you run:postgres,qdrant,redis,kafka,k3s/helm,ci.component_kind: "integration"— what you call:openai,s3,stripe,smtp.
The threshold between the two layers, when a case is unclear: has work ever been filed against this alone? A migration, an upgrade, an outage, a quota — yes, it earns a component. If every mention of it is really a task about the service that uses it — it is a tag.
3. Create the components
For each part of the system:
list_components({ project_id }) // ALWAYS first — see idempotency below
create_component({
project_id,
title: "core-service", // the name it has in the repository
repo_path: "services/core-service",
component_kind: "service", // service | app | library | infra | integration
description: "Purpose. Boundaries. Entry points.\nСтек: Go 1.23, gRPC, PostgreSQL (AF-COMP-9).",
parent: "AF-COMP-1", // omit for a top-level component
tags: ["go", "grpc", "postgres"],
})The tree mirrors the repository: monorepo → group (services) → service. Two or three
levels is what the UI is built for. Infra and integration components sit at the top level
next to the code groups — they are not part of any one service, that is the whole point;
or under one group (infra, platform) when there are more than three of them.
An infra component gets a repo_path too — the strongest evidence for it, the place where
its work actually lands: migrations for the database, k3s for the cluster,
.github/workflows for the pipeline. An integration to an external service usually has none;
name the config or client package instead, and leave repo_path empty rather than inventing
a path.
Write the stack into the description, and tag it. Descriptions are what rag_query
searches (step 4) and what a future agent reads to decide whether its task belongs here, so
a Стек: line naming the language, framework and the datastores it talks to — with the slugs
of the infra components — is what makes "which services go to Postgres" answerable at all.
Tags carry the same words in normalized form: one spelling per technology across the whole
project (postgres, never also postgresql and pg), lowercase. Two spellings split the
answer in half and nothing warns you.
The skill must be safe to re-run. Start with list_components and skip anything whose
repo_path or title already exists. Duplicated components split a service's work in half
and nothing warns you. On a re-run over a project that already has code components but no
stack layer, do not recreate anything: add the missing infra components, and bring the
existing descriptions up to date with update_component (it changes only the fields you
pass) — tags is not among them, so use add_tag for those.
4. Gather candidate links
For each component, collect candidates from several independent angles — one angle alone finds only what it is shaped to find:
rag_query({ project_id, query: "<component name> <purpose> <repo path>", k: 20 })
list_nodes({ project_id, kind: "task" }) // titles, tags, ticket_ref
list_docs({ project_id })
list_reviews({ project_id })Grounds worth recording, roughly in descending order of trust:
| Ground | Example |
|---|---|
| the path appears in the node's body or commit | services/gateway/... in the task text |
| the branch of a review touches the component's path | fix/gateway-timeout |
| tag matches the component | #rag → rag-service |
| the component name appears in the title | «Обработчики gateway…» |
| semantic hit on the description alone | a task about routing → gateway |
For the infra and integration components the same trust order holds, on their own evidence:
| Ground | Component |
|---|---|
the node touches migrations/, or the body carries DDL / an index / a query plan | the database |
the node touches k3s/, helm/, Dockerfile, or the body carries a deploy failure | the platform |
the node touches .github/workflows/, or is about a red pipeline | the CI |
| the body names the client, the collection, the topic, the bucket, the model | that datastore / broker / storage / provider |
| semantic hit alone: «переиндексация», «эмбеддинги», «квоты» | uncertain — propose it as such |
Infra links are additional, not alternative. A task that added a migration to core-service
belongs to core-service and to postgres; links are many-to-many, and dropping either one
loses an answer someone will later ask for. Docs follow the same rule: a schema description,
an ADR about the queue, a deploy runbook get the infra component, not only the service they
were written for — a spec reachable only from a service is invisible from the thing it
actually documents.
5. Propose in batches
Group proposals by component, and inside each group split them:
- Confident — path, branch or explicit name. Confirmed in one answer for the whole group.
- Uncertain — a semantic hit alone, or a tag that fits several components. Listed separately, one line each, so the human can reject individually.
Mixing the two costs the confident ones their speed and the uncertain ones their scrutiny.
Apply only what was confirmed:
set_node_components({ node: "AF-TASK-341", components: ["AF-COMP-3", "AF-COMP-9"] })
set_review_components({ review_id: "...", components: ["AF-COMP-3"] })set_node_components replaces the whole set. When a node already has links, include them
in the list unless the human asked to drop them — otherwise the confirmation silently
unlinks whatever was there before. This is what makes the infra pass dangerous when it runs
second: adding postgres to a task means sending ["AF-COMP-3", "AF-COMP-9"], not
["AF-COMP-9"].
6. Report
State plainly:
- how many nodes, docs and reviews were linked, and to what;
- the stack layer separately: which infra and integration components were created, on what evidence, and how much work each one collected — a datastore that ended up with two links in a project full of migrations means the grounds missed, not that the work does not exist;
- what was proposed and rejected;
- what is left with no component, and why — no obvious owner, spans everything, or predates the part of the system it touches.
The leftovers are the useful half of the report: they are found later with the
"No component" filter (component: "none" in list_nodes / list_docs / list_reviews),
and this report is what explains them.
Acceptance
- Every created component matches a real part of the repository, with
repo_pathfilled — and every infra/integration component names the manifest that declares it. - Every code component's description carries its stack line, and its tags use the project's one spelling per technology.
- A second run creates nothing new.
- Every applied link was confirmed by a human and had a stated ground.
- Adding an infra link never dropped the code link that was already there.
- The report names what stayed unlinked.